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

	This file is free software

 	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.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:

	• Redistributions of source code must retain the above copyright notice,
	  this list of conditions and the following disclaimer.
	• Redistributions in binary form must reproduce the above copyright notice,
	  this list of conditions and the following disclaimer in the documentation
	  and/or other materials provided with the distribution.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
	OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
	WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
	OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



	Dual UART 88C192 Driver for K1-Bus Board
	----------------------------------------

	This is the driver for a dual-sio board for the K1-Bus.
	For K1-Bus see: https://k1.spdns.de/Develop/Hardware/K1-Computer/IO-Boards/SIO/
*/

#include "sio.h"
#include "sio-defines.h"


// ==============================================
// local data:
// ==============================================

uint16[16] uart_values_for_baudrate = (uint16[])
{
/* UART register value for baudrate*2400:

	%0000000x		1  *2400 = 2400
	%00000010		2  *2400 = 4800
	%0000010x		4  *2400 = 9600
	%000010xx		8  *2400 = 19k2
	%00010xxx		16 *2400 = 38k4
	%0010xxxx		32 *2400 = 76k8
	%010xxxxx		Illegal, returns 9600
	%10xxxxxx		Illegal, returns 9600

	%00000011		3  *2400 = 7200
	%0000011x		6  *2400 = 14k4
	%000011xx		12 *2400 = 28k8
	%00011xxx		24 *2400 = 57k6
	%0011xxxx		48 *2400 = 115k2
	%011xxxxx		96 *2400 = 230k4
	%11xxxxxx		192*2400 = 460k8
*/
#define V(MR0,CSR)	(MR0_value+MR0)*256+(CSR)*17			// N.hi = MR0 register value
															// N.lo = CSR register value

	V(0,0b0110), V(0,0b1000), V(0,0b1001), V(0,0b1011),		// 2400, 4800, 9600, 19k2
	V(4,0b0011), V(0,0b1100), V(0,0b1001), V(0,0b1001),		// 38k4, 76k8, 9600, 9600
	V(0,0),      V(1,0b0101), V(1,0b0110), V(1,0b1000),		// 0,    7200, 14k4, 28k8
	V(1,0b1001), V(1,0b1011), V(4,0b0110), V(1,0b1100)		// 57k6,115k2,230k4,460k8
};


// ==============================================
// global data:
// ==============================================


uint8 imr_value  = IMR_value;	// current value of interrupt mask register IMR
uint8 opcr_value = OPCR_value;	// current value of output port control register OPCR


#if 0
FD_Handlers sio_handlers = (FD_Handlers)
{
	getctl 	  = SerialDevice.getctl
	setctl 	  = SerialDevice.setctl
	avail_in  = SerialDevice.avail_in
	avail_out = SerialDevice.avail_out
	getc	  = SerialDevice.getc
	putc	  = SerialDevice.putc
	read	  = SerialDevice.read
	write	  = SerialDevice.write
	getfpos	  = SerialDevice.getfpos
	setfpos   = SerialDevice.setfpos
	read_block  = SerialDevice.read_block
	write_block = SerialDevice.write_block
	kill		= void FD.kill(FD¢)
}
#else
//helper: shorter source & not allocated in ram:
FD_Handlers new_sio_handlers() = asm
{
	assert FD_Handlers.getctl  == 0
	assert FD_Handlers.setctl  == 2
	assert FD_Handlers.avail_in == 4
	assert FD_Handlers.avail_out == 6
	assert FD_Handlers.getc == 8
	assert FD_Handlers.putc == 10
	assert FD_Handlers.read == 12
	assert FD_Handlers.write == 14
	assert FD_Handlers.read_block == 16
	assert FD_Handlers.write_block == 18
	assert FD_Handlers.kill == 20
	assert FD_Handlers.getfpos == 22
	assert FD_Handlers.setfpos == 24

	p_enter
	dw	ISTR
	dw	26
	dw	SerialDevice.getctl__12SerialDeviceC_5uint8_6uint16
	dw 	SerialDevice.setctl__12SerialDeviceC_5uint8_6uint16_
	dw 	SerialDevice.avail_in__12SerialDeviceC_6uint16
	dw 	SerialDevice.avail_out__12SerialDeviceC_6uint16
	dw 	SerialDevice.getc__12SerialDeviceC_8ucs1char
	dw 	SerialDevice.putc__12SerialDeviceC_8ucs1char_
	dw 	SerialDevice.read__12SerialDeviceC_5uint8AEC_6uint16_6uint16_4bool
	dw 	SerialDevice.write__12SerialDeviceC_5uint8AEC_6uint16_6uint16_4bool
	dw	no_read_block__2FDC_6uint32_5uint8AEC_4bool
	dw	no_read_block__2FDC_6uint32_5uint8AEC_4bool		; write_block
	dw	FD.kill__2FDC_
	dw 	no_getfpos__2FDC_6uint32
	dw 	no_setfpos__2FDC_6uint32_4bool
	dw	RETURN
}

FD_Handlers sio_handlers = new_sio_handlers();
#endif



// ==============================================
// procedures:
// ==============================================


// ___________________ c'tor, d'tor _______________________

/*	create a serial device struct:
	the UART is already initialized by assembler boot code
	new() must only be used to init sio1A and sio1B
*/
SerialDevice new (uint8 ch, str¢ name)
{
	SerialDevice this = alloc SerialDevice();
	FD.init(this, t_ser, name, sio_handlers);

	this.device   := mSIO_SELECT;	// select mask
	this.baudrate := 9600/2400;		// baudrate / 2400
	this.channel  := ch;
	uint ch3 = ch << 3;
	this.sreg     := ch3 + SRA + k1_rd_data;
	this.rxreg    := ch3 + RXA + k1_rd_data;
	this.txreg    := ch3 + TXA + k1_wr_data;

	this.ibu = alloc char[SerialDevice.ibusz]();
	this.obu = alloc char[SerialDevice.obusz]();

	return this;
}


scope SerialDevice
{

// ___________________ helper _______________________

/*  helper: set baudrate:
	in: interrupts disabled & device selected
	validates baudrate
	handles clocks on handshake lines
	!! does not reset the channel !!
*/
void set_baudrate( SerialDevice¢ this, uint8 baudrate )
{
	uint8 chx8 = this.channel << 3;

 // UART bytes for baudrate:
	uint8 i = msbit(baudrate);				// 0 .. 7
	bool  t = i && baudrate>>(i-1) & 1;		// table: 0=2*1200 series, 1=3*1200 series
	uint  v = uart_values_for_baudrate[t<<3+i];

 // set SioData.baudrate to actually used value:
	this.baudrate = t ? 3<<(i-1) : i<6 ? 1<<i : baud_9600;

 // check for clocks on hsk lines:
	uint8 csr_mask = 0;
	if(this.clk_handshake)
	{
		if(	this.clk_handshake&1 ) csr_mask |= 0x0F;	// TX clock empfangen?
		if(	this.clk_handshake&2 ) csr_mask |= 0xF0;	// RX clock empfangen?
	}

 // set UART registers:
	out(chx8+CRA,  0xB0);					// point MRx to MR0
	out(chx8+MR0A, v.hi);					// WDT, BdRateTable, RX und TX buffer trigger level
	out(chx8+CSRA, v.lo|csr_mask);
}


/* 	helper: set software handshake on or off:
	if no change: nothing is done!
	if state changes: xon/xoff state for sender and transmitter are cleared
	!! does not reset the channel !!
*/
void set_sw_handshake( SerialDevice¢ this, bool f )
{
	if( this.sw_handshake & 1 != f )
	{
		this.sw_handshake 	= f;
	//	this.xoff_received	= false;	bit implicitly cleared by above assignment
	//	this.xoff_sent 		= false;	bit implicitly cleared by above assignment
	}
}


/*	helper: set hardware handshake on or off:
	in: interrupts disabled & device selected
	!! does not reset the channel !!
*/
void set_hw_handshake( SerialDevice¢ this, bool f )
{
	uint chx8 = this.channel << 3;

	if( this.hw_handshake != f )
	{
		// TODO: switch off clock on hsk lines
		//if(f) this.set_clk_handshake(false);

		this.hw_handshake = f;

		// set signal inverter to "not inverted":
		//	RTSx = don't care (hw controlled)
		//	CLKx = 0<asserted>	= RTSx not inverted
		//	CTLx = no change
		//	INVx = 0<asserted>	= CTSx not inverted
 //		uint8 ch = this.channel;
 //		out(SOPR,(mOPR_CLKA+mOPR_INVA)<<ch);

		out(chx8+CRA,  0x10);				// point MRx to MR1
		out(chx8+MR1A, MR1_value + f<<7);	// OP0|1: receiver RTS flow control
		out(chx8+MR2A, MR2_value + f<<4);	// IP0|1: transmitter CTS flow control
	}
}


/* 	helper: set clocked lines mode on or off:
	in: interrupts disabled & device selected
		flags.bit0: emit TX clock
		flags.bit1: emit RX clock
		flags.bit2: invert clocks
		flags.bit3: may be set
		switched on:  switch on CLK.out if TX or RX needs this
		switched off: assert (pin=low) RTS, CLK.out and HSK.INV
					  => can be used 'as is' by hardware handshake
		selects internal or external clock for TX and RX by call to set_baudrate
	!! does not reset the channel !!
*/
void set_clock_handshake( SerialDevice¢ this, uint8 flags )
{
	uint8 ch   = this.channel;
	uint8 chx2 = ch << 1;

	if( this.clk_handshake != flags )
	{
		// TODO: switch of hw hsk
		//if(f) this.set_hw_handshake(false);

		this.clk_handshake = flags;


		// TX clock senden oder empfangen?		// bit.1: TXD: emit clk.out
		if(flags&3) opcr_value |=   3<<chx2;	// bit.2: RXD: emit clk.out
		else 		opcr_value &= ~(3<<chx2);	// OP2|3: (CLK) normal port mode
		out(OPCR,opcr_value);					// output port config register

		out(SOPR, (1<<0+1<<2+1<<6)<<ch );		// assert (pin=low) RTS, CLK.out and HSK.INV

		if(flags&4) out(ROPR,(0x41<<ch));		// RTS und receiver hsk inverter := 1  --> invert
												// RTS und receiver hsk inverter := 0  --> !invert
		this.set_baudrate(this.baudrate);
	}
}


/* 	helper: reset receiver and/or transmitter
	in: interrupts disabled & device selected
	does not change baudrate or mode
*/
void reset( SerialDevice¢ this, bool tx, bool rx )
{
	uint8 CR = CRA + this.channel << 3;

	if(rx)
	{
		out(CR,	0x20);		// reset receiver
		this.sw_handshake &= ~mXOFF_SENT;
		this.ibuwi = this.iburi;
	}
	if(tx)
	{
		out(CR,	0x30);		// reset transmitter
		this.sw_handshake &= ~mXOFF_RECEIVED
		this.oburi = this.obuwi;
	}
	out(CR,	0x40);			// reset error status: Clears channel, break, parity,
							// and over-run error bits in the status register.

 // re-enable receiver and transmitter:
	out(CR,	1 << 0			// enable receiver
		  + 1 << 2);		// enable transmitter
}


// ___________________ setctl, getctl _______________________

void setctl( SerialDevice¢ channel, uint8 fu, uint value )
{
	if(fu==c_flushout)
	{
		uint8 n=50;
		do{ while channel.oburi!=channel.obuwi && n--; wait(); }
	}

	select(mSIO_SELECT);
	switch(fu)
	{
	case c_clk_hsk:	channel.set_clock_handshake(value);		break;
	case c_reset:	channel.reset(yes,yes);					break;
	case c_speed:	channel.set_baudrate(value/24);			break;
	case c_hwhsk:	channel.set_hw_handshake(value!=0);		break;
	case c_swhsk:	channel.set_sw_handshake(value!=0);		break;
	case c_flushin:	channel.reset(no,yes);					break;
	case c_flushout:channel.reset(yes,no);					break;
	}
	ei();
}

uint getctl( SerialDevice¢ channel, uint8 fu )
{
	switch(fu)
	{
	case c_speed:	return channel.baudrate*24;		// baudrate/100
	case c_hwhsk:	return channel.hw_handshake;
	case c_swhsk:	return channel.sw_handshake;
	case c_clk_hsk:	return channel.clk_handshake;
	case c_availin:	return channel.avail_in();
	case c_availout:return channel.avail_out();
	default:		return 0;						// don't know
	}
}


// ___________________ avail_in / avail_out _______________________

uint avail_in(SerialDevice¢ channel)  = asm
{
	assert SerialDevice.iburi == SerialDevice.ibuwi+1
	assert SerialDevice.oburi == SerialDevice.obuwi+1

	deref_handle_de			; de -> channel data

	ld		hl,SerialDevice.ibuwi
	add		hl,de			; hl -> ibuwi
	ld		a,(hl)			; a = ibuwi
	inc 	hl				; hl -> iburi
	sub		a,(hl)			; a = ibuwi-iburi
	jp		ld_de_a_next
}

uint avail_out(SerialDevice¢ channel) = asm
{
	deref_handle_de			; de -> channel data

	ld		a,SerialDevice.obusz	; a = const obusz
	ld		hl,SerialDevice.obuwi
	add		hl,de			; hl -> obuwi
	sub		a,(hl)			; a = obusz-obuwi
	inc 	hl				; hl -> oburi
	add		a,(hl)			; a = obusz-obuwi+iburi = obusz - (obuwi-oburi)
	jp		ld_de_a_next
}


// ___________________ serial read/write _______________________
// blocking!

char getc( SerialDevice¢ channel )
{
	// wait until byte available: blocking
	do { until channel.avail_in(); wait(); }

	// get byte. increment iburi _after_ reading the byte:
	char c = channel.ibu[channel.iburi & ibumask];
	channel.iburi++;
	return c;
}

void putc( SerialDevice¢ channel, char c )
{
	// wait until byte available:
	do { until channel.avail_out(); wait(); }

	// store byte. increment obuwi _after_ writing the byte:
	channel.obu[channel.obuwi & obumask] = c;
	channel.obuwi++;
}

void puts(SerialDevice¢ fd, str¢ s)
{
	(void) fd.write(s[…]);
}

void puts(SerialDevice¢ fd, str s)
{
	(void) fd.write(s[…]);
}


// ___________________ read/write block _______________________
// blocking!

bool read(SerialDevice¢ channel, uint8[]¢ bu, uint a, uint e)
{
	do
	{
		uint8 i = channel.iburi & ibumask;			// ibu index
		uint8 m = ibusz - i;						// bytes before wrap around
		uint8 n = min(e-a, channel.avail_in());		// limit n to available bytes

		// copy bytes to provided buffer:
		if(m < n)
		{
			memcpy( &bu[a],   &channel.ibu[i], m  );
			memcpy( &bu[a+m], &channel.ibu[0], n-m);
		}
		else memcpy( &bu[a],  &channel.ibu[i], n);

		channel.iburi += n;			// increment iburi _after_ reading the bytes
		a += n;
		while a < e;
		wait();
	}
	return no;		// no error
}

bool write(SerialDevice¢ channel, uint8[]¢ bu, uint a, uint e)
{
#if 0
	do
	{
		while a < e;
		channel.putc((char)bu[a++]);
	}
#else
	do
	{
		uint8 i = channel.obuwi & obumask;			// obu index
		uint8 m = obusz - i;						// space before wrap around
		uint8 n = min(e-a, channel.avail_out());	// limit n to available space

		// load bytes from provided buffer:
		if(m < n)
		{
			memcpy(&channel.obu[i], &bu[a],   m  );
			memcpy(&channel.obu[0], &bu[a+m], n-m);
		}
		else memcpy(&channel.obu[i], &bu[a],   n);

		channel.obuwi += n;			// increment obuwi _after_ writing the bytes
		a += n;
		while a < e;
		wait();
	}
#endif
	return no;		// no error
}



} // scope SerialDevice












