ref: b0ad72be22c9125e674d53bc7f9c086cd7c1ac83
dir: /sys/src/9/pc/uartaxp.c/
/* * Avanstar Xp pci uart driver */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/pci.h" #include "../port/error.h" #include "uartaxp.i" typedef struct Cc Cc; typedef struct Ccb Ccb; typedef struct Ctlr Ctlr; typedef struct Gcb Gcb; /* * Global Control Block. * Service Request fields must be accessed using XCHG. */ struct Gcb { u16int gcw; /* Global Command Word */ u16int gsw; /* Global Status Word */ u16int gsr; /* Global Service Request */ u16int abs; /* Available Buffer Space */ u16int bt; /* Board Type */ u16int cpv; /* Control Program Version */ u16int ccbn; /* Ccb count */ u16int ccboff; /* Ccb offset */ u16int ccbsz; /* Ccb size */ u16int gcw2; /* Global Command Word 2 */ u16int gsw2; /* Global Status Word 2 */ u16int esr; /* Error Service Request */ u16int isr; /* Input Service Request */ u16int osr; /* Output Service Request */ u16int msr; /* Modem Service Request */ u16int csr; /* Command Service Request */ }; /* * Channel Control Block. */ struct Ccb { u16int br; /* Baud Rate */ u16int df; /* Data Format */ u16int lp; /* Line Protocol */ u16int ibs; /* Input Buffer Size */ u16int obs; /* Output Buffer Size */ u16int ibtr; /* Ib Trigger Rate */ u16int oblw; /* Ob Low Watermark */ u8int ixon[2]; /* IXON characters */ u16int ibhw; /* Ib High Watermark */ u16int iblw; /* Ib Low Watermark */ u16int cc; /* Channel Command */ u16int cs; /* Channel Status */ u16int ibsa; /* Ib Start Addr */ u16int ibea; /* Ib Ending Addr */ u16int obsa; /* Ob Start Addr */ u16int obea; /* Ob Ending Addr */ u16int ibwp; /* Ib write pointer (RO) */ u16int ibrp; /* Ib read pointer (R/W) */ u16int obwp; /* Ob write pointer (R/W) */ u16int obrp; /* Ob read pointer (RO) */ u16int ces; /* Communication Error Status */ u16int bcp; /* Bad Character Pointer */ u16int mc; /* Modem Control */ u16int ms; /* Modem Status */ u16int bs; /* Blocking Status */ u16int crf; /* Character Received Flag */ u8int ixoff[2]; /* IXOFF characters */ u16int cs2; /* Channel Status 2 */ u8int sec[2]; /* Strip/Error Characters */ }; enum { /* br */ Br76800 = 0xFF00, Br115200 = 0xFF01, }; enum { /* df */ Db5 = 0x0000, /* Data Bits - 5 bits/byte */ Db6 = 0x0001, /* 6 bits/byte */ Db7 = 0x0002, /* 7 bits/byte */ Db8 = 0x0003, /* 8 bits/byte */ DbMASK = 0x0003, Sb1 = 0x0000, /* 1 Stop Bit */ Sb2 = 0x0004, /* 2 Stop Bit */ SbMASK = 0x0004, Np = 0x0000, /* No Parity */ Op = 0x0008, /* Odd Parity */ Ep = 0x0010, /* Even Parity */ Mp = 0x0020, /* Mark Parity */ Sp = 0x0030, /* Space Parity */ PMASK = 0x0038, Cmn = 0x0000, /* Channel Mode Normal */ Cme = 0x0040, /* CM Echo */ Cmll = 0x0080, /* CM Local Loopback */ Cmrl = 0x00C0, /* CM Remote Loopback */ }; enum { /* lp */ Ixon = 0x0001, /* Obey IXON/IXOFF */ Ixany = 0x0002, /* Any character retarts Tx */ Ixgen = 0x0004, /* Generate IXON/IXOFF */ Cts = 0x0008, /* CTS controls Tx */ Dtr = 0x0010, /* Rx controls DTR */ ½d = 0x0020, /* RTS off during Tx */ Rts = 0x0040, /* generate RTS */ Emcs = 0x0080, /* Enable Modem Control */ Ecs = 0x1000, /* Enable Character Stripping */ Eia422 = 0x2000, /* EIA422 */ }; enum { /* cc */ Ccu = 0x0001, /* Configure Channel and UART */ Cco = 0x0002, /* Configure Channel Only */ Fib = 0x0004, /* Flush Input Buffer */ Fob = 0x0008, /* Flush Output Buffer */ Er = 0x0010, /* Enable Receiver */ Dr = 0x0020, /* Disable Receiver */ Et = 0x0040, /* Enable Transmitter */ Dt = 0x0080, /* Disable Transmitter */ }; enum { /* ces */ Oe = 0x0001, /* Overrun Error */ Pe = 0x0002, /* Parity Error */ Fe = 0x0004, /* Framing Error */ Br = 0x0008, /* Break Received */ }; enum { /* mc */ Adtr = 0x0001, /* Assert DTR */ Arts = 0x0002, /* Assert RTS */ Ab = 0x0010, /* Assert BREAK */ }; enum { /* ms */ Scts = 0x0001, /* Status od CTS */ Sdsr = 0x0002, /* Status of DSR */ Sri = 0x0004, /* Status of RI */ Sdcd = 0x0008, /* Status of DCD */ }; enum { /* bs */ Rd = 0x0001, /* Receiver Disabled */ Td = 0x0002, /* Transmitter Disabled */ Tbxoff = 0x0004, /* Tx Blocked by XOFF */ Tbcts = 0x0008, /* Tx Blocked by CTS */ Rbxoff = 0x0010, /* Rx Blocked by XOFF */ Rbrts = 0x0020, /* Rx Blocked by RTS */ }; enum { /* Local Configuration */ Range = 0x00, Remap = 0x04, Region = 0x18, Mb0 = 0x40, /* Mailbox 0 */ Ldb = 0x60, /* PCI to Local Doorbell */ Pdb = 0x64, /* Local to PCI Doorbell */ Ics = 0x68, /* Interrupt Control/Status */ Mcc = 0x6C, /* Misc. Command and Control */ }; enum { /* Mb0 */ Edcc = 1, /* exec. downloaded code cmd */ Aic = 0x10, /* adapter init'zed correctly */ Cpr = 1ul << 31, /* control program ready */ }; enum { /* Mcc */ Rcr = 1ul << 29, /* reload config. reg.s */ Asr = 1ul << 30, /* pci adapter sw reset */ Lis = 1ul << 31, /* local init status */ }; typedef struct Cc Cc; typedef struct Ccb Ccb; typedef struct Ctlr Ctlr; /* * Channel Control, one per uart. * Devuart communicates via the PhysUart functions with * a Uart* argument. Uart.regs is filled in by this driver * to point to a Cc, and Cc.ctlr points to the Axp board * controller. */ struct Cc { int uartno; Ccb* ccb; Ctlr* ctlr; Rendez; Uart; }; typedef struct Ctlr { char* name; Pcidev* pcidev; int ctlrno; Ctlr* next; u32int* reg; uchar* mem; Gcb* gcb; int im; /* interrupt mask */ Cc cc[16]; } Ctlr; #define csr32r(c, r) (*((c)->reg+((r)/4))) #define csr32w(c, r, v) (*((c)->reg+((r)/4)) = (v)) static Ctlr* axpctlrhead; static Ctlr* axpctlrtail; extern PhysUart axpphysuart; static int axpccdone(void* ccb) { return !((Ccb*)ccb)->cc; /* hw sets ccb->cc to zero */ } static void axpcc(Cc* cc, int cmd) { Ccb *ccb; int timeo; u16int cs; ccb = cc->ccb; ccb->cc = cmd; if(!cc->ctlr->im) for(timeo = 0; timeo < 1000000; timeo++){ if(!ccb->cc) break; microdelay(1); } else tsleep(cc, axpccdone, ccb, 1000); cs = ccb->cs; if(ccb->cc || cs){ print("%s: cmd %#ux didn't terminate: %#ux %#ux\n", cc->name, cmd, ccb->cc, cs); if(cc->ctlr->im) error(Eio); } } static long axpstatus(Uart* uart, void* buf, long n, long offset) { char *p; Ccb *ccb; u16int bs, fstat, ms; ccb = ((Cc*)(uart->regs))->ccb; p = smalloc(READSTR); bs = ccb->bs; fstat = ccb->df; ms = ccb->ms; snprint(p, READSTR, "b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n" "dev(%d) type(%d) framing(%d) overruns(%d) " "berr(%d) serr(%d)%s%s%s%s\n", uart->baud, uart->hup_dcd, ms & Sdsr, uart->hup_dsr, (fstat & DbMASK) + 5, 0, (fstat & PMASK) ? ((fstat & Ep) == Ep? 'e': 'o'): 'n', (bs & Rbrts) ? 1 : 0, (fstat & Sb2) ? 2 : 1, 0, uart->dev, uart->type, uart->ferr, uart->oerr, uart->berr, uart->serr, (ms & Scts) ? " cts" : "", (ms & Sdsr) ? " dsr" : "", (ms & Sdcd) ? " dcd" : "", (ms & Sri) ? " ring" : "" ); n = readstr(offset, buf, n, p); free(p); return n; } static void axpfifo(Uart*, int) { } static void axpdtr(Uart* uart, int on) { Ccb *ccb; u16int mc; ccb = ((Cc*)(uart->regs))->ccb; mc = ccb->mc; if(on) mc |= Adtr; else mc &= ~Adtr; ccb->mc = mc; } /* * can be called from uartstageinput() during an input interrupt, * with uart->rlock ilocked or the uart qlocked, sometimes both. */ static void axprts(Uart* uart, int on) { Ccb *ccb; u16int mc; ccb = ((Cc*)(uart->regs))->ccb; mc = ccb->mc; if(on) mc |= Arts; else mc &= ~Arts; ccb->mc = mc; } static void axpmodemctl(Uart* uart, int on) { Ccb *ccb; u16int lp; ccb = ((Cc*)(uart->regs))->ccb; ilock(&uart->tlock); lp = ccb->lp; if(on){ lp |= Cts|Rts; lp &= ~Emcs; uart->cts = ccb->ms & Scts; } else{ lp &= ~(Cts|Rts); lp |= Emcs; uart->cts = 1; } uart->modem = on; iunlock(&uart->tlock); ccb->lp = lp; axpcc(uart->regs, Ccu); } static int axpparity(Uart* uart, int parity) { Ccb *ccb; u16int df; switch(parity){ default: return -1; case 'e': parity = Ep; break; case 'o': parity = Op; break; case 'n': parity = Np; break; } ccb = ((Cc*)(uart->regs))->ccb; df = ccb->df & ~PMASK; ccb->df = df|parity; axpcc(uart->regs, Ccu); return 0; } static int axpstop(Uart* uart, int stop) { Ccb *ccb; u16int df; switch(stop){ default: return -1; case 1: stop = Sb1; break; case 2: stop = Sb2; break; } ccb = ((Cc*)(uart->regs))->ccb; df = ccb->df & ~SbMASK; ccb->df = df|stop; axpcc(uart->regs, Ccu); return 0; } static int axpbits(Uart* uart, int bits) { Ccb *ccb; u16int df; bits -= 5; if(bits < 0 || bits > 3) return -1; ccb = ((Cc*)(uart->regs))->ccb; df = ccb->df & ~DbMASK; ccb->df = df|bits; axpcc(uart->regs, Ccu); return 0; } static int axpbaud(Uart* uart, int baud) { Ccb *ccb; int i, ibtr; /* * Set baud rate (high rates are special - only 16 bits). */ if(baud <= 0) return -1; uart->baud = baud; ccb = ((Cc*)(uart->regs))->ccb; switch(baud){ default: ccb->br = baud; break; case 76800: ccb->br = Br76800; break; case 115200: ccb->br = Br115200; break; } /* * Set trigger level to about 50 per second. */ ibtr = baud/500; i = (ccb->ibea - ccb->ibsa)/2; if(ibtr > i) ibtr = i; ccb->ibtr = ibtr; axpcc(uart->regs, Ccu); return 0; } static void axpbreak(Uart* uart, int ms) { Ccb *ccb; u16int mc; /* * Send a break. */ if(ms <= 0) ms = 200; ccb = ((Cc*)(uart->regs))->ccb; mc = ccb->mc; ccb->mc = Ab|mc; tsleep(&up->sleep, return0, 0, ms); ccb->mc = mc & ~Ab; } /* only called from interrupt service */ static void axpmc(Cc* cc) { int old; Ccb *ccb; u16int ms; ccb = cc->ccb; ms = ccb->ms; if(ms & Scts){ ilock(&cc->tlock); old = cc->cts; cc->cts = ms & Scts; if(old == 0 && cc->cts) cc->ctsbackoff = 2; iunlock(&cc->tlock); } if(ms & Sdsr){ old = ms & Sdsr; if(cc->hup_dsr && cc->dsr && !old) cc->dohup = 1; cc->dsr = old; } if(ms & Sdcd){ old = ms & Sdcd; if(cc->hup_dcd && cc->dcd && !old) cc->dohup = 1; cc->dcd = old; } } /* called from uartkick() with uart->tlock ilocked */ static void axpkick(Uart* uart) { Cc *cc; Ccb *ccb; uchar *ep, *mem, *rp, *wp, *bp; if(uart->cts == 0 || uart->blocked) return; cc = uart->regs; ccb = cc->ccb; mem = (uchar*)cc->ctlr->gcb; bp = mem + ccb->obsa; rp = mem + ccb->obrp; wp = mem + ccb->obwp; ep = mem + ccb->obea; while(wp != rp-1 && (rp != bp || wp != ep)){ /* * if we've exhausted the uart's output buffer, * ask for more from the output queue, and quit if there * isn't any. */ if(uart->op >= uart->oe && uartstageoutput(uart) == 0) break; *wp++ = *(uart->op++); if(wp > ep) wp = bp; ccb->obwp = wp - mem; } } /* only called from interrupt service */ static void axprecv(Cc* cc) { Ccb *ccb; uchar *ep, *mem, *rp, *wp; ccb = cc->ccb; mem = (uchar*)cc->ctlr->gcb; rp = mem + ccb->ibrp; wp = mem + ccb->ibwp; ep = mem + ccb->ibea; while(rp != wp){ uartrecv(cc, *rp++); /* ilocks cc->tlock */ if(rp > ep) rp = mem + ccb->ibsa; ccb->ibrp = rp - mem; } } static void axpinterrupt(Ureg*, void* arg) { int work; Cc *cc; Ctlr *ctlr; u32int ics; u16int r, sr; work = 0; ctlr = arg; ics = csr32r(ctlr, Ics); if(ics & 0x0810C000) print("%s: unexpected interrupt %#ux\n", ctlr->name, ics); if(!(ics & 0x00002000)) { /* we get a steady stream of these on consoles */ // print("%s: non-doorbell interrupt\n", ctlr->name); ctlr->gcb->gcw2 = 0x0001; /* set Gintack */ return; } // while(work to do){ cc = ctlr->cc; for(sr = xchgw(&ctlr->gcb->isr, 0); sr != 0; sr >>= 1){ if(sr & 0x0001) work++, axprecv(cc); cc++; } cc = ctlr->cc; for(sr = xchgw(&ctlr->gcb->osr, 0); sr != 0; sr >>= 1){ if(sr & 0x0001) work++, uartkick(&cc->Uart); cc++; } cc = ctlr->cc; for(sr = xchgw(&ctlr->gcb->csr, 0); sr != 0; sr >>= 1){ if(sr & 0x0001) work++, wakeup(cc); cc++; } cc = ctlr->cc; for(sr = xchgw(&ctlr->gcb->msr, 0); sr != 0; sr >>= 1){ if(sr & 0x0001) work++, axpmc(cc); cc++; } cc = ctlr->cc; for(sr = xchgw(&ctlr->gcb->esr, 0); sr != 0; sr >>= 1){ if(sr & 0x0001){ r = cc->ccb->ms; if(r & Oe) cc->oerr++; if(r & Pe) cc->perr++; if(r & Fe) cc->ferr++; if (r & (Oe|Pe|Fe)) work++; } cc++; } // } /* only meaningful if we don't share the irq */ if (0 && !work) print("%s: interrupt with no work\n", ctlr->name); csr32w(ctlr, Pdb, 1); /* clear doorbell interrupt */ ctlr->gcb->gcw2 = 0x0001; /* set Gintack */ } static void axpdisable(Uart* uart) { Cc *cc; u16int lp; Ctlr *ctlr; /* * Turn off DTR and RTS, disable interrupts. */ (*uart->phys->dtr)(uart, 0); (*uart->phys->rts)(uart, 0); cc = uart->regs; lp = cc->ccb->lp; cc->ccb->lp = Emcs|lp; axpcc(cc, Dt|Dr|Fob|Fib|Ccu); /* * The Uart is qlocked. */ ctlr = cc->ctlr; ctlr->im &= ~(1<<cc->uartno); if(ctlr->im == 0) intrdisable(ctlr->pcidev->intl, axpinterrupt, ctlr, ctlr->pcidev->tbdf, ctlr->name); } static void axpenable(Uart* uart, int ie) { Cc *cc; Ctlr *ctlr; u16int lp; cc = uart->regs; ctlr = cc->ctlr; /* * 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(ie){ /* * The Uart is qlocked. */ if(ctlr->im == 0){ intrenable(ctlr->pcidev->intl, axpinterrupt, ctlr, ctlr->pcidev->tbdf, ctlr->name); csr32w(ctlr, Ics, 0x00031F00); csr32w(ctlr, Pdb, 1); ctlr->gcb->gcw2 = 1; } ctlr->im |= 1<<cc->uartno; } (*uart->phys->dtr)(uart, 1); (*uart->phys->rts)(uart, 1); /* * Make sure we control RTS, DTR and break. */ lp = cc->ccb->lp; cc->ccb->lp = Emcs|lp; cc->ccb->oblw = 64; axpcc(cc, Et|Er|Ccu); } static void* axpdealloc(Ctlr* ctlr) { int i; for(i = 0; i < 16; i++){ if(ctlr->cc[i].name != nil) free(ctlr->cc[i].name); } if(ctlr->reg != nil) vunmap(ctlr->reg, ctlr->pcidev->mem[0].size); if(ctlr->mem != nil) vunmap(ctlr->mem, ctlr->pcidev->mem[2].size); if(ctlr->name != nil) free(ctlr->name); free(ctlr); return nil; } static Uart* axpalloc(int ctlrno, Pcidev* pcidev) { Cc *cc; uchar *p; Ctlr *ctlr; void *addr; char name[64]; u32int r; int i, n, timeo; uvlong io; ctlr = malloc(sizeof(Ctlr)); if(ctlr == nil){ print("uartaxp: can't allocate memory\n"); return nil; } seprint(name, name+sizeof(name), "uartaxp%d", ctlrno); kstrdup(&ctlr->name, name); ctlr->pcidev = pcidev; ctlr->ctlrno = ctlrno; /* * Access to runtime registers. */ io = pcidev->mem[0].bar & ~0xF; addr = vmap(io, pcidev->mem[0].size); if(addr == nil){ print("%s: can't map registers at %llux\n", ctlr->name, io); return axpdealloc(ctlr); } ctlr->reg = addr; print("%s: port 0x%llux irq %d ", ctlr->name, io, pcidev->intl); /* * Local address space 0. */ io = pcidev->mem[2].bar & ~0xF; addr = vmap(io, pcidev->mem[2].size); if(addr == nil){ print("%s: can't map memory at %llux\n", ctlr->name, io); return axpdealloc(ctlr); } ctlr->mem = addr; ctlr->gcb = (Gcb*)(ctlr->mem+0x10000); print("mem 0x%llux size %d: ", io, pcidev->mem[2].size); pcienable(pcidev); /* * Toggle the software reset and wait for * the adapter local init status to indicate done. * * The two 'delay(100)'s below are important, * without them the board seems to become confused * (perhaps it needs some 'quiet time' because the * timeout loops are not sufficient in themselves). */ r = csr32r(ctlr, Mcc); csr32w(ctlr, Mcc, r|Asr); microdelay(1); csr32w(ctlr, Mcc, r&~Asr); delay(100); for(timeo = 0; timeo < 100000; timeo++){ if(csr32r(ctlr, Mcc) & Lis) break; microdelay(1); } if(!(csr32r(ctlr, Mcc) & Lis)){ print("%s: couldn't reset\n", ctlr->name); return axpdealloc(ctlr); } print("downloading..."); /* * Copy the control programme to the card memory. * The card's i960 control structures live at 0xD000. */ if(sizeof(uartaxpcp) > 0xD000){ print("%s: control programme too big\n", ctlr->name); return axpdealloc(ctlr); } /* TODO: is this right for more than 1 card? devastar does the same */ csr32w(ctlr, Remap, 0xA0000001); for(i = 0; i < sizeof(uartaxpcp); i++) ctlr->mem[i] = uartaxpcp[i]; /* * Execute downloaded code and wait for it * to signal ready. */ csr32w(ctlr, Mb0, Edcc); delay(100); /* the manual says to wait for Cpr for 1 second */ for(timeo = 0; timeo < 10000; timeo++){ if(csr32r(ctlr, Mb0) & Cpr) break; microdelay(100); } if(!(csr32r(ctlr, Mb0) & Cpr)){ print("control programme not ready; Mb0 %#ux\n", csr32r(ctlr, Mb0)); print("%s: distribution panel not connected or card not fully seated?\n", ctlr->name); return axpdealloc(ctlr); } print("\n"); n = ctlr->gcb->ccbn; if(ctlr->gcb->bt != 0x12 || n > 16){ print("%s: wrong board type %#ux, %d channels\n", ctlr->name, ctlr->gcb->bt, ctlr->gcb->ccbn); return axpdealloc(ctlr); } p = ((uchar*)ctlr->gcb) + ctlr->gcb->ccboff; for(i = 0; i < n; i++){ cc = &ctlr->cc[i]; cc->ccb = (Ccb*)p; p += ctlr->gcb->ccbsz; cc->uartno = i; cc->ctlr = ctlr; cc->regs = cc; /* actually Uart->regs */ seprint(name, name+sizeof(name), "uartaxp%d%2.2d", ctlrno, i); kstrdup(&cc->name, name); cc->freq = 0; cc->bits = 8; cc->stop = 1; cc->parity = 'n'; cc->baud = 9600; cc->phys = &axpphysuart; cc->console = 0; cc->special = 0; cc->next = &ctlr->cc[i+1]; } ctlr->cc[n-1].next = nil; ctlr->next = nil; if(axpctlrhead != nil) axpctlrtail->next = ctlr; else axpctlrhead = ctlr; axpctlrtail = ctlr; return ctlr->cc; } static Uart* axppnp(void) { Pcidev *p; int ctlrno; Uart *head, *tail, *uart; /* * Loop through all PCI devices looking for simple serial * controllers (ccrb == 0x07) and configure the ones which * are familiar. */ head = tail = nil; ctlrno = 0; for(p = pcimatch(nil, 0, 0); p != nil; p = pcimatch(p, 0, 0)){ if(p->ccrb != 0x07) continue; if((p->mem[0].bar & 1) != 0 || (p->mem[2].bar & 1) != 0) continue; switch((p->did<<16)|p->vid){ default: continue; case (0x6001<<16)|0x114F: /* AvanstarXp */ if((uart = axpalloc(ctlrno, p)) == nil) continue; break; } if(head != nil) tail->next = uart; else head = uart; for(tail = uart; tail->next != nil; tail = tail->next) ; ctlrno++; } return head; } PhysUart axpphysuart = { .name = "AvanstarXp", .pnp = axppnp, .enable = axpenable, .disable = axpdisable, .kick = axpkick, .dobreak = axpbreak, .baud = axpbaud, .bits = axpbits, .stop = axpstop, .parity = axpparity, .modemctl = axpmodemctl, .rts = axprts, .dtr = axpdtr, .status = axpstatus, .fifo = axpfifo, .getc = nil, .putc = nil, };