shithub: purgatorio

ref: bd6c2aad586814b091ce5aca9d41cf2c51adb37b
dir: /os/manga/uartks8695.c/

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

#include "../port/uart.h"

/*
 * KS8695 uart; similar to 8250 etc but registers are slightly different,
 * and interrupt control is quite different
 */
enum {
	UartFREQ	= CLOCKFREQ,
};

/*
 * similar to i8250/16450/16550 (slight differences)
 */

enum {					/* I/O ports */
	Rbr		= 0,		/* Receiver Buffer (RO) */
	Thr		= 1,		/* Transmitter Holding (WO) */
	Fcr		= 2,		/* FIFO Control  */
	Lcr		= 3,		/* Line Control */
	Mcr		= 4,		/* Modem Control */
	Lsr		= 5,		/* Line Status */
	Msr		= 6,		/* Modem Status */
	Div		= 7,		/* Divisor  */
	Usr		= 8,		/* Status */
};

enum {					/* Fcr */
	FIFOena		= 0x01,		/* FIFO enable */
	FIFOrclr	= 0x02,		/* clear Rx FIFO */
	FIFOtclr	= 0x04,		/* clear Tx FIFO */
	FIFO1		= 0x00,		/* Rx FIFO trigger level 1 byte */
	FIFO4		= 0x40,		/*	4 bytes */
	FIFO8		= 0x80,		/*	8 bytes */
	FIFO14		= 0xC0,		/*	14 bytes */
};

enum {					/* Lcr */
	Wls5		= 0x00,		/* Word Length Select 5 bits/byte */
	Wls6		= 0x01,		/*	6 bits/byte */
	Wls7		= 0x02,		/*	7 bits/byte */
	Wls8		= 0x03,		/*	8 bits/byte */
	WlsMASK		= 0x03,
	Stb		= 0x04,		/* 2 stop bits */
	Pen		= 0x08,		/* Parity Enable */
	Eps		= 0x10,		/* Even Parity Select */
	Stp		= 0x20,		/* Stick Parity */
	Brk		= 0x40,		/* Break */
	Dlab		= 0x80,		/* Divisor Latch Access Bit */
};

enum {					/* Mcr */
	Dtr		= 0x01,		/* Data Terminal Ready */
	Rts		= 0x02,		/* Ready To Send */
	Out1		= 0x04,		/* UART OUT1 asserted */
	Out2		= 0x08,		/* UART OUT2 asserted */
	Dm		= 0x10,		/* Diagnostic Mode loopback */
};

enum {					/* Lsr */
	Dr		= 0x01,		/* Data Ready */
	Oe		= 0x02,		/* Overrun Error */
	Pe		= 0x04,		/* Parity Error */
	Fe		= 0x08,		/* Framing Error */
	Bi		= 0x10,		/* Break Interrupt */
	Thre		= 0x20,		/* Thr Empty */
	Temt		= 0x40,		/* Tramsmitter Empty */
	FIFOerr		= 0x80,		/* error in receiver FIFO */
	LsrInput		= FIFOerr|Oe|Pe|Fe|Dr|Bi,	/* input status only */
};

enum {					/* Msr */
	Dcts		= 0x01,		/* Delta Cts */
	Ddsr		= 0x02,		/* Delta Dsr */
	Teri		= 0x04,		/* Trailing Edge of Ri */
	Ddcd		= 0x08,		/* Delta Dcd */
	Cts		= 0x10,		/* Clear To Send */
	Dsr		= 0x20,		/* Data Set Ready */
	Ri		= 0x40,		/* Ring Indicator */
	Dcd		= 0x80,		/* Data Set Ready */
};

enum {					/* Usr */
	Uti		= 0x01,		/* INTST[9]=1=> =1, interrupt is timeout; =0, receive FIFO trigger */
};

typedef struct Ctlr {
	ulong*	regs;
	int	irq;
	int	iena;

	Lock;
	int	fena;
} Ctlr;

extern PhysUart ks8695physuart;


static Ctlr ks8695_ctlr[1] = {
{	.regs	= (ulong*)PHYSUART,
	.irq	= IRQuts,	/* base: ts then rs, ls, ms */
},
};

static Uart ks8695_uart[1] = {
{	.regs	= &ks8695_ctlr[0],
	.name	= "eia0",
	.freq	= UartFREQ,
	.phys	= &ks8695physuart,
	.special= 0,
	.next	= nil, },
};

#define csr8r(c, r)	((c)->regs[(r)])
#define csr8w(c, r, v)	((c)->regs[(r)] = (v))

static long
ks8695_status(Uart* uart, void* buf, long n, long offset)
{
	char *p;
	Ctlr *ctlr;
	uchar ier, lcr, mcr, msr;

	ctlr = uart->regs;
	p = malloc(READSTR);
	mcr = csr8r(ctlr, Mcr);
	msr = csr8r(ctlr, Msr);
	ier = INTRREG->en;
	lcr = csr8r(ctlr, Lcr);
	snprint(p, READSTR,
		"b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d ier=%ux\n"
		"dev(%d) type(%d) framing(%d) overruns(%d)%s%s%s%s\n",

		uart->baud,
		uart->hup_dcd, 
		(msr & Dsr) != 0,
		uart->hup_dsr,
		(lcr & WlsMASK) + 5,
		(ier & (1<<IRQums)) != 0, 
		(lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n',
		(mcr & Rts) != 0,
		(lcr & Stb) ? 2: 1,
		ctlr->fena,
		ier,

		uart->dev,
		uart->type,
		uart->ferr,
		uart->oerr, 
		(msr & Cts) ? " cts": "",
		(msr & Dsr) ? " dsr": "",
		(msr & Dcd) ? " dcd": "",
		(msr & Ri) ? " ring": ""
	);
	n = readstr(offset, buf, n, p);
	free(p);

	return n;
}

static void
ks8695_fifo(Uart* uart, int level)
{
	Ctlr *ctlr;

	ctlr = uart->regs;

	/*
	 * Changing the FIFOena bit in Fcr flushes data
	 * from both receive and transmit FIFOs; there's
	 * no easy way to guarantee not losing data on
	 * the receive side, but it's possible to wait until
	 * the transmitter is really empty.
	 */
	ilock(ctlr);
	while(!(csr8r(ctlr, Lsr) & Temt))
		;

	/*
	 * Set the trigger level, default is the max.
	 * value.
	 */
	ctlr->fena = level;
	switch(level){
	case 0:
		break;
	case 1:
		level = FIFO1|FIFOena;
		break;
	case 4:
		level = FIFO4|FIFOena;
		break;
	case 8:
		level = FIFO8|FIFOena;
		break;
	default:
		level = FIFO14|FIFOena;
		break;
	}
	csr8w(ctlr, Fcr, level);
	iunlock(ctlr);
}

static void
ks8695_dtr(Uart* uart, int on)
{
	Ctlr *ctlr;
	int r;

	/*
	 * Toggle DTR.
	 */
	ctlr = uart->regs;
	r = csr8r(ctlr, Mcr);
	if(on)
		r |= Dtr;
	else
		r &= ~Dtr;
	csr8w(ctlr, Mcr, r);
}

static void
ks8695_rts(Uart* uart, int on)
{
	Ctlr *ctlr;
	int r;

	/*
	 * Toggle RTS.
	 */
	ctlr = uart->regs;
	r = csr8r(ctlr, Mcr);
	if(on)
		r |= Rts;
	else
		r &= ~Rts;
	csr8w(ctlr, Mcr, r);
}

static void
ks8695_modemctl(Uart* uart, int on)
{
	Ctlr *ctlr;

	ctlr = uart->regs;
	ilock(&uart->tlock);
	if(on){
		INTRREG->en |= 1<<IRQums;	/* TO DO */
		uart->modem = 1;
		uart->cts = csr8r(ctlr, Msr) & Cts;
	}
	else{
		INTRREG->en &= ~(1<<IRQums);
		uart->modem = 0;
		uart->cts = 1;
	}
	iunlock(&uart->tlock);

	/* modem needs fifo */
	(*uart->phys->fifo)(uart, on);
}

static int
ks8695_parity(Uart* uart, int parity)
{
	int lcr;
	Ctlr *ctlr;

	ctlr = uart->regs;
	lcr = csr8r(ctlr, Lcr) & ~(Eps|Pen);

	switch(parity){
	case 'e':
		lcr |= Eps|Pen;
		break;
	case 'o':
		lcr |= Pen;
		break;
	case 'n':
	default:
		break;
	}
	csr8w(ctlr, Lcr, lcr);

	uart->parity = parity;

	return 0;
}

static int
ks8695_stop(Uart* uart, int stop)
{
	int lcr;
	Ctlr *ctlr;

	ctlr = uart->regs;
	lcr = csr8r(ctlr, Lcr);
	switch(stop){
	case 1:
		lcr &= ~Stb;
		break;
	case 2:
		lcr |= Stb;
		break;
	default:
		return -1;
	}
	csr8w(ctlr, Lcr, lcr);
	uart->stop = stop;
	return 0;
}

static int
ks8695_bits(Uart* uart, int bits)
{
	int lcr;
	Ctlr *ctlr;

	ctlr = uart->regs;
	lcr = csr8r(ctlr, Lcr) & ~WlsMASK;

	switch(bits){
	case 5:
		lcr |= Wls5;
		break;
	case 6:
		lcr |= Wls6;
		break;
	case 7:
		lcr |= Wls7;
		break;
	case 8:
		lcr |= Wls8;
		break;
	default:
		return -1;
	}
	csr8w(ctlr, Lcr, lcr);

	uart->bits = bits;

	return 0;
}

static int
ks8695_baud(Uart* uart, int baud)
{
	ulong bgc;
	Ctlr *ctlr;

	if(uart->freq == 0 || baud <= 0)
		return -1;
	ctlr = uart->regs;
	bgc = (uart->freq+baud-1)/baud;
	csr8w(ctlr, Div, bgc);
	uart->baud = baud;
	return 0;
}

static void
ks8695_break(Uart* uart, int ms)
{
	Ctlr *ctlr;
	int lcr;

	/*
	 * Send a break.
	 */
	if(ms == 0)
		ms = 200;

	ctlr = uart->regs;
	lcr = csr8r(ctlr, Lcr);
	csr8w(ctlr, Lcr, lcr|Brk);
	tsleep(&up->sleep, return0, 0, ms);
	csr8w(ctlr, Lcr, lcr);
}

static void
ks8695_kick(Uart* uart)
{
	int i;
	Ctlr *ctlr;

	if(uart->cts == 0 || uart->blocked)
		return;

	ctlr = uart->regs;
	for(i = 0; i < 16; i++){
		if(!(csr8r(ctlr, Lsr) & Thre))
			break;
		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
			break;
		csr8w(ctlr, Thr, *uart->op++);
	}
}

static void
ks8695_modemintr(Ureg*, void *arg)
{
	Ctlr *ctlr;
	Uart *uart;
	int old, r;

	uart = arg;
	ctlr = uart->regs;
	r = csr8r(ctlr, Msr);
	if(r & Dcts){
		ilock(&uart->tlock);
		old = uart->cts;
		uart->cts = r & Cts;
		if(old == 0 && uart->cts)
			uart->ctsbackoff = 2;
		iunlock(&uart->tlock);
	}
 	if(r & Ddsr){
		old = r & Dsr;
		if(uart->hup_dsr && uart->dsr && !old)
			uart->dohup = 1;
		uart->dsr = old;
	}
 	if(r & Ddcd){
		old = r & Dcd;
		if(uart->hup_dcd && uart->dcd && !old)
			uart->dohup = 1;
		uart->dcd = old;
	}
}

static void
ks8695_rxintr(Ureg*, void* arg)
{
	Ctlr *ctlr;
	Uart *uart;
	int lsr, r;

	/* handle line error status here as well */
	uart = arg;
	ctlr = uart->regs;
	while((lsr = csr8r(ctlr, Lsr) & LsrInput) != 0){
		/*
		 * Consume any received data.
		 * If the received byte came in with a break,
		 * parity or framing error, throw it away;
		 * overrun is an indication that something has
		 * already been tossed.
		 */
		if(lsr & (FIFOerr|Oe))
			uart->oerr++;
		if(lsr & Pe)
			uart->perr++;
		if(lsr & Fe)
			uart->ferr++;
		if(lsr & Dr){
			r = csr8r(ctlr, Rbr);
			if(!(lsr & (Bi|Fe|Pe)))
				uartrecv(uart, r);
		}
	}
}

static void
ks8695_txintr(Ureg*, void* arg)
{
	uartkick(arg);
}

static void
ks8695_disable(Uart* uart)
{
	Ctlr *ctlr;

	/*
 	 * Turn off DTR and RTS, disable interrupts and fifos.
	 */
	(*uart->phys->dtr)(uart, 0);
	(*uart->phys->rts)(uart, 0);
	(*uart->phys->fifo)(uart, 0);

	ctlr = uart->regs;

	if(ctlr->iena != 0){
		intrdisable(IRQ, ctlr->irq, ks8695_txintr, uart, uart->name);
		intrdisable(IRQ, ctlr->irq+1, ks8695_rxintr, uart, uart->name);
		intrdisable(IRQ, ctlr->irq+2, ks8695_rxintr, uart, uart->name);
		intrdisable(IRQ, ctlr->irq+3, ks8695_modemintr, uart, uart->name);
		ctlr->iena = 0;
	}
}

static void
ks8695_enable(Uart* uart, int ie)
{
	Ctlr *ctlr;

	ctlr = uart->regs;

	/*
 	 * Enable interrupts and turn on DTR and RTS.
	 * Be careful if this is called to set up a polled serial line
	 * early on not to try to enable interrupts as interrupt-
	 * -enabling mechanisms might not be set up yet.
	 */
	if(ctlr->iena == 0 && ie){
		intrenable(IRQ, ctlr->irq, ks8695_txintr, uart, uart->name);
		intrenable(IRQ, ctlr->irq+1, ks8695_rxintr, uart, uart->name);
		intrenable(IRQ, ctlr->irq+2, ks8695_rxintr, uart, uart->name);
		intrenable(IRQ, ctlr->irq+3, ks8695_modemintr, uart, uart->name);
		ctlr->iena = 1;
	}

	(*uart->phys->dtr)(uart, 1);
	(*uart->phys->rts)(uart, 1);
}

static Uart*
ks8695_pnp(void)
{
	return ks8695_uart;
}

static int
ks8695_getc(Uart *uart)
{
	Ctlr *ctlr;

	ctlr = uart->regs;
	while(!(csr8r(ctlr, Lsr)&Dr))
		delay(1);
	return csr8r(ctlr, Rbr);
}

static void
ks8695_putc(Uart *uart, int c)
{
	serialputc(c);
#ifdef ROT
	int i;
	Ctlr *ctlr;

	ctlr = uart->regs;
	for(i = 0; !(csr8r(ctlr, Lsr)&Thre) && i < 256; i++)
		delay(1);
	csr8w(ctlr, Thr, c);
	if(c == '\n')
		while((csr8r(ctlr, Lsr) & Temt) == 0){	/* let fifo drain */
			/* skip */
		}
#endif
}

PhysUart ks8695physuart = {
	.name		= "ks8695",
	.pnp		= ks8695_pnp,
	.enable		= ks8695_enable,
	.disable	= ks8695_disable,
	.kick		= ks8695_kick,
	.dobreak	= ks8695_break,
	.baud		= ks8695_baud,
	.bits		= ks8695_bits,
	.stop		= ks8695_stop,
	.parity		= ks8695_parity,
	.modemctl	= ks8695_modemctl,
	.rts		= ks8695_rts,
	.dtr		= ks8695_dtr,
	.status		= ks8695_status,
	.fifo		= ks8695_fifo,
	.getc		= ks8695_getc,
	.putc		= ks8695_putc,
};

void
uartconsole(void)
{
	Uart *uart;

	uart = &ks8695_uart[0];
	(*uart->phys->enable)(uart, 0);
	uartctl(uart, "b38400 l8 pn s1");
	consuart = uart;
	uart->console = 1;
}

#define	UR(p,r)	((ulong*)(p))[r]

void
serialputc(int c)
{
	ulong *p;

	if(c == 0)
		return;
	p = (ulong*)PHYSUART;
	while((UR(p,Lsr) & Thre) == 0){
		/* skip */
	}
	UR(p,Thr) = c;
	if(c == '\n')
		while((UR(p,Lsr) & Temt) == 0){	/* let fifo drain */
			/* skip */
		}
}

/*
 *  for iprint, just write it
 */
void
serialputs(char *data, int len)
{
	ulong *p;

	p = (ulong*)PHYSUART;
	while(--len >= 0){
		if(*data == '\n')
			serialputc('\r');
		serialputc(*data++);
	}
	while((UR(p,Lsr) & Temt) == 0){	/* let fifo drain */
		/* skip */
	}
}
void (*serwrite)(char*, int) = serialputs;