ref: 94443daf8e248e65afc8d3f17f26efea22748b51
dir: /os/pxa/devuart.c/
#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 uart.
* TO DO: replace by uartpxa.c
*/
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 */
Rtoie= 1<<4, /* character timeout indication */
Nrze= 1<<5, /* NRZ encoding enabled */
Uue= 1<<6, /* Uart unit enabled */
Dmae= 1<<7, /* DMA requests enabled */
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 */
Fdma= (1<<3), /* dma on */
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 */
Ringl= (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= 4, /* max per machine */
};
typedef struct Uart Uart;
struct Uart
{
QLock;
int opens;
int enabled;
Uart *elist; /* next enabled interface */
char name[KNAMELEN];
ulong sticky[8]; /* sticky write register values */
void* regs;
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;
struct Uartalloc {
Lock;
Uart *elist; /* list of enabled interfaces */
} uartalloc;
static void uartintr(Uart*);
/*
* pick up architecture specific routines and definitions
*/
#include "uart.h"
/*
* set the baud rate by calculating and setting the baudrate
* generator constant. This will work with fairly non-standard
* baud rates.
*/
static void
uartsetbaud(Uart *p, int rate)
{
ulong brconst;
if(rate <= 0)
return;
p->freq = archuartclock(p->port, rate);
if(p->freq == 0)
return;
brconst = (p->freq+8*rate-1)/(16*rate);
uartwrreg(p, Format, Dra);
uartwr(p, Dmsb, (brconst>>8) & 0xff);
uartwr(p, Dlsb, brconst & 0xff);
uartwrreg(p, Format, 0);
p->baud = rate;
}
/*
* decide if we should hangup when dsr or dcd drops.
*/
static void
uartdsrhup(Uart *p, int n)
{
p->hup_dsr = n;
}
static void
uartdcdhup(Uart *p, int n)
{
p->hup_dcd = n;
}
static void
uartparity(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
uartbits(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
uartdtr(Uart *p, int n)
{
if(n)
p->sticky[Mctl] |= Dtr;
else
p->sticky[Mctl] &= ~Dtr;
uartwrreg(p, Mctl, 0);
}
/*
* toggle RTS
*/
void
uartrts(Uart *p, int n)
{
if(n)
p->sticky[Mctl] |= Rts;
else
p->sticky[Mctl] &= ~Rts;
uartwrreg(p, Mctl, 0);
}
/*
* send break
*/
static void
uartbreak(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
uartfifoon(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
uartmflow(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 */
// uartfifoon(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
uartenable(Uart *p)
{
Uart **l;
if(p->enabled)
return;
uartportpower(p, 1);
p->hup_dsr = p->hup_dcd = 0;
p->cts = p->dsr = p->dcd = 0;
/*
* turn on interrupts
*/
p->sticky[Iena] = Ircv | Ixmt | Irstat | Uue;
uartwrreg(p, Iena, 0);
/*
* turn on DTR and RTS
*/
uartdtr(p, 1);
uartrts(p, 1);
uartfifoon(p);
/*
* assume we can send
*/
ilock(&p->tlock);
p->cts = 1;
p->blocked = 0;
iunlock(&p->tlock);
/*
* set baud rate to the last used
*/
uartsetbaud(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
uartdisable(Uart *p)
{
Uart **l;
/*
* turn off interrupts
*/
p->sticky[Iena] = Uue;
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
*/
uartdtr(p, 0);
uartrts(p, 0);
uartmflow(p, 0);
ilock(&p->tlock);
p->xonoff = p->blocked = 0;
iunlock(&p->tlock);
uartportpower(p, 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
uartkick0(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;
uartwr(p, Data, *p->op++);
}
}
static void
uartkick(void *v)
{
Uart *p;
p = v;
ilock(&p->tlock);
uartkick0(p);
iunlock(&p->tlock);
}
/*
* restart input if it's off
*/
static void
uartflow(void *v)
{
Uart *p;
p = v;
if(p->modem)
uartrts(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
uartsetup0(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);
// uartsetbaud(p, 9600);
uartsetbaud(p, 38400);
p->iq = qopen(4*1024, 0, uartflow, p);
p->oq = qopen(4*1024, 0, uartkick, p);
if(p->iq == nil || p->oq == nil)
panic("uartsetup0");
p->ip = p->istage;
p->ie = &p->istage[Stagesize];
p->op = p->ostage;
p->oe = p->ostage;
}
/*
* called by uartinstall() to create a new duart
*/
void
uartsetup(ulong port, void *regs, 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->regs = regs;
p->freq = freq;
uartsetup0(p);
}
/*
* called by main() to configure a duart port as a console or a mouse
*/
void
uartspecial(int port, int baud, Queue **in, Queue **out, int (*putc)(Queue*, int))
{
Uart *p = uart[port];
uartenable(p);
if(baud)
uartsetbaud(p, baud);
p->putc = putc;
if(in)
*in = p->iq;
if(out)
*out = p->oq;
p->opens++;
}
/*
* handle an interrupt to a single uart
*/
static void
uartintr(Uart *p)
{
uchar ch;
int s, l;
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 */
uartkick(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:
iprint("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)
uartrts(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)
uartkick0(p);
}
iunlock(&p->tlock);
}
}
}
Dirtab *uartdir;
int ndir;
static void
setlength(int i)
{
Uart *p;
if(i >= 0){
p = uart[i];
if(p && p->opens && p->iq)
uartdir[1+3*i].length = qlen(p->iq);
} else for(i = 0; i < nuart; i++){
p = uart[i];
if(p && p->opens && p->iq)
uartdir[1+3*i].length = qlen(p->iq);
}
}
/*
* all uarts must be uartsetup() by this point or inside of uartinstall()
*/
static void
uartreset(void)
{
int i;
Dirtab *dp;
uartinstall(); /* architecture specific */
ndir = 1+3*nuart;
uartdir = xalloc(ndir * sizeof(Dirtab));
dp = uartdir;
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*
uartattach(char *spec)
{
return devattach('t', spec);
}
static Walkqid*
uartwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, uartdir, ndir, devgen);
}
static int
uartstat(Chan *c, uchar *dp, int n)
{
if(NETTYPE(c->qid.path) == Ndataqid)
setlength(NETID(c->qid.path));
return devstat(c, dp, n, uartdir, ndir, devgen);
}
static Chan*
uartopen(Chan *c, int omode)
{
Uart *p;
c = devopen(c, omode, uartdir, ndir, devgen);
switch(NETTYPE(c->qid.path)){
case Nctlqid:
case Ndataqid:
p = uart[NETID(c->qid.path)];
qlock(p);
if(p->opens++ == 0){
uartenable(p);
qreopen(p->iq);
qreopen(p->oq);
}
qunlock(p);
break;
}
return c;
}
static void
uartclose(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){
uartdisable(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 & Ringl) ? " ring" : ""
);
return readstr(offset, buf, n, str);
}
static long
uartread(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, uartdir, 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
uartctl(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){
uartbreak(p, 0);
return;
}
n = atoi(cmd+1);
switch(*cmd){
case 'B':
case 'b':
uartsetbaud(p, n);
break;
case 'C':
case 'c':
uartdcdhup(p, n);
break;
case 'D':
case 'd':
uartdtr(p, n);
break;
case 'E':
case 'e':
uartdsrhup(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':
uartbits(p, n);
break;
case 'm':
case 'M':
uartmflow(p, n);
break;
case 'n':
case 'N':
qnoblock(p->oq, n);
break;
case 'P':
case 'p':
uartparity(p, *(cmd+1));
break;
case 'K':
case 'k':
uartbreak(p, n);
break;
case 'R':
case 'r':
uartrts(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
uartwrite(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)
uartfifoon(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;
uartctl(p, cmd);
return n;
default:
error(Egreg);
return n;
}
}
static int
uartwstat(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 = &uartdir[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 uartdevtab = {
't',
"uart",
uartreset,
devinit,
devshutdown,
uartattach,
uartwalk,
uartstat,
uartopen,
devcreate,
uartclose,
uartread,
devbread,
uartwrite,
devbwrite,
devremove,
uartwstat,
};