diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
| commit | 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch) | |
| tree | c6e220ba61db3a6ea4052e6841296d829654e664 /os/sa1110/devuart.c | |
| parent | 46439007cf417cbd9ac8049bb4122c890097a0fa (diff) | |
20060303
Diffstat (limited to 'os/sa1110/devuart.c')
| -rw-r--r-- | os/sa1110/devuart.c | 781 |
1 files changed, 781 insertions, 0 deletions
diff --git a/os/sa1110/devuart.c b/os/sa1110/devuart.c new file mode 100644 index 00000000..65eb17a3 --- /dev/null +++ b/os/sa1110/devuart.c @@ -0,0 +1,781 @@ +#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" + +/* + * currently no DMA or flow control (hardware or software) + */ + +enum +{ + Stagesize= 1024, + Dmabufsize=Stagesize/2, + Nuart=7, /* max per machine */ + + CTLS= 023, + CTLQ= 021, +}; + +typedef struct Uart Uart; +struct Uart +{ + QLock; + + int opens; + + int enabled; + + int frame; /* framing errors */ + int overrun; /* rcvr overruns */ + int soverrun; /* software overruns */ + int perror; /* parity error */ + int bps; /* baud rate */ + uchar bits; + char parity; + + int inters; /* total interrupt count */ + int rinters; /* interrupts due to read */ + int winters; /* interrupts due to write */ + + int rcount; /* total read count */ + int wcount; /* total output count */ + + int xonoff; /* software flow control on */ + int blocked; /* output blocked */ + + /* buffers */ + int (*putc)(Queue*, int); + Queue *iq; + Queue *oq; + + int port; + UartReg *reg; + + /* staging areas to avoid some of the per character costs */ + uchar *ip; + uchar *ie; + uchar *op; + uchar *oe; + + /* put large buffers last to aid register-offset optimizations: */ + char name[KNAMELEN]; + uchar istage[Stagesize]; + uchar ostage[Stagesize]; +}; + +enum { + UTCR0_PE= 0x01, + UTCR0_OES= 0x02, + UTCR0_SBS= 0x04, + UTCR0_DSS= 0x08, + UTCR0_SCE= 0x10, + UTCR0_RCE= 0x20, + UTCR0_TCE= 0x40, + + UTCR3_RXE= 0x01, + UTCR3_TXE= 0x02, + UTCR3_BRK= 0x04, + UTCR3_RIM= 0x08, + UTCR3_TIM= 0x10, + UTCR3_LBM= 0x20, + + UTSR0_TFS= 0x01, + UTSR0_RFS= 0x02, + UTSR0_RID= 0x04, + UTSR0_RBB= 0x08, + UTSR0_REB= 0x10, + UTSR0_EIF= 0x20, + + UTSR1_TBY= 0x01, + UTSR1_RNE= 0x02, + UTSR1_TNF= 0x04, + UTSR1_PRE= 0x08, + UTSR1_FRE= 0x10, + UTSR1_ROR= 0x20, +}; + +static Uart *uart[Nuart]; +static int nuart; +static int uartspcl; +int redirectconsole; + +static void +uartset(Uart *p) +{ + UartReg *reg = p->reg; + ulong ocr3; + ulong brdiv; + int n; + + brdiv = CLOCKFREQ/16/p->bps - 1; + ocr3 = reg->utcr3; + reg->utcr3 = ocr3&~(UTCR3_RXE|UTCR3_TXE); + reg->utcr1 = brdiv >> 8; + reg->utcr2 = brdiv & 0xff; + /* set PE and OES appropriately for o/e/n: */ + reg->utcr0 = ((p->parity&3)^UTCR0_OES)|(p->bits&UTCR0_DSS); + reg->utcr3 = ocr3; + + /* set buffer length according to speed, to allow + * at most a 200ms delay before dumping the staging buffer + * into the input queue + */ + n = p->bps/(10*1000/200); + p->ie = &p->istage[n < Stagesize ? n : Stagesize]; +} + +/* + * send break + */ +static void +uartbreak(Uart *p, int ms) +{ + UartReg *reg = p->reg; + if(ms == 0) + ms = 200; + reg->utcr3 |= UTCR3_BRK; + tsleep(&up->sleep, return0, 0, ms); + reg->utcr3 &= ~UTCR3_BRK; +} + +/* + * turn on a port + */ +static void +uartenable(Uart *p) +{ + UartReg *reg = p->reg; + + if(p->enabled) + return; + + archuartpower(p->port, 1); + uartset(p); + reg->utsr0 = 0xff; // clear all sticky status bits + // enable receive, transmit, and receive interrupt: + reg->utcr3 = UTCR3_RXE|UTCR3_TXE|UTCR3_RIM; + p->blocked = 0; + p->xonoff = 0; + p->enabled = 1; +} + +/* + * turn off a port + */ +static void +uartdisable(Uart *p) +{ + p->reg->utcr3 = 0; // disable TX, RX, and ints + p->blocked = 0; + p->xonoff = 0; + p->enabled = 0; + archuartpower(p->port, 0); +} + +/* + * put some bytes into the local queue to avoid calling + * qconsume for every character + */ +static int +stageoutput(Uart *p) +{ + int n; + Queue *q = p->oq; + + if(q == nil) + return 0; + n = qconsume(q, p->ostage, Stagesize); + if(n <= 0) + return 0; + p->op = p->ostage; + p->oe = p->ostage + n; + return n; +} + +static void +uartxmit(Uart *p) +{ + UartReg *reg = p->reg; + ulong e = 0; + + if(!p->blocked) { + while(p->op < p->oe || stageoutput(p)) { + if(reg->utsr1 & UTSR1_TNF) { + reg->utdr = *(p->op++); + p->wcount++; + } else { + e = UTCR3_TIM; + break; + } + } + } + reg->utcr3 = (reg->utcr3&~UTCR3_TIM)|e; +} + +static void +uartrecvq(Uart *p) +{ + uchar *cp = p->istage; + int n = p->ip - cp; + + if(n == 0) + return; + if(p->putc) + while(n-- > 0) + p->putc(p->iq, *cp++); + else if(p->iq) + if(qproduce(p->iq, p->istage, n) < n){ + /* if xonoff, should send XOFF when qwindow(p->iq) < threshold */ + p->soverrun++; + //print("qproduce flow control"); + } + p->ip = p->istage; +} + +static void +uartrecv(Uart *p) +{ + UartReg *reg = p->reg; + ulong n; + while(reg->utsr1 & UTSR1_RNE) { + int c; + n = reg->utsr1; + c = reg->utdr; + if(n & (UTSR1_PRE|UTSR1_FRE|UTSR1_ROR)) { + if(n & UTSR1_PRE) + p->perror++; + if(n & UTSR1_FRE) + p->frame++; + if(n & UTSR1_ROR) + p->overrun++; + continue; + } + if(p->xonoff){ + if(c == CTLS){ + p->blocked = 1; + }else if (c == CTLQ){ + p->blocked = 0; + } + } + *p->ip++ = c; + if(p->ip >= p->ie) + uartrecvq(p); + p->rcount++; + } + if(reg->utsr0 & UTSR0_RID) { + reg->utsr0 = UTSR0_RID; + uartrecvq(p); + } +} + +static void +uartclock(void) +{ + Uart *p; + int i; + + for(i=0; i<nuart; i++){ + p = uart[i]; + if(p != nil) + uartrecvq(p); + } +} + +static void +uartkick(void *a) +{ + Uart *p = a; + int x; + + x = splhi(); + uartxmit(p); + splx(x); +} + +/* + * UART Interrupt Handler + */ +static void +uartintr(Ureg*, void* arg) +{ + Uart *p = arg; + UartReg *reg = p->reg; + ulong m = reg->utsr0; + int dokick; + + dokick = p->blocked; + p->inters++; + if(m & (UTSR0_RFS|UTSR0_RID|UTSR0_EIF)) { + p->rinters++; + uartrecv(p); + } + if(p->blocked) + dokick = 0; + if((m & UTSR0_TFS) && (reg->utcr3&UTCR3_TIM || dokick)) { + p->winters++; + uartxmit(p); + } + + if(m & (UTSR0_RBB|UTSR0_REB)) { + //print("<BREAK>"); + /* reg->utsr0 = UTSR0_RBB|UTSR0_REB; */ + reg->utsr0 = m & (UTSR0_RBB|UTSR0_REB); + /* what to do? if anything */ + } +} + +static void +uartsetup(ulong port, char *name) +{ + Uart *p; + + if(nuart >= Nuart) + return; + + p = xalloc(sizeof(Uart)); + uart[nuart++] = p; + strcpy(p->name, name); + + p->port = port; + p->reg = UARTREG(port); + p->bps = 9600; + p->bits = 8; + p->parity = 'n'; + + p->iq = qopen(4*1024, 0, 0 , p); + p->oq = qopen(4*1024, 0, uartkick, p); + + p->ip = p->istage; + p->ie = &p->istage[Stagesize]; + p->op = p->ostage; + p->oe = p->ostage; + if(port == 1) + GPCLKREG->gpclkr0 |= 1; /* SUS=1 for uart on serial 1 */ + + intrenable(UARTbit(port), uartintr, p, BusCPU, name); +} + +static void +uartinstall(void) +{ + static int already; + + if(already) + return; + already = 1; + + uartsetup(3, "eia0"); + uartsetup(1, "eia1"); + addclock0link(uartclock, 22); +} + +/* + * called by main() to configure a duart port as a console or a mouse + */ +void +uartspecial(int port, int bps, char parity, Queue **in, Queue **out, int (*putc)(Queue*, int)) +{ + Uart *p; + + uartinstall(); + if(port >= nuart) + return; + p = uart[port]; + if(bps) + p->bps = bps; + if(parity) + p->parity = parity; + uartenable(p); + p->putc = putc; + if(in) + *in = p->iq; + if(out) + *out = p->oq; + p->opens++; + uartspcl = 1; +} + +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(); + + 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 = 0660; + dp++; + sprint(dp->name, "%sctl", uart[i]->name); + dp->qid.path = NETQID(i, Nctlqid); + dp->perm = 0660; + 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; + } + qunlock(p); + break; + } +} + +static long +uartstatus(Chan *c, Uart *p, void *buf, long n, long offset) +{ + char str[256]; + USED(c); + + str[0] = 0; + snprint(str, sizeof(str), + "b%d l%d p%c s%d x%d\n" + "opens %d ferr %d oerr %d perr %d baud %d parity %c" + " intr %d rintr %d wintr %d" + " rcount %d wcount %d", + p->bps, p->bits, p->parity, (p->reg->utcr0&UTCR0_SBS)?2:1, p->xonoff, + p->opens, p->frame, p->overrun+p->soverrun, p->perror, p->bps, p->parity, + p->inters, p->rinters, p->winters, + p->rcount, p->wcount); + + strcat(str, "\n"); + return readstr(offset, buf, n, str); +} + +static long +uartread(Chan *c, void *buf, long n, vlong offset) +{ + Uart *p; + + 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 (up to 4 secs) */ + for(i = 0; i < 200 && (qlen(p->oq) || p->reg->utsr1 & UTSR1_TBY); i++) + tsleep(&up->sleep, return0, 0, 20); + + if(strncmp(cmd, "break", 5) == 0){ + uartbreak(p, 0); + return; + } + + n = atoi(cmd+1); + switch(*cmd){ + case 'B': + case 'b': + if(n <= 0) + error(Ebadarg); + p->bps = n; + uartset(p); + 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': + if(n < 7 || n > 8) + error(Ebadarg); + p->bits = n; + uartset(p); + break; + case 'n': + case 'N': + qnoblock(p->oq, n); + break; + case 'P': + case 'p': + p->parity = *(cmd+1); + uartset(p); + break; + case 'K': + case 'k': + uartbreak(p, n); + break; + case 'Q': + case 'q': + qsetlimit(p->iq, n); + qsetlimit(p->oq, n); + break; + case 'X': + case 'x': + p->xonoff = n; + break; + } +} + +static long +uartwrite(Chan *c, void *buf, long n, vlong offset) +{ + Uart *p; + char cmd[32]; + + USED(offset); + + if(c->qid.type & QTDIR) + error(Eperm); + + p = uart[NETID(c->qid.path)]; + + 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; + } +} + +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(d.mode != ~0UL){ + d.mode &= 0666; + dt[0].perm = dt[1].perm = d.mode; + } + return n; +} + +void +uartpower(int on) +{ + Uart *p; + int i; + + for(i=0; i<nuart; i++){ + p = uart[i]; + if(p != nil && p->opens){ + if(on && !p->enabled){ + p->enabled = 0; + uartenable(p); + uartkick(p); + }else{ + if(p->port != 3) /* leave the console */ + uartdisable(p); + p->enabled = 0; + } + } + } +} + +Dev uartdevtab = { + 't', + "uart", + + uartreset, + devinit, + devshutdown, + uartattach, + uartwalk, + uartstat, + uartopen, + devcreate, + uartclose, + uartread, + devbread, + uartwrite, + devbwrite, + devremove, + uartwstat, + uartpower, +}; + +/* + * for use by iprint + */ +void +uartputc(int c) +{ + UartReg *r; + + if(!uartspcl && !redirectconsole) + return; + if(c == 0) + return; + r = UARTREG(3); + while((r->utsr1 & UTSR1_TNF) == 0) + {} + r->utdr = c; + if(c == '\n') + while(r->utsr1 & UTSR1_TBY) /* flush xmit fifo */ + {} +} + +void +uartputs(char *data, int len) +{ + int s; + + if(!uartspcl && !redirectconsole) + return; + clockpoll(); + s = splfhi(); + while(--len >= 0){ + if(*data == '\n') + uartputc('\r'); + uartputc(*data++); + } + splx(s); +} + +/* + * for use by debugger + */ +int +uartgetc(void) +{ + UartReg *r; + + if(!uartspcl) + return -1; + clockcheck(); + r = UARTREG(3); + while(!(r->utsr1 & UTSR1_RNE)) + clockcheck(); + return r->utdr; +} |
