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

enum {
	RBR = 0,
	IER,
	FCR,
	LCR,
	MCR,
	LSR,
	MSR,
	SCR,
	
	IIR = FCR,
};

enum {
	LSR_THRE = 1<<5,
	LSR_DR = 1<<0,
	
	ENTXIRQ = 1<<1,
	ENRXIRQ = 1<<0
};

typedef struct Ctlr {
	Lock;
	ulong *r;
	int irq, iena;
} Ctlr;

extern PhysUart cycvphysuart;

static Ctlr vctlr[1] = {
	{
		.r = (void *) UART_BASE,
		.irq = UART0IRQ,
	}
};

static Uart vuart[1] = {
	{
		.regs = &vctlr[0],
		.name = "UART1",
		.freq = 25000000,
		.phys = &cycvphysuart,
		.console = 1,
		.baud = 115200,
	}
};

void
uartconsinit(void)
{
	consuart = vuart;
}

static Uart *
vuartpnp(void)
{
	return vuart;
}

static void
vuartkick(Uart *uart)
{
	Ctlr *ct;
	int i;

	ct = uart->regs;
	if((ct->r[LSR] & LSR_THRE) == 0)
		return;
	for(i = 0; i < 128; i++){
		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
			break;
		ct->r[RBR] = *uart->op++;
	}
}

static void
vuartintr(Ureg *, void *arg)
{
	Uart *uart;
	Ctlr *c;
	int ch, f;
	
	uart = arg;
	c = uart->regs;
	for(;;){
		f = c->r[IIR] & 15;
		switch(f){
		case 6: USED(c->r[LSR]); break;
		case 4: case 8:
			while((c->r[LSR] & LSR_DR) != 0){
				ch = c->r[RBR];
				uartrecv(uart, ch);
			}
			break;
		case 2:
			uartkick(uart);
			break;
		default:
			return;
		}
	}
}

static void
vuartenable(Uart *uart, int ie)
{
	Ctlr *c;
	
	c = uart->regs;
	ilock(c);
	while((c->r[LSR] & LSR_THRE) == 0)
		;
	c->r[LCR] = 0x03;
	c->r[FCR] = 0x1;
	c->r[IER] = 0x0;
	if(ie){
		if(!c->iena){
			intrenable(c->irq, vuartintr, uart, LEVEL, uart->name);
			c->iena = 1;
		}
		c->r[IER] = ENTXIRQ | ENRXIRQ;
	}
	iunlock(c);
}

static int
vuartgetc(Uart *uart)
{
	Ctlr *c;
	
	c = uart->regs;
	while((c->r[LSR] & LSR_DR) == 0)
		;
	return c->r[RBR];
}

static void
vuartputc(Uart *uart, int c)
{
	Ctlr *ct;
	
	ct = uart->regs;
	while((ct->r[LSR] & LSR_THRE) == 0)
		;
	ct->r[RBR] = c;
	return;
}

int
vuartbits(Uart *uart, int n)
{
	Ctlr *c;
	
	c = uart->regs;
	switch(n){
	case 5: c->r[LCR] = c->r[LCR] & ~3 | 0; return 0;
	case 6: c->r[LCR] = c->r[LCR] & ~3 | 1; return 0;
	case 7: c->r[LCR] = c->r[LCR] & ~3 | 2; return 0;
	case 8: c->r[LCR] = c->r[LCR] & ~3 | 3; return 0;
	default:
		return -1;
	}
}

int
vuartbaud(Uart *, int n)
{
	print("uart baud %d\n", n);
	return 0;
}

int
vuartparity(Uart *uart, int p)
{
	Ctlr *c;
	
	c = uart->regs;
	switch(p){
	case 'n': c->r[LCR] = c->r[LCR] & ~0x38; return 0;
	case 'o': c->r[LCR] = c->r[LCR] & ~0x38 | 0x08; return 0;
	case 'e': c->r[LCR] = c->r[LCR] & ~0x38 | 0x18; return 0;
	default:
		return -1;
	}
}

void
vuartnop(Uart *, int)
{
}

int
vuartnope(Uart *, int)
{
	return -1;
}


PhysUart cycvphysuart = {
	.pnp = vuartpnp,
	.enable = vuartenable,
	.kick = vuartkick,
	.getc = vuartgetc,
	.putc = vuartputc,
	.bits = vuartbits,
	.baud = vuartbaud,
	.parity = vuartparity,
	
	.stop = vuartnope,
	.rts = vuartnop,
	.dtr = vuartnop,
	.dobreak = vuartnop,
	.fifo = vuartnop,
	.power = vuartnop,
	.modemctl = vuartnop,
};
