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

/*
 * PowerPC 8260 SMC UART
 */

enum {
	/* SMC Mode Registers */
	Clen		= 0x7800,	/* Character length */
	Sl		= 0x0400,	/* Stop length, 0: one stop bit, 1: two */
	Pen		= 0x0200,	/* Parity enable */
	Pm		= 0x0100,	/* Parity mode, 0 is odd */
	Sm		= 0x0030,	/* SMC mode, two bits */
	SMUart	= 0x0020,	/* SMC mode, 0b10 is uart */
	Dm		= 0x000c,	/* Diagnostic mode, 00 is normal */
	Ten		= 0x0002,	/* Transmit enable, 1 is enabled */
	Ren		= 0x0001,	/* Receive enable, 1 is enabled */

	/* SMC Event/Mask Registers */
	ce_Brke	= 0x0040,	/* Break end */
	ce_Br	= 0x0020,	/* Break character received */
	ce_Bsy	= 0x0004,	/* Busy condition */
	ce_Txb	= 0x0002,	/* Tx buffer */
	ce_Rxb	= 0x0001,	/* Rx buffer */

	/* Receive/Transmit Buffer Descriptor Control bits */
	BDContin=	1<<9,
	BDIdle=		1<<8,
	BDPreamble=	1<<8,
	BDBreak=	1<<5,
	BDFrame=	1<<4,
	BDParity=	1<<3,
	BDOverrun=	1<<1,

	/* Tx and Rx buffer sizes (32 bytes) */
	Rxsize=		CACHELINESZ,
	Txsize=		CACHELINESZ,
};

extern PhysUart smcphysuart;

Uart smcuart[Nuart] = {
	{
		.name = "SMC1",
		.baud = 115200,
		.bits = 8,
		.stop = 1,
		.parity = 'n',
		.phys = &smcphysuart,
		.special = 0,
	},
/*	Only configure SMC1 for now
	{
		.name = "SMC2",
		.baud = 115200,
		.bits = 8,
		.stop = 1,
		.parity = 'n',
		.phys = &smcphysuart,
		.special = 0,
	},
*/
};

int uartinited = 0;

static void smcinterrupt(Ureg*, void*);
static void smcputc(Uart *uart, int c);

int
baudgen(int baud)
{
	int d;

	d = ((m->brghz+(baud>>1))/baud)>>4;
	if(d >= (1<<12))
		return ((d+15)>>3)|1;
	return d<<1;
}

static Uart*
smcpnp(void)
{
	int i;

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

void
smcsetup(Uart *uart)
{
	Uartsmc *p;
	SMC *smc;
	UartData *ud;

	ud = uart->regs;

	/* magic addresses */

	p = &m->immr->uartsmc[ud->smcno];
	smc = imm->smc + ud->smcno;	/* SMC1 */
	ud->smc = smc;
	ud->usmc = p;

	/* step 0: disable rx/tx */
	smc->smcmr &= ~3;

	ioplock();

	/* step 1, Using Port D */
	if (ud->smcno != 0)
		panic("Don't know how to set Port D bits");
	imm->port[SMC1PORT].ppar |= SMRXD1|SMTXD1;
	imm->port[SMC1PORT].pdir |= SMTXD1;
	imm->port[SMC1PORT].pdir &= ~SMRXD1;
	imm->port[SMC1PORT].psor &= ~(SMRXD1|SMTXD1);

	/* step 2: set up brgc1 */
	imm->brgc[ud->smcno]  = baudgen(uart->baud) | 0x10000;

	/* step 3: route clock to SMC1 */
	imm->cmxsmr &= (ud->smcno == 0) ? ~0xb0 : ~0xb;	/* clear smcx and smcxcs */

	iopunlock();

	/* step 4: assign a pointer to the SMCparameter RAM */
	m->immr->param[ud->smcno].smcbase = (ulong)p - IMMR;

	/* step 6: issue command to CP */
	if (ud->smcno == 0)
		cpmop(InitRxTx, SMC1ID, 0);
	else
		cpmop(InitRxTx, SMC2ID, 0);

	/* step 7: protocol parameters */
	p->rfcr = 0x30;
	p->tfcr = 0x30;
}

void
smcinit(Uart *uart)
{
	Uartsmc *p;
	SMC *smc;
	UartData *ud;
	ulong lcr;
	int bits;

	ud = uart->regs;

	if (ud->initialized)
		return;

	smcsetup(uart);	/* Steps 1 through 4, PPC-dependent */
	p = ud->usmc;
	smc = ud->smc;

	/* step 5: set up buffer descriptors */
	/* setup my uart structure */
	if (ud->rxb == nil)
		ud->rxb = bdalloc(1);
	if (ud->txb == nil)
		ud->txb = bdalloc(1);

	p->rbase = ((ulong)ud->rxb) - (ulong)IMMR;
	p->tbase = ((ulong)ud->txb) - (ulong)IMMR;

	/* step 8: receive buffer size */
	p->mrblr = Rxsize;

	/* step 9: */
	p->maxidl = 15;

	/* step 10: */
	p->brkln = 0;
	p->brkec = 0;

	/* step 11: */
	p->brkcr = 0;

	/* step 12: setup receive buffer */
	ud->rxb->status = BDEmpty|BDWrap|BDInt;
	ud->rxb->length = 0;
	ud->rxbuf = xspanalloc(Rxsize, 0, CACHELINESZ);
	ud->rxb->addr = PADDR(ud->rxbuf);

	/* step 13: step transmit buffer */
	ud->txb->status = BDWrap|BDInt;
	ud->txb->length = 0;
	ud->txbuf = xspanalloc(Txsize, 0, CACHELINESZ);
	ud->txb->addr = PADDR(ud->txbuf);

	/* step 14: clear events */
	smc->smce = ce_Brke | ce_Br | ce_Bsy | ce_Txb | ce_Rxb;

	/* 
	 * step 15: enable interrupts (done later)
	 * smc->smcm = ce_Brke | ce_Br | ce_Bsy | ce_Txb | ce_Rxb;
	 */

	/* step 17: set parity, no of bits, UART mode, ... */
	lcr = SMUart;
	bits = uart->bits + 1;

	switch(uart->parity){
	case 'e':
		lcr |= (Pen|Pm);
		bits +=1;
		break;
	case 'o':
		lcr |= Pen;
		bits +=1;
		break;
	case 'n':
	default:
		break;
	}

	if(uart->stop == 2){
		lcr |= Sl;
		bits += 1;
	}

	/* Set new value and reenable if device was previously enabled */
	smc->smcmr = lcr |  bits <<11 | 0x3;

	ud->initialized = 1;
}

static void
smcenable(Uart *uart, int intenb)
{
	UartData *ud;
	SMC *smc;
	int nr;

	nr = uart - smcuart;
	if (nr < 0 || nr > Nuart)
		panic("No SMC %d", nr);
	ud = uartdata + nr;
	ud->smcno = nr;
	uart->regs = ud;
	if (ud->initialized == 0)
		smcinit(uart);
	if (ud->enabled || intenb == 0)
		return;
	smc = ud->smc;
	/* clear events */
	smc->smce = ce_Brke | ce_Br | ce_Bsy | ce_Txb | ce_Rxb;
	/* enable interrupts */
	smc->smcm = ce_Brke | ce_Br | ce_Bsy | ce_Txb | ce_Rxb;
	intrenable(VecSMC1 + ud->smcno, smcinterrupt, uart, uart->name);
	ud->enabled = 1;
}

static long
smcstatus(Uart* uart, void* buf, long n, long offset)
{
	SMC *sp;
	char p[128];

	sp = ((UartData*)uart->regs)->smc;
	snprint(p, sizeof p, "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,
		((sp->smcmr & Clen) >>11) - ((sp->smcmr&Pen) ? 1 : 0) - ((sp->smcmr&Sl) ? 2 : 1),
		(sp->smcmr & Pen) ? ((sp->smcmr & Pm) ? 'e': 'o'): 'n',
		(sp->smcmr & Sl) ? 2: 1,

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

	return n;
}

static void
smcfifo(Uart*, int)
{
	/*
	 * Toggle FIFOs:
	 * if none, do nothing;
	 * reset the Rx and Tx FIFOs;
	 * empty the Rx buffer and clear any interrupt conditions;
	 * if enabling, try to turn them on.
	 */
	return;
}

static void
smcdtr(Uart*, int)
{
}

static void
smcrts(Uart*, int)
{
}

static void
smcmodemctl(Uart*, int)
{
}

static int
smcparity(Uart* uart, int parity)
{
	int lcr;
	SMC *sp;

	sp = ((UartData*)uart->regs)->smc;

	lcr = sp->smcmr & ~(Pen|Pm);

	/* Disable transmitter/receiver. */
	sp->smcmr &= ~(Ren | Ten);

	switch(parity){
	case 'e':
		lcr |= (Pen|Pm);
		break;
	case 'o':
		lcr |= Pen;
		break;
	case 'n':
	default:
		break;
	}
	/* Set new value and reenable if device was previously enabled */
	sp->smcmr = lcr;

	uart->parity = parity;

	return 0;
}

static int
smcstop(Uart* uart, int stop)
{
	int lcr, bits;
	SMC *sp;

	sp = ((UartData*)uart->regs)->smc;
	lcr = sp->smcmr & ~(Sl | Clen);

	/* Disable transmitter/receiver. */
	sp->smcmr &= ~(Ren | Ten);

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

	bits = uart->bits + ((lcr & Pen) ? 1 : 0) + ((lcr & Sl) ? 2 : 1);
	lcr |= bits<<11;

	/* Set new value and reenable if device was previously enabled */
	sp->smcmr = lcr;

	uart->stop = stop;

	return 0;
}

static int
smcbits(Uart* uart, int bits)
{
	int lcr, b;
	SMC *sp;

	if (bits < 5 || bits > 14)
		return -1;

	sp = ((UartData*)uart->regs)->smc;
	lcr = sp->smcmr & ~Clen;

	b = bits + ((sp->smcmr & Pen) ? 1 : 0) + ((sp->smcmr & Sl) ? 2 : 1);

	if (b > 15)
		return -1;

	/* Disable transmitter/receiver */
	sp->smcmr &= ~(Ren | Ten);

	/* Set new value and reenable if device was previously enabled */
	sp->smcmr = lcr |  b<<11;

	uart->bits = bits;

	return 0;
}

static int
smcbaud(Uart* uart, int baud)
{
	int i;
	SMC *sp;

	if (uart->enabled){
		sp = ((UartData*)uart->regs)->smc;
	
		if(uart->freq == 0 || baud <= 0)
			return -1;
	
		i = sp - imm->smc;
		imm->brgc[i] = (((m->brghz >> 4) / baud) << 1) | 0x00010000;
	}
	uart->baud = baud;

	return 0;
}

static void
smcbreak(Uart*, int)
{
}

static void
smckick(Uart *uart)
{
	BD *txb;
	UartData *ud;
	int i;

	if(uart->blocked)
		return;

	ud = uart->regs;
	txb = ud->txb;

	if (txb->status & BDReady)
		return;	/* Still busy */

	for(i = 0; i < Txsize; i++){
		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
			break;
		ud->txbuf[i] = *(uart->op++);
	}
	if (i == 0)
		return;
	dcflush(ud->txbuf, Txsize);
	txb->length = i;
	sync();
	txb->status |= BDReady|BDInt;
}

static void
smcinterrupt(Ureg*, void* u)
{
	int i, nc;
	char *buf;
	BD *rxb;
	UartData *ud;
	Uart *uart;
	uchar events;

	uart = u;
	if (uart == nil)
		panic("uart is nil");
	ud = uart->regs;
	if (ud == nil)
		panic("ud is nil");

	events = ud->smc->smce;
	ud->smc->smce = events;	/* Clear events */

	if (events & 0x10)
		iprint("smc%d: break\n", ud->smcno);
	if (events & 0x4)
		uart->oerr++;
	if (events & 0x1){
		/* Receive characters
		*/
		rxb = ud->rxb;
		buf = ud->rxbuf;
		dczap(buf, Rxsize);	/* invalidate data cache before copying */
		if ((rxb->status & BDEmpty) == 0){
			nc = rxb->length;
			for (i=0; i<nc; i++)
				uartrecv(uart, *buf++);
			sync();
			rxb->status |= BDEmpty;
		}else{
			iprint("uartsmc: unexpected receive event\n");
		}
	}
	if (events & 0x2){
		if ((ud->txb->status & BDReady) == 0)
			uartkick(uart);
	}
}

static void
smcdisable(Uart* uart)
{
	SMC *sp;

	sp = ((UartData*)uart->regs)->smc;
	sp->smcmr &= ~(Ren | Ten);
}

static int
getchars(Uart *uart, uchar *cbuf)
{
	int i, nc;
	char *buf;
	BD *rxb;
	UartData *ud;

	ud = uart->regs;
	rxb = ud->rxb;

	/* Wait for character to show up.
	*/
	buf = ud->rxbuf;
	while (rxb->status & BDEmpty)
		;
	nc = rxb->length;
	for (i=0; i<nc; i++)
		*cbuf++ = *buf++;
	sync();
	rxb->status |= BDEmpty;

	return(nc);
}

static int
smcgetc(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
smcputc(Uart *uart, int c)
{
	BD *txb;
	UartData *ud;
	SMC *smc;

	ud = uart->regs;
	txb = ud->txb;
	smc = ud->smc;
	smc->smcm = 0;

	/* Wait for last character to go.
	*/
	while (txb->status & BDReady)
		;

	ud->txbuf[0] = c;
	dcflush(ud->txbuf, 1);
	txb->length = 1;
	sync();
	txb->status |= BDReady;

	while (txb->status & BDReady)
		;
}

PhysUart smcphysuart = {
	.name		= "smc",
	.pnp			= smcpnp,
	.enable		= smcenable,
	.disable		= smcdisable,
	.kick			= smckick,
	.dobreak		= smcbreak,
	.baud		= smcbaud,
	.bits			= smcbits,
	.stop			= smcstop,
	.parity		= smcparity,
	.modemctl	= smcmodemctl,
	.rts			= smcrts,
	.dtr			= smcdtr,
	.status		= smcstatus,
	.fifo			= smcfifo,
	.getc			= smcgetc,
	.putc			= smcputc,
};

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(smcuart))
		return;
	uart = smcuart + n;

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

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