summaryrefslogtreecommitdiff
path: root/os/sa1110/devuart.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
commit74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch)
treec6e220ba61db3a6ea4052e6841296d829654e664 /os/sa1110/devuart.c
parent46439007cf417cbd9ac8049bb4122c890097a0fa (diff)
20060303
Diffstat (limited to 'os/sa1110/devuart.c')
-rw-r--r--os/sa1110/devuart.c781
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;
+}