shithub: purgatorio

ref: b7af62b250e5dff30320a181ca9d53ab5a7c276d
dir: /os/port/devns16552.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/netif.h"

/*
 *  Driver for the ns16552.
 */
enum
{
	/*
	 *  register numbers
	 */
	Data=	0,		/* xmit/rcv buffer */
	Iena=	1,		/* interrupt enable */
	 Ircv=	(1<<0),		/*  for char rcv'd */
	 Ixmt=	(1<<1),		/*  for xmit buffer empty */
	 Irstat=(1<<2),		/*  for change in rcv'er status */
	 Imstat=(1<<3),		/*  for change in modem status */
	Istat=	2,		/* interrupt flag (read) */
	 Ipend=	1,		/* interrupt pending (not) */
	 Fenabd=(3<<6),   		/*  on if fifo's enabled */
	Fifoctl=2,		/* fifo control (write) */
	 Fena=	(1<<0),		/*  enable xmit/rcv fifos */
	 Ftrig=	(1<<6),		/*  trigger after 4 input characters */
	 Fclear=(3<<1),		/*  clear xmit & rcv fifos */
	Format=	3,		/* byte format */
	 Bits8=	(3<<0),		/*  8 bits/byte */
	 Stop2=	(1<<2),		/*  2 stop bits */
	 Pena=	(1<<3),		/*  generate parity */
	 Peven=	(1<<4),		/*  even parity */
	 Pforce=(1<<5),		/*  force parity */
	 Break=	(1<<6),		/*  generate a break */
	 Dra=	(1<<7),		/*  address the divisor */
	Mctl=	4,		/* modem control */
	 Dtr=	(1<<0),		/*  data terminal ready */
	 Rts=	(1<<1),		/*  request to send */
	 Ri=	(1<<2),		/*  ring */
	 Inton=	(1<<3),		/*  turn on interrupts */
	 Loop=	(1<<4),		/*  loop back */
	Lstat=	5,		/* line status */
	 Inready=(1<<0),	/*  receive buffer full */
	 Oerror=(1<<1),		/*  receiver overrun */
	 Perror=(1<<2),		/*  receiver parity error */
	 Ferror=(1<<3),		/*  rcv framing error */
	 Berror=(1<<4),		/* break alarm */
	 Outready=(1<<5),	/*  output buffer full */
	Mstat=	6,		/* modem status */
	 Ctsc=	(1<<0),		/*  clear to send changed */
	 Dsrc=	(1<<1),		/*  data set ready changed */
	 Rire=	(1<<2),		/*  rising edge of ring indicator */
	 Dcdc=	(1<<3),		/*  data carrier detect changed */
	 Cts=	(1<<4),		/*  complement of clear to send line */
	 Dsr=	(1<<5),		/*  complement of data set ready line */
	 Ring=	(1<<6),		/*  complement of ring indicator line */
	 Dcd=	(1<<7),		/*  complement of data carrier detect line */
	Scratch=7,		/* scratchpad */
	Dlsb=	0,		/* divisor lsb */
	Dmsb=	1,		/* divisor msb */

	CTLS= 023,
	CTLQ= 021,

	Stagesize= 1024,
	Nuart=	32,		/* max per machine */
};

typedef struct Uart Uart;
struct Uart
{
	QLock;
	int	opens;

	int	enabled;
	Uart	*elist;			/* next enabled interface */
	char	name[KNAMELEN];

	uchar	sticky[8];		/* sticky write register values */
	ulong	port;
	ulong	freq;			/* clock frequency */
	uchar	mask;			/* bits/char */
	int	dev;
	int	baud;			/* baud rate */

	uchar	istat;			/* last istat read */
	int	frame;			/* framing errors */
	int	overrun;		/* rcvr overruns */

	/* buffers */
	int	(*putc)(Queue*, int);
	Queue	*iq;
	Queue	*oq;

	Lock	flock;			/* fifo */
	uchar	fifoon;			/* fifo's enabled */
	uchar	nofifo;			/* earlier chip version with nofifo */

	Lock	rlock;			/* receive */
	uchar	istage[Stagesize];
	uchar	*ip;
	uchar	*ie;

	int	haveinput;

	Lock	tlock;			/* transmit */
	uchar	ostage[Stagesize];
	uchar	*op;
	uchar	*oe;

	int	modem;			/* hardware flow control on */
	int	xonoff;			/* software flow control on */
	int	blocked;
	int	cts, dsr, dcd;		/* keep track of modem status */ 
	int	ctsbackoff;
	int	hup_dsr, hup_dcd;	/* send hangup upstream? */
	int	dohup;

	Rendez	r;
};

static Uart *uart[Nuart];
static int nuart;
static Uart *consuart;

struct Uartalloc {
	Lock;
	Uart *elist;	/* list of enabled interfaces */
} uartalloc;

void ns16552intr(int);

/*
 *  pick up architecture specific routines and definitions
 */
#include "ns16552.h"

/*
 *  set the baud rate by calculating and setting the baudrate
 *  generator constant.  This will work with fairly non-standard
 *  baud rates.
 */
static void
ns16552setbaud(Uart *p, int rate)
{
	ulong brconst;

	if(rate <= 0)
		return;

	brconst = (p->freq+8*rate-1)/(16*rate);

	uartwrreg(p, Format, Dra);
	outb(p->port + Dmsb, (brconst>>8) & 0xff);
	outb(p->port + Dlsb, brconst & 0xff);
	uartwrreg(p, Format, 0);

	p->baud = rate;
}

/*
 * decide if we should hangup when dsr or dcd drops.
 */
static void
ns16552dsrhup(Uart *p, int n)
{
	p->hup_dsr = n;
}

static void
ns16552dcdhup(Uart *p, int n)
{
	p->hup_dcd = n;
}

static void
ns16552parity(Uart *p, char type)
{
	switch(type){
	case 'e':
		p->sticky[Format] |= Pena|Peven;
		break;
	case 'o':
		p->sticky[Format] &= ~Peven;
		p->sticky[Format] |= Pena;
		break;
	default:
		p->sticky[Format] &= ~(Pena|Peven);
		break;
	}
	uartwrreg(p, Format, 0);
}

/*
 *  set bits/character, default 8
 */
void
ns16552bits(Uart *p, int bits)
{
	if(bits < 5 || bits > 8)
		error(Ebadarg);

	p->sticky[Format] &= ~3;
	p->sticky[Format] |= bits-5;

	uartwrreg(p, Format, 0);
}


/*
 *  toggle DTR
 */
void
ns16552dtr(Uart *p, int n)
{
	if(n)
		p->sticky[Mctl] |= Dtr;
	else
		p->sticky[Mctl] &= ~Dtr;

	uartwrreg(p, Mctl, 0);
}

/*
 *  toggle RTS
 */
void
ns16552rts(Uart *p, int n)
{
	if(n)
		p->sticky[Mctl] |= Rts;
	else
		p->sticky[Mctl] &= ~Rts;

	uartwrreg(p, Mctl, 0);
}

/*
 *  send break
 */
static void
ns16552break(Uart *p, int ms)
{
	if(ms == 0)
		ms = 200;

	uartwrreg(p, Format, Break);
	tsleep(&up->sleep, return0, 0, ms);
	uartwrreg(p, Format, 0);
}

static void
ns16552fifoon(Uart *p)
{
	ulong i, x;

	if(p->nofifo || uartrdreg(p, Istat) & Fenabd)
		return;

	x = splhi();

	/* reset fifos */
	p->sticky[Fifoctl] = 0;
	uartwrreg(p, Fifoctl, Fclear);

	/* empty buffer and interrupt conditions */
	for(i = 0; i < 16; i++){
		if(uartrdreg(p, Istat)){
			/* nothing to do */
		}
		if(uartrdreg(p, Data)){
			/* nothing to do */
		}
	}

	/* turn on fifo */
	p->fifoon = 1;
	p->sticky[Fifoctl] = Fena|Ftrig;
	uartwrreg(p, Fifoctl, 0);
	p->istat = uartrdreg(p, Istat);
	if((p->istat & Fenabd) == 0) {
		/* didn't work, must be an earlier chip type */
		p->nofifo = 1;
	}

	splx(x);
}

/*
 *  modem flow control on/off (rts/cts)
 */
static void
ns16552mflow(Uart *p, int n)
{
	ilock(&p->tlock);
	if(n){
		p->sticky[Iena] |= Imstat;
		uartwrreg(p, Iena, 0);
		p->modem = 1;
		p->cts = uartrdreg(p, Mstat) & Cts;
	} else {
		p->sticky[Iena] &= ~Imstat;
		uartwrreg(p, Iena, 0);
		p->modem = 0;
		p->cts = 1;
	}
	iunlock(&p->tlock);

//	ilock(&p->flock);
//	if(1)
//		/* turn on fifo's */
//		ns16552fifoon(p);
//	else {
//		/* turn off fifo's */
//		p->fifoon = 0;
//		p->sticky[Fifoctl] = 0;
//		uartwrreg(p, Fifoctl, Fclear);
//	}
//	iunlock(&p->flock);
}

/*
 *  turn on a port's interrupts.  set DTR and RTS
 */
static void
ns16552enable(Uart *p)
{
	Uart **l;

	if(p->enabled)
		return;

	uartpower(p->dev, 1);

	p->hup_dsr = p->hup_dcd = 0;
	p->cts = p->dsr = p->dcd = 0;

	/*
 	 *  turn on interrupts
	 */
	p->sticky[Iena] = Ircv | Ixmt | Irstat;
	uartwrreg(p, Iena, 0);

	/*
	 *  turn on DTR and RTS
	 */
	ns16552dtr(p, 1);
	ns16552rts(p, 1);

	ns16552fifoon(p);

	/*
	 *  assume we can send
	 */
	ilock(&p->tlock);
	p->cts = 1;
	p->blocked = 0;
	iunlock(&p->tlock);

	/*
	 *  set baud rate to the last used
	 */
	ns16552setbaud(p, p->baud);

	lock(&uartalloc);
	for(l = &uartalloc.elist; *l; l = &(*l)->elist){
		if(*l == p)
			break;
	}
	if(*l == 0){
		p->elist = uartalloc.elist;
		uartalloc.elist = p;
	}
	p->enabled = 1;
	unlock(&uartalloc);
}

/*
 *  turn off a port's interrupts.  reset DTR and RTS
 */
static void
ns16552disable(Uart *p)
{
	Uart **l;

	/*
 	 *  turn off interrupts
	 */
	p->sticky[Iena] = 0;
	uartwrreg(p, Iena, 0);

	/*
	 *  revert to default settings
	 */
	p->sticky[Format] = Bits8;
	uartwrreg(p, Format, 0);

	/*
	 *  turn off DTR, RTS, hardware flow control & fifo's
	 */
	ns16552dtr(p, 0);
	ns16552rts(p, 0);
	ns16552mflow(p, 0);
	ilock(&p->tlock);
	p->xonoff = p->blocked = 0;
	iunlock(&p->tlock);

	uartpower(p->dev, 0);

	lock(&uartalloc);
	for(l = &uartalloc.elist; *l; l = &(*l)->elist){
		if(*l == p){
			*l = p->elist;
			break;
		}
	}
	p->enabled = 0;
	unlock(&uartalloc);
}

/*
 *  put some bytes into the local queue to avoid calling
 *  qconsume for every character
 */
static int
stageoutput(Uart *p)
{
	int n;

	n = qconsume(p->oq, p->ostage, Stagesize);
	if(n <= 0)
		return 0;
	p->op = p->ostage;
	p->oe = p->ostage + n;
	return n;
}

/*
 *  (re)start output
 */
static void
ns16552kick0(Uart *p)
{
	int i;
	if((p->modem && (p->cts == 0)) || p->blocked)
		return;

	/*
	 *  128 here is an arbitrary limit to make sure
	 *  we don't stay in this loop too long.  If the
	 *  chips output queue is longer than 128, too
	 *  bad -- presotto
	 */
	for(i = 0; i < 128; i++){
		if(!(uartrdreg(p, Lstat) & Outready))
			break;
		if(p->op >= p->oe && stageoutput(p) == 0)
			break;
		outb(p->port + Data, *(p->op++));
	}
}

static void
ns16552kick(void *v)
{
	Uart *p;

	p = v;
	ilock(&p->tlock);
	ns16552kick0(p);
	iunlock(&p->tlock);
}

/*
 *  restart input if it's off
 */
static void
ns16552flow(void *v)
{
	Uart *p;

	p = v;
	if(p->modem)
		ns16552rts(p, 1);
	ilock(&p->rlock);
	p->haveinput = 1;
	iunlock(&p->rlock);
}

/*
 *  default is 9600 baud, 1 stop bit, 8 bit chars, no interrupts,
 *  transmit and receive enabled, interrupts disabled.
 */
static void
ns16552setup0(Uart *p)
{
	memset(p->sticky, 0, sizeof(p->sticky));
	/*
	 *  set rate to 9600 baud.
	 *  8 bits/character.
	 *  1 stop bit.
	 *  interrupts enabled.
	 */
	p->sticky[Format] = Bits8;
	uartwrreg(p, Format, 0);
	p->sticky[Mctl] |= Inton;
	uartwrreg(p, Mctl, 0x0);

	ns16552setbaud(p, 9600);

	p->iq = qopen(4*1024, 0, ns16552flow, p);
	p->oq = qopen(4*1024, 0, ns16552kick, p);
	if(p->iq == nil || p->oq == nil)
		panic("ns16552setup0");

	p->ip = p->istage;
	p->ie = &p->istage[Stagesize];
	p->op = p->ostage;
	p->oe = p->ostage;
}

/*
 *  called by main() to create a new duart
 */
void
ns16552setup(ulong port, ulong freq, char *name)
{
	Uart *p;

	if(nuart >= Nuart)
		return;

	p = xalloc(sizeof(Uart));
	uart[nuart] = p;
	strcpy(p->name, name);
	p->dev = nuart;
	nuart++;
	p->port = port;
	p->freq = freq;
	ns16552setup0(p);
}

/*
 *  called by main() to configure a duart port as a console or a mouse
 */
void
ns16552special(int port, int baud, Queue **in, Queue **out, int (*putc)(Queue*, int))
{
	Uart *p = uart[port];
	ns16552enable(p);
	if(baud)
		ns16552setbaud(p, baud);
	p->putc = putc;
	if(in)
		*in = p->iq;
	if(out)
		*out = p->oq;
	p->opens++;
}

/*
 *  handle an interrupt to a single uart
 */
void
ns16552intr(int dev)
{
	uchar ch;
	int s, l;
	Uart *p = uart[dev];

	for (s = uartrdreg(p, Istat); !(s&Ipend); s = uartrdreg(p, Istat)) {
		switch(s&0x3f){
		case 4:	/* received data available */
		case 6:	/* receiver line status (alarm or error) */
		case 12:	/* character timeout indication */
			while ((l = uartrdreg(p, Lstat)) & Inready) {
				if(l & Ferror)
					p->frame++;
				if(l & Oerror)
					p->overrun++;
				ch = uartrdreg(p, Data) & 0xff;
				if (l & (Berror|Perror|Ferror)) {
					/* ch came with break, parity or framing error - consume */
					continue;
				}
				if (ch == CTLS || ch == CTLQ) {
					ilock(&p->tlock);
					if(p->xonoff){
						if(ch == CTLS)
							p->blocked = 1;
						else
							p->blocked = 0;	/* clock gets output going again */
					}
					iunlock(&p->tlock);
				}
				if(p->putc)
					p->putc(p->iq, ch);
				else {
					ilock(&p->rlock);
					if(p->ip < p->ie)
						*p->ip++ = ch;
					else
						p->overrun++;
					p->haveinput = 1;
					iunlock(&p->rlock);
				}
			}
			break;

		case 2:	/* transmitter not full */
			ns16552kick(p);
			break;

		case 0:	/* modem status */
			ch = uartrdreg(p, Mstat);
			if(ch & Ctsc){
				ilock(&p->tlock);
				l = p->cts;
				p->cts = ch & Cts;
				if(l == 0 && p->cts)
					p->ctsbackoff = 2; /* clock gets output going again */
				iunlock(&p->tlock);
			}
	 		if (ch & Dsrc) {
				l = ch & Dsr;
				if(p->hup_dsr && p->dsr && !l){
					ilock(&p->rlock);
					p->dohup = 1;
					iunlock(&p->rlock);
				}
				p->dsr = l;
			}
	 		if (ch & Dcdc) {
				l = ch & Dcd;
				if(p->hup_dcd && p->dcd && !l){
					ilock(&p->rlock);
					p->dohup = 1;
					iunlock(&p->rlock);
				}
				p->dcd = l;
			}
			break;

		default:
			print("weird uart interrupt #%2.2ux\n", s);
			break;
		}
	}
	p->istat = s;
}

/*
 *  we save up input characters till clock time
 *
 *  There's also a bit of code to get a stalled print going.
 *  It shouldn't happen, but it does.  Obviously I don't
 *  understand something.  Since it was there, I bundled a
 *  restart after flow control with it to give some hysteresis
 *  to the hardware flow control.  This makes compressing
 *  modems happier but will probably bother something else.
 *	 -- presotto
 */
void
uartclock(void)
{
	int n;
	Uart *p;

	for(p = uartalloc.elist; p; p = p->elist){

		/* this amortizes cost of qproduce to many chars */
		if(p->haveinput){
			ilock(&p->rlock);
			if(p->haveinput){
				n = p->ip - p->istage;
				if(n > 0 && p->iq){
					if(n > Stagesize)
						panic("uartclock");
					if(qproduce(p->iq, p->istage, n) < 0)
						ns16552rts(p, 0);
					else
						p->ip = p->istage;
				}
				p->haveinput = 0;
			}
			iunlock(&p->rlock);
		}
		if(p->dohup){
			ilock(&p->rlock);
			if(p->dohup){
				qhangup(p->iq, 0);
				qhangup(p->oq, 0);
			}
			p->dohup = 0;
			iunlock(&p->rlock);
		}

		/* this adds hysteresis to hardware flow control */
		if(p->ctsbackoff){
			ilock(&p->tlock);
			if(p->ctsbackoff){
				if(--(p->ctsbackoff) == 0)
					ns16552kick0(p);
			}
			iunlock(&p->tlock);
		}
	}
}

Dirtab *ns16552dir;
int ndir;

static void
setlength(int i)
{
	Uart *p;

	if(i > 0){
		p = uart[i];
		if(p && p->opens && p->iq)
			ns16552dir[1+3*i].length = qlen(p->iq);
	} else for(i = 0; i < nuart; i++){
		p = uart[i];
		if(p && p->opens && p->iq)
			ns16552dir[1+3*i].length = qlen(p->iq);
	}
}

/*
 *  all uarts must be ns16552setup() by this point or inside of ns16552install()
 */
static void
ns16552reset(void)
{
	int i;
	Dirtab *dp;
	ns16552install();	/* architecture specific */

	ndir = 1+3*nuart;
	ns16552dir = xalloc(ndir * sizeof(Dirtab));
	dp = ns16552dir;
	strcpy(dp->name, ".");
	mkqid(&dp->qid, 0, 0, QTDIR);
	dp->length = 0;
	dp->perm = DMDIR|0555;
	dp++;
	for(i = 0; i < nuart; i++){
		/* 3 directory entries per port */
		strcpy(dp->name, uart[i]->name);
		dp->qid.path = NETQID(i, Ndataqid);
		dp->perm = 0666;
		dp++;
		sprint(dp->name, "%sctl", uart[i]->name);
		dp->qid.path = NETQID(i, Nctlqid);
		dp->perm = 0666;
		dp++;
		sprint(dp->name, "%sstatus", uart[i]->name);
		dp->qid.path = NETQID(i, Nstatqid);
		dp->perm = 0444;
		dp++;
	}
}

static Chan*
ns16552attach(char *spec)
{
	return devattach('t', spec);
}

static Walkqid*
ns16552walk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, ns16552dir, ndir, devgen);
}

static int
ns16552stat(Chan *c, uchar *dp, int n)
{
	if(NETTYPE(c->qid.path) == Ndataqid)
		setlength(NETID(c->qid.path));
	return devstat(c, dp, n, ns16552dir, ndir, devgen);
}

static Chan*
ns16552open(Chan *c, int omode)
{
	Uart *p;

	c = devopen(c, omode, ns16552dir, ndir, devgen);

	switch(NETTYPE(c->qid.path)){
	case Nctlqid:
	case Ndataqid:
		p = uart[NETID(c->qid.path)];
		qlock(p);
		if(p->opens++ == 0){
			ns16552enable(p);
			qreopen(p->iq);
			qreopen(p->oq);
		}
		qunlock(p);
		break;
	}

	return c;
}

static void
ns16552close(Chan *c)
{
	Uart *p;

	if(c->qid.type & QTDIR)
		return;
	if((c->flag & COPEN) == 0)
		return;
	switch(NETTYPE(c->qid.path)){
	case Ndataqid:
	case Nctlqid:
		p = uart[NETID(c->qid.path)];
		qlock(p);
		if(--(p->opens) == 0){
			ns16552disable(p);
			qclose(p->iq);
			qclose(p->oq);
			p->ip = p->istage;
			p->dcd = p->dsr = p->dohup = 0;
		}
		qunlock(p);
		break;
	}
}

static long
uartstatus(Chan*, Uart *p, void *buf, long n, long offset)
{
	uchar mstat, fstat, istat, tstat;
	char str[256];

	str[0] = 0;
	tstat = p->sticky[Mctl];
	mstat = uartrdreg(p, Mstat);
	istat = p->sticky[Iena];
	fstat = p->sticky[Format];
	snprint(str, sizeof str,
		"b%d c%d d%d e%d l%d m%d p%c r%d s%d\n"
		"%d %d %d%s%s%s%s%s\n",

		p->baud,
		p->hup_dcd, 
		(tstat & Dtr) != 0,
		p->hup_dsr,
		(fstat & Bits8) + 5,
		(istat & Imstat) != 0, 
		(fstat & Pena) ? ((fstat & Peven) ? 'e' : 'o') : 'n',
		(tstat & Rts) != 0,
		(fstat & Stop2) ? 2 : 1,

		p->dev,
		p->frame,
		p->overrun, 
		uartrdreg(p, Istat) & Fenabd       ? " fifo" : "",
		(mstat & Cts)    ? " cts"  : "",
		(mstat & Dsr)    ? " dsr"  : "",
		(mstat & Dcd)    ? " dcd"  : "",
		(mstat & Ring)   ? " ring" : ""
	);
	return readstr(offset, buf, n, str);
}

static long
ns16552read(Chan *c, void *buf, long n, vlong off)
{
	Uart *p;
	ulong offset = off;

	if(c->qid.type & QTDIR){
		setlength(-1);
		return devdirread(c, buf, n, ns16552dir, ndir, devgen);
	}

	p = uart[NETID(c->qid.path)];
	switch(NETTYPE(c->qid.path)){
	case Ndataqid:
		return qread(p->iq, buf, n);
	case Nctlqid:
		return readnum(offset, buf, n, NETID(c->qid.path), NUMSIZE);
	case Nstatqid:
		return uartstatus(c, p, buf, n, offset);
	}

	return 0;
}

static void
ns16552ctl(Uart *p, char *cmd)
{
	int i, n;

	/* let output drain for a while */
	for(i = 0; i < 16 && qlen(p->oq); i++)
		tsleep(&p->r, (int(*)(void*))qlen, p->oq, 125);

	if(strncmp(cmd, "break", 5) == 0){
		ns16552break(p, 0);
		return;
	}


	n = atoi(cmd+1);
	switch(*cmd){
	case 'B':
	case 'b':
		ns16552setbaud(p, n);
		break;
	case 'C':
	case 'c':
		ns16552dcdhup(p, n);
		break;
	case 'D':
	case 'd':
		ns16552dtr(p, n);
		break;
	case 'E':
	case 'e':
		ns16552dsrhup(p, n);
		break;
	case 'f':
	case 'F':
		qflush(p->oq);
		break;
	case 'H':
	case 'h':
		qhangup(p->iq, 0);
		qhangup(p->oq, 0);
		break;
	case 'L':
	case 'l':
		ns16552bits(p, n);
		break;
	case 'm':
	case 'M':
		ns16552mflow(p, n);
		break;
	case 'n':
	case 'N':
		qnoblock(p->oq, n);
		break;
	case 'P':
	case 'p':
		ns16552parity(p, *(cmd+1));
		break;
	case 'K':
	case 'k':
		ns16552break(p, n);
		break;
	case 'R':
	case 'r':
		ns16552rts(p, n);
		break;
	case 'Q':
	case 'q':
		qsetlimit(p->iq, n);
		qsetlimit(p->oq, n);
		break;
	case 'W':
	case 'w':
		/* obsolete */
		break;
	case 'X':
	case 'x':
		ilock(&p->tlock);
		p->xonoff = n;
		iunlock(&p->tlock);
		break;
	}
}

static long
ns16552write(Chan *c, void *buf, long n, vlong)
{
	Uart *p;
	char cmd[32];

	if(c->qid.type & QTDIR)
		error(Eperm);

	p = uart[NETID(c->qid.path)];

	/*
	 *  The fifo's turn themselves off sometimes.
	 *  It must be something I don't understand. -- presotto
	 */
	lock(&p->flock);
	if((p->istat & Fenabd) == 0 && p->fifoon && p->nofifo == 0)
		ns16552fifoon(p);
	unlock(&p->flock);

	switch(NETTYPE(c->qid.path)){
	case Ndataqid:
		return qwrite(p->oq, buf, n);
	case Nctlqid:
		if(n >= sizeof(cmd))
			n = sizeof(cmd)-1;
		memmove(cmd, buf, n);
		cmd[n] = 0;
		ns16552ctl(p, cmd);
		return n;
	}
}

static int
ns16552wstat(Chan *c, uchar *dp, int n)
{
	Dir d;
	Dirtab *dt;

	if(!iseve())
		error(Eperm);
	if(c->qid.type & QTDIR)
		error(Eperm);
	if(NETTYPE(c->qid.path) == Nstatqid)
		error(Eperm);

	dt = &ns16552dir[1+3 * NETID(c->qid.path)];
	n = convM2D(dp, n, &d, nil);
	if(n == 0)
		error(Eshortstat);
	if(d.mode != ~0UL){
		d.mode &= 0666;
		dt[0].perm = dt[1].perm = d.mode;
	}
	return n;
}

Dev ns16552devtab = {
	't',
	"ns16552",

	ns16552reset,
	devinit,
	devshutdown,
	ns16552attach,
	ns16552walk,
	ns16552stat,
	ns16552open,
	devcreate,
	ns16552close,
	ns16552read,
	devbread,
	ns16552write,
	devbwrite,
	devremove,
	ns16552wstat,
};

void
uartputc(int c)
{
	Uart *p;
	int i;

	p = consuart;
	if(p == nil)
		return;
	for(i = 0; !(uartrdreg(p, Lstat)&Outready) && i < 128; i++)
		delay(1);
	outb(p->port+Data, c);
	for(i = 0; !(uartrdreg(p, Lstat)&Outready) && i < 128; i++)
		delay(1);
}

void
uartputs(char *s, int n)
{
	char *e;

	if(consuart == nil)
		return;
	e = s+n;
	for(; s < e; s++){
		if(*s == '\n')
			uartputc('\r');
		uartputc(*s);
	}
}