/*
 * bcm2835 mini uart (UART1)
 */

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

#define AUXREGS		(VIRTIO+0x215000)
#define	OkLed		16
#define	TxPin		14
#define	RxPin		15

/* AUX regs */
enum {
	Irq	= 0x00>>2,
		UartIrq	= 1<<0,
	Enables	= 0x04>>2,
		UartEn	= 1<<0,
	MuIo	= 0x40>>2,
	MuIer	= 0x44>>2,
		RxIen	= 1<<0,
		TxIen	= 1<<1,
	MuIir	= 0x48>>2,
	MuLcr	= 0x4c>>2,
		Bitsmask= 3<<0,
		Bits7	= 2<<0,
		Bits8	= 3<<0,
	MuMcr	= 0x50>>2,
		RtsN	= 1<<1,
	MuLsr	= 0x54>>2,
		TxDone	= 1<<6,
		TxRdy	= 1<<5,
		RxRdy	= 1<<0,
	MuCntl	= 0x60>>2,
		CtsFlow	= 1<<3,
		TxEn	= 1<<1,
		RxEn	= 1<<0,
	MuBaud	= 0x68>>2,
};

extern PhysUart miniphysuart;

static Uart miniuart = {
	.regs	= (u32int*)AUXREGS,
	.name	= "uart0",
	.freq	= 250000000,
	.baud	= 115200,
	.phys	= &miniphysuart,
};

static int baud(Uart*, int);

static void
interrupt(Ureg*, void *arg)
{
	Uart *uart;
	u32int *ap;

	uart = arg;
	ap = (u32int*)uart->regs;

	coherence();
	if(0 && (ap[Irq] & UartIrq) == 0)
		return;
	if(ap[MuLsr] & TxRdy)
		uartkick(uart);
	if(ap[MuLsr] & RxRdy){
		if(uart->console){
			if(uart->opens == 1)
				uart->putc = kbdcr2nl;
			else
				uart->putc = nil;
		}
		do{
			uartrecv(uart, ap[MuIo] & 0xFF);
		}while(ap[MuLsr] & RxRdy);
	}
	coherence();
}

static Uart*
pnp(void)
{
	Uart *uart;

	uart = &miniuart;
	if(uart->console == 0)
		kbdq = qopen(8*1024, 0, nil, nil);
	return uart;
}

static void
enable(Uart *uart, int ie)
{
	u32int *ap;

	ap = (u32int*)uart->regs;
	delay(10);
	gpiosel(TxPin, Alt5);
	gpiosel(RxPin, Alt5);
	gpiopulloff(TxPin);
	gpiopullup(RxPin);
	ap[Enables] |= UartEn;
	ap[MuIir] = 6;
	ap[MuLcr] = Bits8;
	ap[MuCntl] = TxEn|RxEn;
	baud(uart, uart->baud);
	if(ie){
		intrenable(IRQaux, interrupt, uart, 0, "uart");
		ap[MuIer] = RxIen|TxIen;
	}else
		ap[MuIer] = 0;
}

static void
disable(Uart *uart)
{
	u32int *ap;

	ap = (u32int*)uart->regs;
	ap[MuCntl] = 0;
	ap[MuIer] = 0;
}

static void
kick(Uart *uart)
{
	u32int *ap;

	ap = (u32int*)uart->regs;
	if(uart->blocked)
		return;
	coherence();
	while(ap[MuLsr] & TxRdy){
		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
			break;
		ap[MuIo] = *(uart->op++);
	}
	if(ap[MuLsr] & TxDone)
		ap[MuIer] &= ~TxIen;
	else
		ap[MuIer] |= TxIen;
	coherence();
}

/* TODO */
static void
dobreak(Uart *uart, int ms)
{
	USED(uart, ms);
}

static int
baud(Uart *uart, int n)
{
	u32int *ap;

	ap = (u32int*)uart->regs;
	if(uart->freq == 0 || n <= 0)
		return -1;
	ap[MuBaud] = (uart->freq + 4*n - 1) / (8 * n) - 1;
	uart->baud = n;
	return 0;
}

static int
bits(Uart *uart, int n)
{
	u32int *ap;
	int set;

	ap = (u32int*)uart->regs;
	switch(n){
	case 7:
		set = Bits7;
		break;
	case 8:
		set = Bits8;
		break;
	default:
		return -1;
	}
	ap[MuLcr] = (ap[MuLcr] & ~Bitsmask) | set;
	uart->bits = n;
	return 0;
}

static int
stop(Uart *uart, int n)
{
	if(n != 1)
		return -1;
	uart->stop = n;
	return 0;
}

static int
parity(Uart *uart, int n)
{
	if(n != 'n')
		return -1;
	uart->parity = n;
	return 0;
}

/*
 * cts/rts flow control
 *   need to bring signals to gpio pins before enabling this
 */

static void
modemctl(Uart *uart, int on)
{
	u32int *ap;

	ap = (u32int*)uart->regs;
	if(on)
		ap[MuCntl] |= CtsFlow;
	else
		ap[MuCntl] &= ~CtsFlow;
	uart->modem = on;
}

static void
rts(Uart *uart, int on)
{
	u32int *ap;

	ap = (u32int*)uart->regs;
	if(on)
		ap[MuMcr] &= ~RtsN;
	else
		ap[MuMcr] |= RtsN;
}

static long
status(Uart *uart, void *buf, long n, long offset)
{
	char *p;

	p = malloc(READSTR);
	if(p == nil)
		error(Enomem);
	snprint(p, READSTR,
		"b%d\n"
		"dev(%d) type(%d) framing(%d) overruns(%d) "
		"berr(%d) serr(%d)\n",

		uart->baud,
		uart->dev,
		uart->type,
		uart->ferr,
		uart->oerr,
		uart->berr,
		uart->serr
	);
	n = readstr(offset, buf, n, p);
	free(p);

	return n;
}

static void
donothing(Uart*, int)
{
}

void
putc(Uart*, int c)
{
	u32int *ap;

	ap = (u32int*)AUXREGS;
	while((ap[MuLsr] & TxRdy) == 0)
		;
	ap[MuIo] = c;
	while((ap[MuLsr] & TxRdy) == 0)
		;
}

int
getc(Uart*)
{
	u32int *ap;

	ap = (u32int*)AUXREGS;
	while((ap[MuLsr] & RxRdy) == 0)
		;
	return ap[MuIo] & 0xFF;
}

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

	if((p = getconf("console")) == nil)
		return;
	n = strtoul(p, &cmd, 0);
	if(p == cmd)
		return;
	switch(n){
	default:
		return;
	case 0:
		uart = &miniuart;
		break;
	}

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

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

PhysUart miniphysuart = {
	.name		= "miniuart",
	.pnp		= pnp,
	.enable		= enable,
	.disable	= disable,
	.kick		= kick,
	.dobreak	= dobreak,
	.baud		= baud,
	.bits		= bits,
	.stop		= stop,
	.parity		= parity,
	.modemctl	= donothing,
	.rts		= rts,
	.dtr		= donothing,
	.status		= status,
	.fifo		= donothing,
	.getc		= getc,
	.putc		= putc,
};

void
okay(int on)
{
	static int first;
	static int okled, polarity;
	char *p;

	if(!first++){
		p = getconf("bcm2709.disk_led_gpio");
		if(p == nil)
			p = getconf("bcm2708.disk_led_gpio");
		if(p != nil)
			okled = strtol(p, 0, 0);
		else
			okled = 'v';
		p = getconf("bcm2709.disk_led_active_low");
		if(p == nil)
			p = getconf("bcm2708.disk_led_active_low");
		polarity = (p == nil || *p == '1');
		if(okled != 'v')
			gpiosel(okled, Output);
	}
	if(okled == 'v')
		vgpset(0, on);
	else if(okled != 0)
		gpioout(okled, on^polarity);
}
