#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "msaturn.h"

enum{
	UartAoffs = Saturn + 0x0a00,
	UartBoffs = Saturn + 0x0b00,
	Nuart = 2,

	Baudfreq = 14745600 / 16,
	Lcr_div = RBIT(1, uchar),
	Lcr_peven = RBIT(3, uchar),
	Lcr_pen = RBIT(4, uchar),
	Lcr_stop = RBIT(5, uchar),
	Lcr_wrdlenmask = RBIT(6, uchar) | RBIT(7, uchar),
	Lcr_wrdlenshift = 0,
	Lsr_tbre = RBIT(2, uchar),	
	Fcr_txreset = RBIT(5, uchar),
	Fcr_rxreset = RBIT(6, uchar),
	Iir_txempty = RBIT(5, uchar),
	Iir_rxfull = RBIT(6, uchar),
	Iir_rxerr = RBIT(7, uchar),
	Ier_rxerr = RBIT(5, uchar),
	Ier_txempty = RBIT(6, uchar),
	Ier_rxfull = RBIT(7, uchar),
	Lsr_rxavail = RBIT(7, uchar),
	Txsize = 16,
	Rxsize = 16,
};

typedef struct Saturnuart Saturnuart;
struct Saturnuart {
	uchar	rxb;
#define txb	rxb
#define dll	rxb
	uchar	ier;			// Interrupt enable, divisor latch
#define dlm	ier
	uchar	iir;			// Interrupt identification, fifo control
#define fcr	iir	
	uchar	lcr;			// Line control register
	uchar	f1;		
	uchar	lsr;			// Line status register
	ushort	f2;
};

typedef struct UartData UartData;
struct UartData {
	int			suno;	/* saturn uart number: 0 or 1 */
	Saturnuart	*su;
	char			*rxbuf;
	char			*txbuf;
	int			initialized;
	int			enabled;
} uartdata[Nuart];

extern PhysUart saturnphysuart;

Uart suart[Nuart] = {
	{
		.name = "SaturnUart1",
		.baud = 19200,
		.bits = 8,
		.stop = 1,
		.parity = 'n',
		.phys = &saturnphysuart,
		.special = 0,
	},
	{
		.name = "SaturnUart2",
		.baud = 115200,
		.bits = 8,
		.stop = 1,
		.parity = 'n',
		.phys = &saturnphysuart,
		.special = 0,
	},
};

static void suinterrupt(Ureg*, void*);

static Uart*
supnp(void)
{
	int i;

	for (i = 0; i < nelem(suart)-1; i++)
		suart[i].next = &suart[i + 1];
	suart[nelem(suart)-1].next=nil;
	return suart;
}

static void
suinit(Uart*uart)
{
	UartData *ud;
	Saturnuart *su;

	ud = uart->regs;
	su = ud->su;
	su->fcr=Fcr_txreset|Fcr_rxreset;
	ud->initialized=1;
}

static void
suenable(Uart*uart, int ie)
{
	Saturnuart *su;
	UartData *ud;
	int nr;

	nr = uart - suart;
	if (nr < 0 || nr > Nuart)
		panic("No uart %d", nr);
	ud = uartdata + nr;
	ud->suno = nr;
	su=ud->su = (Saturnuart*)((nr == 0)? UartAoffs: UartBoffs);
	uart->regs = ud;

	if(ud->initialized==0)
		suinit(uart);

	if(!ud->enabled && ie){
		intrenable(Vecuart0+nr , suinterrupt, uart, uart->name);
		su->ier=Ier_txempty|Ier_rxfull;
		ud->enabled=1;
	}
}


static char*
sustatus(Uart* uart, char *p, char *e)
{
	Saturnuart *su;

	su = ((UartData*)uart->regs)->su;
	return seprint(p, e, "b%d c%d e%d l%d m0 p%c s%d i1\n"
		"dev(%d) type(%d) framing(%d) overruns(%d)\n",

		uart->baud,
		uart->hup_dcd, 
		uart->hup_dsr,
		Txsize,
		(su->lcr & Lcr_pen)? ((su->lcr & Lcr_peven) ? 'e': 'o'): 'n',
		(su->lcr & Lcr_stop)? 2: 1,

		uart->dev,
		uart->type,
		uart->ferr,
		uart->oerr);
}

static void
sufifo(Uart*, int)
{}

static void
sudtr(Uart*, int)
{}

static void
surts(Uart*, int)
{}

static void
sumodemctl(Uart*, int)
{}

static int
suparity(Uart*uart, int parity)
{
	int lcr;
	Saturnuart *su;

	su = ((UartData*)uart->regs)->su;

	lcr = su->lcr & ~(Lcr_pen|Lcr_peven);

	switch(parity){
	case 'e':
		lcr |= (Lcr_pen|Lcr_peven);
		break;
	case 'o':
		lcr |= Lcr_pen;
		break;
	case 'n':
	default:
		break;
	}

	su->lcr = lcr;
	uart->parity = parity;

	return 0;
}

static int
sustop(Uart* uart, int stop)
{
	int lcr;
	Saturnuart *su;

	su = ((UartData*)uart->regs)->su;
	lcr = su->lcr & ~Lcr_stop;

	switch(stop){
	case 1:
		break;
	case 2:
		lcr |= Lcr_stop;
		break;
	default:
		return -1;
	}

	/* Set new value and reenable if device was previously enabled */
	su->lcr = lcr;
	uart->stop = stop;

	return 0;
}

static int
subits(Uart*uart, int n)
{	
	Saturnuart *su;
	uchar lcr;

	su = ((UartData*)uart->regs)->su;
	if(n<5||n>8)
		return -1;

	lcr = su->lcr & ~Lcr_wrdlenmask;
	lcr |= (n-5) << Lcr_wrdlenshift;
	su->lcr = lcr;
	return 0;
}

static int
subaud(Uart* uart, int baud)
{
	ushort v;
	Saturnuart *su;

	if (uart->enabled){
		su = ((UartData*)uart->regs)->su;
	
		if(baud <= 0)
			return -1;

		v = Baudfreq / baud;
		su->lcr |= Lcr_div;
		su->dll = v;
		su->dlm = v >> 8;
		su->lcr &= ~Lcr_div;
	}
	uart->baud = baud;

	return 0;
}

static void
subreak(Uart*, int)
{}

static void
sukick(Uart *uart)
{
	Saturnuart *su;
	int i;

	su = ((UartData*)uart->regs)->su;
	if((su->iir & Iir_txempty) == 0)
		return;

	for(i = 0; i < Txsize; i++){
		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
			break;
		su->txb = *(uart->op++);
		su->ier |= Ier_txempty;
		break;
	}
}

static void
suputc(Uart *uart, int c)
{
	Saturnuart *su;

	su = ((UartData*)uart->regs)->su;
	while((su->lsr&Lsr_tbre) == 0)
		;

	su->txb=c;
	while((su->lsr&Lsr_tbre) == 0)
			;
}

static int
getchars(Uart *uart, uchar *cbuf)
{
	int nc;
	UartData *ud;
	Saturnuart *su;

	ud = uart->regs;
	su = ud->su;

	while((su->lsr&Lsr_rxavail) == 0)
		;

	*cbuf++ = su->rxb;
	nc = 1;
	while(su->lsr&Lsr_rxavail){
		*cbuf++ = su->rxb;
		nc++;
	}
	return nc;
}

static int
sugetc(Uart *uart)
{
	static uchar buf[128], *p;
	static int cnt;
	char	c;

	if (cnt <= 0) {
		cnt = getchars(uart, buf);
		p = buf;
	}
	c = *p++;
	cnt--;
	return c;
}

static void
suinterrupt(Ureg*, void*u)
{
	Saturnuart *su;
	Uart *uart;
	uchar iir;

	uart = u;
	if (uart == nil)
		panic("uart is nil");
	su = ((UartData*)uart->regs)->su;
	iir = su->iir;
	if(iir&Iir_rxfull)
		while(su->lsr&Lsr_rxavail)
			uartrecv(uart, su->rxb);
	if(iir & Iir_txempty){
		su->ier&=~Ier_txempty;
		uartkick(uart);
	}
	if (iir & Iir_rxerr)
		uart->oerr++;
	intack();
}

static void
sudisable(Uart* uart)
{
	Saturnuart *su;

	su = ((UartData*)uart->regs)->su;
	su->ier&=~(Ier_txempty|Ier_rxfull);
}

PhysUart saturnphysuart = {
	.name		= "su",
	.pnp			= supnp,
	.enable		= suenable,
	.disable		= sudisable,
	.kick			= sukick,
	.dobreak		= subreak,
	.baud		= subaud,
	.bits			= subits,
	.stop			= sustop,
	.parity		= suparity,
	.modemctl	= sumodemctl,
	.rts			= surts,
	.dtr			= sudtr,
	.status		= sustatus,
	.fifo			= sufifo,
	.getc			= sugetc,
	.putc			= suputc,
};

void
console(void)
{
	Uart *uart;
	int n;
	char *cmd, *p;

	if((p = getconf("console")) == nil)
		return;
	n = strtoul(p, &cmd, 0);
	if(p == cmd)
		return;
	if(n < 0 || n >= nelem(suart))
		return;

	uart = suart + n;

/*	uartctl(uart, "b115200 l8 pn s1"); */
	if(*cmd != '\0')
		uartctl(uart, cmd);
	(*uart->phys->enable)(uart, 0);

	consuart = uart;
	uart->console = 1;
} 

Saturnuart*uart = (Saturnuart*)UartAoffs;

void
dbgputc(int c)
{
	while((uart->lsr&Lsr_tbre) == 0)
		;

	uart->txb=c;
	while((uart->lsr&Lsr_tbre) == 0)
			;
}

void
dbgputs(char*s)
{
	while(*s)
		dbgputc(*s++);
}

void
dbgputx(ulong x)
{
	int i;
	char c;

	for(i=0; i < sizeof(ulong) * 2; i++){
		c = ((x >> (28 - i * 4))) & 0xf;
		if(c >= 0 && c <= 9)
			c += '0';
		else
			c += 'a' - 10;

		while((uart->lsr&Lsr_tbre) == 0)
			;

		uart->txb=c;
	}
	while((uart->lsr&Lsr_tbre) == 0)
			;

	uart->txb='\n';
	while((uart->lsr&Lsr_tbre) == 0)
			;
}
