/*	Copyright  (c)	Günter Woigk 2013 - 2013
					mailto:kio@little-bat.de

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	Permission to use, copy, modify, distribute, and sell this software and
	its documentation for any purpose is hereby granted without fee, provided
	that the above copyright notice appear in all copies and that both that
	copyright notice and this permission notice appear in supporting
	documentation, and that the name of the copyright holder not be used
	in advertising or publicity pertaining to distribution of the software
	without specific, written prior permission.  The copyright holder makes no
	representations about the suitability of this software for any purpose.
	It is provided "as is" without express or implied warranty.

	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
	PERFORMANCE OF THIS SOFTWARE.
*/


#include "Sio.h"
#include "Cpu.h"
#include "unix/os_utilities.h"
#include "unix/pthreads.h"


/*	Baud rates for 3.6864 MHz:
 */
float baudrate[2][3][13] =	// [SET][MR0][CSR]
{
	{
		{ 50, 110, 134.5, 200, 300, 600, 1200, 1050, 2400, 4800, 7200, 9600, 38400 },
		{ 300, 110, 134.5, 1200, 1800, 3600, 7200, 1050, 14400, 28800, 7200, 57.600, 230400 },
		{ 4800, 880, 1076, 19.200, 28.800, 57.600, 115200, 1050, 57.600, 4800, 57600, 9600, 38400 }
	},
	{
		{ 75, 110, 134.5, 150, 300, 600, 1200, 2000, 2400, 4800, 1800, 9600, 19200 },
		{ 450, 110, 134.5, 900, 1800, 3600, 7200, 2000, 14400, 28.800, 1800, 57600, 115200 },
		{ 7200, 880, 1076, 14400, 28800, 57600, 115200, 2000, 57600, 4800, 14400, 9600, 19200 }
	}
};



enum			// READ register:
{
	MR0A = 0, 	// Mode Registers A
	MR1A = 0,
	MR2A = 0,
	SRA	 = 1,	// Status Register A
	UNUSED1,	//
	RXA	 = 3,	// Receive Buffer A
	IPCR = 4,	// Input Port Change Register
	ISR	 = 5,	// Interrupt Status Register
	CUR	 = 6,	// Counter/Timer Upper Register
	CLR	 = 7,	// Counter/Timer Lower Register

	MR0B = 8,	// Mode Registers B
	MR1B = 8,
	MR2B = 8,
	SRB	 = 9,	// Status Register B
	UNUSED2,	//
	RXB	 = 11,	// Receive Buffer B
	GPR	 = 12,	// General Purpose Register
	IPR	 = 13,	// Input Port Register
	STCR = 14,	// Start C/T Command
	SPCR = 15	// Stop C/T Command
};

enum			// WRITE register:
{
//	MR0A = 0,	// Mode Registers A
	CSRA = 1,	// Clock Select Register A
	CRA	 = 2,	// Command Register A
	TXA	 = 3,	// Transmitter Buffer A
	ACR	 = 4,	// Auxiliary Control Register
	IMR	 = 5,	// Interrupt Mask Register
	CTPU = 6,	// C/T Preload Value Upper Register
	CTPL = 7,	// C/T Preload Value Lower Register

//	MR0B = 8,	// Mode Registers B
	CSRB = 9,	// Clock Select Register B
	CRB	 = 10,	// Command Register B
	TXB	 = 11,	// Transmitter Buffer B
//	GPR	 = 12,	// General Purpose Register
	OPCR = 13,	// Output Port Configuration Register
	SOPR = 14,	// Set Output Port Register
	ROPR = 15	// Reset Output Port Register
};


enum
{
// bits in status register SRA & SRB:
	mSR_RX_ready	= 1,
	mSR_RX_full		= 2,
	mSR_TX_ready 	= 4,
	mSR_TX_empty 	= 8,
	mSR_RX_overrun	= 16,
	mSR_RX_parity	= 32,
	mSR_RX_framing	= 64,
	mSR_RX_break	= 128,

// bits in interrupt mask register IMR and interrupt status register ISR
	mIMR_TXA_ready 	= 1,
	mIMR_RXA_ready	= 2,
	mIMR_RXA_break	= 4,
	mIMR_CT_ready	= 8,
	mIMR_TXB_ready	= 16,
	mIMR_RXB_ready	= 32,
	mIMR_RXB_break	= 64,
	mIMR_IP_changed	= 128
};




//	thread helpers:
void* fu_rxa( void* arg ) { Sio* sio = (Sio*)arg; sio->fu_rxa(); return NULL; }
void* fu_rxb( void* arg ) { Sio* sio = (Sio*)arg; sio->fu_rxb(); return NULL; }
void* fu_txa( void* arg ) { Sio* sio = (Sio*)arg; sio->fu_txa(); return NULL; }
void* fu_txb( void* arg ) { Sio* sio = (Sio*)arg; sio->fu_txb(); return NULL; }
void* fu_ct ( void* arg ) { Sio* sio = (Sio*)arg; sio->fu_ct();  return NULL; }


// -------------------------------------------------------
//			CTOR
// -------------------------------------------------------


Sio::Sio(QObject *parent, uint my_bit, cstr eeprom_filepath)
:	Device(parent,my_bit,eeprom_filepath),
	xtal(7372000),
	mra0(0),mra1(0),mra2(0),
	mrb0(0),mrb1(0),mrb2(0),
	csra(0),csrb(0),
	acr(0),
	isr(0), imr(0),
	ctpu(0),ctpl(0),
	gpr(0), ipr(0),
	opcr(0),opr(0),
	sra(mSR_TX_ready|mSR_TX_empty),
	srb(mSR_TX_ready|mSR_TX_empty),
	mra_ptr(1),
	mrb_ptr(1),
	ct_tid(0)
{
	ct_period = (1<<16)*32.0/xtal;

	assert((bool)2 == 1);

	int err = 0;
	err |= pthread_create(&tid_rxa,NULL,::fu_rxa,this);
	err |= pthread_create(&tid_txa,NULL,::fu_txa,this);
	err |= pthread_create(&tid_rxb,NULL,::fu_rxb,this);
	err |= pthread_create(&tid_txb,NULL,::fu_txb,this);
	assert(err==0);


}


// -------------------------------------------------------
//			Helper
// -------------------------------------------------------


// helper: set bits in isr
inline void Sio::set_bits_in_isr(uint8 mask)
{
	bool old = isr&imr;
	isr |= mask;
	bool nju = isr&imr;
	if( old!=nju ) set_irpt(nju);
}

// helper: reset bits in isr
inline void Sio::reset_bits_in_isr(uint8 mask)
{
	bool old = isr&imr;
	isr &= ~mask;
	bool nju = isr&imr;
	if( old!=nju ) set_irpt(nju);
}

// helper: calculate time per character
double Sio::calc_tpc(uint8 acr, uint8 mr0, uint8 csr, bool tx)
{
	acr = acr>>7;
	mr0 = mr0&4?2:mr0&2?1:0;
	csr = tx ? csr&15 : csr>>4;

	if(csr<13) return 10 * 3686400.0 / xtal / baudrate[acr][mr0][csr];
	if(csr==13){} // timer
	if(csr==14){} // txa: IP3/16, rxa: IP4/16, txb: IP5/16, rxb: IP6/16
	if(csr==14){} // txa: IP3/1,  rxa: IP4/1,  txb: IP5/1,  rxb: IP6/1
	return 1e-4;
}


// -------------------------------------------------------
//			OUTPUT to UART
// -------------------------------------------------------


void Sio::wr_data(uint16 addr, uint16 n)
{
	switch(addr&0x0f)
	{
	case GPR:		// General Purpose Register
		gpr = n;
		break;

	case TXA:		// Transmitter Buffer A
		lock();
			txa.iwr(n);
			sra &= ~mSR_TX_empty;
			if(txa.ifull()) { reset_bits_in_isr(mIMR_TXA_ready); sra &= ~mSR_TX_ready; }
		unlock();
		break;

	case TXB:		// Transmitter Buffer B
		lock();
			txb.iwr(n);
			srb &= ~mSR_TX_empty;
			if(txb.ifull()) { reset_bits_in_isr(mIMR_TXB_ready); srb &= ~mSR_TX_ready; }
		unlock();
		break;

	case IMR:		// Interrupt Mask Register
	{
		lock();
			bool old = isr&imr;
			imr = n;
			bool nju = isr&imr;
			if(old!=nju) set_irpt(isr&imr);
		unlock();
		break;
	}
	case CTPU:						// C/T Preload Value Upper Register
		ctpu = n;
		ct_period = (ctpu*256+ctpl) * 32.0 / xtal;
		break;

	case CTPL:						// C/T Preload Value Lower Register
		ctpl = n;
		ct_period = (ctpu*256+ctpl) * 32.0 / xtal;
		break;

	case CSRA:		// Clock Select Register A
		csra = n;
		txa.tpc = calc_tpc(acr,mra0,csra,1);
		rxa.tpc = calc_tpc(acr,mra0,csra,0);
		break;

	case CSRB:		// Clock Select Register B
		csrb = n;
		txb.tpc = calc_tpc(acr,mrb0,csrb,1);
		rxb.tpc = calc_tpc(acr,mrb0,csrb,0);
		break;

	case SOPR:		// Set Output Port Register
		opr &= ~n;	// bit=1 => assert (set to low)
		break;		// superseeded by special functions for pins

	case ROPR:		// Reset Output Port Register
		opr |= n;	// bit=1 => negate (set to high)
		break;		// superseeded by special functions for pins

	case OPCR:		// Output Port Configuration Register
		opcr = n;
		// bits 1-0: OP2 mode select:	01	TxA Clk x 16
		//								10	TxA Clk x 1
		//								11	RxA Clk x 1
		// bits 3-2: OP3 mode select:	01	c/t output
		//								10	TxB Clk x 1
		//								11	RxB Clk x 1
		// bit  4:   OP4 mode select:	-RxARDY which is the complement of ISR bit-1
		// bit  5:   OP5 mode select:	-RxBRDY which is the complement of ISR bit-5
		// bit  6:   OP6 mode select:	-TxARDY which is the complement of ISR bit-0
		// bit  7:   OP7 mode select:	-TxBRDY which is the complement of ISR bit-4
		break;

	case ACR:						// Auxiliary Control Register

		if((n^acr)&128)				// bit 7: baud rate select table: Set 0 or Set 1
		{
			txa.tpc = calc_tpc(n,mra0,csra,1);
			rxa.tpc = calc_tpc(n,mra0,csra,0);
			txb.tpc = calc_tpc(n,mrb0,csrb,1);
			rxb.tpc = calc_tpc(n,mrb0,csrb,0);
		}

		// bits 3-0: which input ports generate interrupts
		{}	//NOP

		switch((n>>4)&7)			// bits 6-4: c/t clock source
		{
		case 0: break; // counter: IP2
		case 1:	break; // counter: TXA clock / 1
		case 2:	break; // counter: TXB clock / 1
		case 3:	break; // counter: XTAL / 16
		case 4:	break; // timer: IP2
		case 5:	break; // timer: IP2 / 16
		case 6:	break; // timer: XTAL / 1
		case 7:	break; // timer: XTAL / 16
		}

		acr = n;
		break;

	case MR0A:						// Mode Registers A
		switch(mra_ptr)
		{
		case 0:
			// bits 2,0: extended baudrate tables
			if((mra0^n)&5)
			{
				txa.tpc = calc_tpc(acr,n,csra,1);
				rxa.tpc = calc_tpc(acr,n,csra,0);
			}
			// bit 1: factory test mode
			// bit 3: not used

			// bits 5,4: transmit irpt trigger level	((88192))
			// 00 16 bytes free (dflt)
			// 01  6 bytes free
			// 10 12 bytes free
			// 11  1 byte  free

			// mr0.6 + mr1.6: receive irpt trigger level	((88192))
			// 00  1 byte available (dflt)
			// 01  6 bytes available
			// 10 12 bytes available
			// 11 16 bytes available

			// bit 7: receive time-out (watchdog)
			// required if rx irpt trigger level is set to > 1

			mra0 = n;
			mra_ptr = 1;
			break;

		case 1:
			// bits 1,0: character length
			// bit  2:   In Multidrop mode, this bit is the Address/Data flag. 0 = Data (default), 1 = Address
			// bits 4,3: parity mode
			// bit  5:   data error mode: 0=single character, 1=block(fifo) mode
			// bit  6:   see mr0
			// bit  7:   receiver RTS flow control: OP0 (ch.B: OP1) serves as RTS output

			mra1 = n;
			mra_ptr = 2;
			break;

		case 2:
			// bits 3-0: stop bit length
			// bit  4:   auto CTS flow control
			// bit  5:   auto transmit RTS control: OP0 (ch.B: OP1) serves as RTS output
			// bits 7,6: loop back mode
			//			 00 = no loopback (dflt)
			//			 01 = automatic echo
			//			 10 = local loop back
			//			 11 = remote loop back

			mra2 = n;
			break;
		}
		break;

	case MR0B:						// Mode Registers B
		switch(mrb_ptr)
		{
		case 0:
			if((mrb0^n)&5)
			{
				txb.tpc = calc_tpc(acr,n,csrb,1);
				rxb.tpc = calc_tpc(acr,n,csrb,0);
			}
			mrb0 = n;
			mrb_ptr = 1;
			break;
		case 1:
			mrb1 = n;
			mrb_ptr = 2;
			break;
		case 2:
			mrb2 = n;
			break;
		}
		break;

	case CRA:						// Command Register A
		if(n&1) this->rxa.enabled = true; 	// enable rx
		if(n&2) this->rxa.enabled = false;	// disable rx
		if(n&4) this->txa.enabled = true;	// enable tx
		if(n&8) this->txa.enabled = false;	// disable tx
		switch((n>>4)&15)
		{
		case  0: break;										// no command
		case 13: break;										// not used
		case  1: mra_ptr=1; break;							// Reset MR Pointer to MR1.
		case 11: mra_ptr = 0; break;						// Set MR pointer to MR0.
		case  2: rxa.enabled=no; rxa.irp=rxa.iwp; break;	// Reset Receiver. Receiver is disabled and FIFO is flushed.
		case  3: txa.enabled=no; txa.iwp=txa.irp; break;	// Reset Transmitter. Transmitter is disabled and FIFO is flushed.
		case  4: break; // sra &= ~(mSR_RX_break|mSR_RX_overrun|mSR_RX_parity); break; // Clear break, parity, and over-run error bits in the status register.
		case  5: isr &= ~mIMR_RXA_break; break;				// Clears break detect change bit in the interrupt status register
		case  6: // Start Break. Forces the transmitter output to go low and stay low.
				 // If transmitter is active, all the characters in the FIFO are transmitted before break signal is sent.
				 // Transmitter must to be enabled for this command to work.
				 break;
		case  7: break;	// Stop Break.
		case  8: break;	// Set -RTS output to low (assertion).
		case  9: break;	// Reset -RTS output to high (negation).
		case 10: // Set Timeout Mode On. The receiver in this channel will restart the C/T as each receive character is transferred
				 // from the shift register to the receive FIFO. The C/T is placed in the counter mode,
				 // the START/STOP counter commands are disabled, the counter is stopped, and the Counter Ready Bit, ISR Bit-3 is reset.
				 break;
		case 12: // Disable Timeout Mode. This command returns control of the C/T to the regular Start/ Stop counter commands.
				 // It does not stop the counter or clear any pending interrupts.
				 // After disabling the timeout mode, a “Stop Counter” command should be issued to force a reset of the ISR Bit-3.
				 break;
		case 14: // Enable Power Down Mode. In this mode, the DUART oscillator is stopped and all functions requiring this clock
				 // are suspended. The execution of commands other than disable power down mode (1111) requires a XTAL1.
				 // While in the power down mode, do not issue any commands to the CRA or CRB except the disable power down mode command.
				 // The contents of all registers will be saved while in this mode.
				 // It is recommended that the transmitter and receiver be disabled prior to placing the DUART into power down mode.
				 // This command is in CRA only.
				 break;
		case 15: // Disable Power Down Mode. This command restarts the oscillator. After invoking this command,
				 // wait for the oscillator to start up before writing further commands to the CR A/B.
				 // For maximum power reduction all input pins should be at GND or VCC. This command is in CRA only.
				 break;
		}
		break;

	case CRB:						// Command Register B
		if(n&1) this->rxb.enabled = true; 	// enable rx
		if(n&2) this->rxb.enabled = false;	// disable rx
		if(n&4) this->txb.enabled = true;	// enable tx
		if(n&8) this->txb.enabled = false;	// disable tx
		switch((n>>4)&15)
		{
		case 14: break;										// not present in channel B
		case 15: break;										// not present in channel B
		case  0: break;										// no command
		case 13: break;										// not used
		case  1: mrb_ptr=1; break;							// Reset MR Pointer to MR1.
		case 11: mrb_ptr = 0; break;						// Set MR pointer to MR0.
		case  2: rxb.enabled=no; rxb.irp=rxb.iwp; break;	// Reset Receiver. Receiver is disabled and FIFO is flushed.
		case  3: txb.enabled=no; txb.iwp=txb.irp; break;	// Reset Transmitter. Transmitter is disabled and FIFO is flushed.
		case  4: break; // srb &= ~(mSR_RX_break|mSR_RX_overrun|mSR_RX_parity); break; // Clear break, parity, and over-run error bits in the status register.
		case  5: isr &= ~mIMR_RXB_break; break;				// Clears break detect change bit in the interrupt status register
		case  6: // Start Break. Forces the transmitter output to go low and stay low.
				 // If transmitter is active, all the characters in the FIFO are transmitted before break signal is sent.
				 // Transmitter must to be enabled for this command to work.
				 break;
		case  7: break;	// Stop Break.
		case  8: break;	// Set -RTS output to low (assertion).
		case  9: break;	// Reset -RTS output to high (negation).
		case 10: // Set Timeout Mode On. The receiver in this channel will restart the C/T as each receive character is transferred
				 // from the shift register to the receive FIFO. The C/T is placed in the counter mode,
				 // the START/STOP counter commands are disabled, the counter is stopped, and the Counter Ready Bit, ISR Bit-3 is reset.
				 break;
		case 12: // Disable Timeout Mode. This command returns control of the C/T to the regular Start/ Stop counter commands.
				 // It does not stop the counter or clear any pending interrupts.
				 // After disabling the timeout mode, a “Stop Counter” command should be issued to force a reset of the ISR Bit-3.
				 break;
		}
		break;
	}
}


// -------------------------------------------------------
//			INPUT from UART
// -------------------------------------------------------


uint16 Sio::rd_data(uint16 addr)
{
	uint8 c;

	switch(addr&0x0f)
	{
	case IPR:			// Input Port Register
		log(" in(IPR)=%i",0);
		return 0;

	case IPCR:	 		// Input Port Change Register
		log(" in(IPCR)=%i",0);
		return 0;

	case MR0A:			// Mode Registers A
	 	return mra0;

	case MR0B:			// Mode Registers B
		return mrb0;

	case CUR:	// Counter/Timer Upper Register
		return max(0,(int)((ct_when-now()) / ct_period)) / 256;

	case CLR:	// Counter/Timer Lower Register
		return max(0,(int)((ct_when-now()) / ct_period)) % 256;

	case GPR:	// General Purpose Register
		return gpr;

	case RXA:	// Receive Buffer A
		lock();
			c = rxa.ird();
			sra &= ~mSR_RX_full;
			if(rxa.iempty()) { reset_bits_in_isr(mIMR_RXA_ready); sra &= ~mSR_RX_ready; }
		unlock();
		return c;

	case RXB:   // Receive Buffer B
		lock();
			c = rxb.ird();
			srb &= ~mSR_RX_full;
			if(rxb.iempty()) { reset_bits_in_isr(mIMR_RXB_ready); srb &= ~mSR_RX_ready; }
		unlock();
		return c;

	case SRA:	// Status Register A
		return sra;

	case SRB:	// Status Register B
		return srb;

	case ISR:	// Interrupt Status Register
		return isr;

	case STCR:	// Start C/T Command
		pthread_create(&(pthread_t&)ct_tid,NULL,::fu_ct,this);
		return 0;

	case SPCR:	// Stop C/T Command
		// note: in timer mode the stop C/T command only resets interrupt and does not stop the C/T
		// ct_tid = 0;		// -> terminate
		lock();
			reset_bits_in_isr(mIMR_CT_ready);
		unlock();
		return 0;
	}

	return floating_k1_bus;
}


// -------------------------------------------------------
//			THREADS
// -------------------------------------------------------


/*	thread:
 *	transfer bytes at sio speed from external input buffer to the sio's receive buffer
 *	xbu[] = external input buffer: filled by application
 *	ibu[] = sio's receive buffer: read by K1-CPU
 */
void Sio::fu_rxa()
{
	double when = now();

	for(;;)
	{
		while(rxa.hsk && rxa.enabled && rxa.xavail())
		{
			lock();
				rxa.iwr(rxa.xrd());
				sra|= mSR_RX_ready;						// set SRA.rx_ready
				if(rxa.ifull()) sra |= mSR_RX_full;		// set SRA.rx_full
				set_bits_in_isr(mIMR_RXA_ready);		// raise ISR.rxa_ready
			unlock();

			waitUntil(when+=rxa.tpc);
		}
		waitUntil(when+=10*rxa.tpc);
	}
}

void Sio::fu_rxb()
{
	double when = now();

	for(;;)
	{
		while(rxb.hsk && rxb.enabled && rxb.xavail())
		{
			lock();
				rxb.iwr(rxb.xrd());
				srb|= mSR_RX_ready;						// set SRB.rx_ready
				if(rxb.ifull()) srb |= mSR_RX_full;		// set SRB.rx_full
				set_bits_in_isr(mIMR_RXB_ready);		// raise ISR.rxa_ready
			unlock();

			waitUntil(when+=rxb.tpc);
		}
		waitUntil(when+=10*rxb.tpc);
	}
}


/*	thread:
 *	transfer bytes at sio speed from sio's transmit buffer to the external output buffer
 *	xbu[] = external output buffer: read by application
 *	ibu[] = sio's transmit buffer: filled by K1-CPU
 */
void Sio::fu_txa()
{
	double when = now();

	for(;;)
	{
		while(txa.hsk && txa.enabled && txa.iavail())
		{
			lock();
				txa.xwr(txa.ird());
				sra|= mSR_TX_ready;						// set SRA.tx_ready
				if(txa.iempty()) sra |= mSR_TX_empty;	// set SRA.tx_empty
				set_bits_in_isr(mIMR_TXA_ready);		// raise ISR.txa_ready
			unlock();

			waitUntil(when+=txa.tpc);
		}
		waitUntil(when+=10*txa.tpc);
	}
}

void Sio::fu_txb()
{
	double when = now();

	for(;;)
	{
		while(txb.hsk && txb.enabled && txb.iavail())
		{
			lock();
				txb.xwr(txb.ird());
				srb|= mSR_TX_ready;						// set SRB.tx_ready
				if(txb.iempty()) srb |= mSR_TX_empty;	// set SRB.tx_empty
				set_bits_in_isr(mIMR_TXB_ready);		// raise ISR.txb_ready
			unlock();

			waitUntil(when+=txb.tpc);
		}
		waitUntil(when+=10*txb.tpc);
	}
}


/*	thread:
 *	counter / timer
 */
void Sio::fu_ct()
{
	ct_when = now();

	while(pthread_equal(ct_tid,pthread_self()))
	{
		lock();
			set_bits_in_isr(mIMR_CT_ready);
			ct_when += ct_period;
		unlock();

		waitUntil( ct_when );
	}
}

















