/* * USB host driver for BCM2835 * Synopsis DesignWare Core USB 2.0 OTG controller * * Copyright © 2012 Richard Miller * * This is work in progress: * - no isochronous pipes * - no bandwidth budgeting * - frame scheduling is crude * - error handling is overly optimistic * It should be just about adequate for a Plan 9 terminal with * keyboard, mouse, ethernet adapter, and an external flash drive. */ #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/usb.h" #include "dwcotg.h" enum { USBREGS = VIRTIO + 0x980000, Enabledelay = 50, Resetdelay = 10, ResetdelayHS = 50, Read = 0, Write = 1, }; typedef struct Ctlr Ctlr; typedef struct Epio Epio; struct Ctlr { Dwcregs *regs; /* controller registers */ int nchan; /* number of host channels */ ulong chanbusy; /* bitmap of in-use channels */ QLock chanlock; /* serialise access to chanbusy */ QLock split; /* serialise split transactions */ int splitretry; /* count retries of Nyet */ int sofchan; /* bitmap of channels waiting for sof */ int wakechan; /* bitmap of channels to wakeup after fiq */ int debugchan; /* bitmap of channels for interrupt debug */ Rendez *chanintr; /* sleep till interrupt on channel N */ }; struct Epio { QLock; Block *cb; ulong lastpoll; }; static Ctlr dwc; static int debug; static char Ebadlen[] = "bad usb request length"; static void clog(Ep *ep, Hostchan *hc); static void logdump(Ep *ep); static Hostchan* chanalloc(Ep *ep) { Ctlr *ctlr; int bitmap, i; ctlr = ep->hp->aux; qlock(&ctlr->chanlock); bitmap = ctlr->chanbusy; for(i = 0; i < ctlr->nchan; i++) if((bitmap & (1<chanbusy = bitmap | 1<chanlock); return &ctlr->regs->hchan[i]; } qunlock(&ctlr->chanlock); panic("miller is a lazy git"); return nil; } static void chanrelease(Ep *ep, Hostchan *chan) { Ctlr *ctlr; int i; ctlr = ep->hp->aux; i = chan - ctlr->regs->hchan; qlock(&ctlr->chanlock); ctlr->chanbusy &= ~(1<chanlock); } static void chansetup(Hostchan *hc, Ep *ep) { int hcc; Ctlr *ctlr = ep->hp->aux; if(ep->debug) ctlr->debugchan |= 1 << (hc - ctlr->regs->hchan); else ctlr->debugchan &= ~(1 << (hc - ctlr->regs->hchan)); switch(ep->dev->state){ case Dconfig: case Dreset: hcc = 0; break; default: hcc = ep->dev->nb<maxpkt | 1<nb<ttype){ case Tctl: hcc |= Epctl; break; case Tiso: hcc |= Episo; break; case Tbulk: hcc |= Epbulk; break; case Tintr: hcc |= Epintr; break; } switch(ep->dev->speed){ case Lowspeed: hcc |= Lspddev; /* fall through */ case Fullspeed: if(ep->dev->hub > 1){ hc->hcsplt = Spltena | POS_ALL | ep->dev->hub<dev->port; break; } /* fall through */ default: hc->hcsplt = 0; break; } hc->hcchar = hcc; hc->hcint = ~0; } static int sofdone(void *a) { Dwcregs *r; r = a; return r->gintsts & Sofintr; } static void sofwait(Ctlr *ctlr, int n) { Dwcregs *r; int x; r = ctlr->regs; do{ r->gintsts = Sofintr; x = splfhi(); ctlr->sofchan |= 1<gintmsk |= Sofintr; sleep(&ctlr->chanintr[n], sofdone, r); splx(x); }while((r->hfnum & 7) == 6); } static int chandone(void *a) { Hostchan *hc; hc = a; if(hc->hcint == (Chhltd|Ack)) return 0; return (hc->hcint & hc->hcintmsk) != 0; } static int chanwait(Ep *ep, Ctlr *ctlr, Hostchan *hc, int mask) { int intr, n, x, ointr; ulong start, now; Dwcregs *r; r = ctlr->regs; n = hc - r->hchan; for(;;){ restart: x = splfhi(); r->haintmsk |= 1<hcintmsk = mask; sleep(&ctlr->chanintr[n], chandone, hc); hc->hcintmsk = 0; splx(x); intr = hc->hcint; if(intr & Chhltd) return intr; start = fastticks(0); ointr = intr; now = start; do{ intr = hc->hcint; if(intr & Chhltd){ if((ointr != Ack && ointr != (Ack|Xfercomp)) || intr != (Ack|Chhltd|Xfercomp) || (now - start) > 60) dprint("await %x after %ld %x -> %x\n", mask, now - start, ointr, intr); return intr; } if((intr & mask) == 0){ dprint("ep%d.%d await %x intr %x -> %x\n", ep->dev->nb, ep->nb, mask, ointr, intr); goto restart; } now = fastticks(0); }while(now - start < 100); dprint("ep%d.%d halting channel %8.8ux hcchar %8.8ux " "grxstsr %8.8ux gnptxsts %8.8ux hptxsts %8.8ux\n", ep->dev->nb, ep->nb, intr, hc->hcchar, r->grxstsr, r->gnptxsts, r->hptxsts); mask = Chhltd; hc->hcchar |= Chdis; start = m->ticks; while(hc->hcchar & Chen){ if(m->ticks - start >= 100){ print("ep%d.%d channel won't halt hcchar %8.8ux\n", ep->dev->nb, ep->nb, hc->hcchar); break; } } logdump(ep); } } static int chanintr(Ctlr *ctlr, int n) { Hostchan *hc; int i; hc = &ctlr->regs->hchan[n]; if(ctlr->debugchan & (1<hcsplt & Spltena) == 0) return 0; i = hc->hcint; if(i == (Chhltd|Ack)){ hc->hcsplt |= Compsplt; ctlr->splitretry = 0; }else if(i == (Chhltd|Nyet)){ if(++ctlr->splitretry >= 3) return 0; }else return 0; if(hc->hcchar & Chen){ iprint("hcchar %8.8ux hcint %8.8ux", hc->hcchar, hc->hcint); hc->hcchar |= Chen | Chdis; while(hc->hcchar&Chen) ; iprint(" %8.8ux\n", hc->hcint); } hc->hcint = i; if(ctlr->regs->hfnum & 1) hc->hcchar &= ~Oddfrm; else hc->hcchar |= Oddfrm; hc->hcchar = (hc->hcchar &~ Chdis) | Chen; return 1; } static Reg chanlog[32][5]; static int nchanlog; static void logstart(Ep *ep) { if(ep->debug) nchanlog = 0; } static void clog(Ep *ep, Hostchan *hc) { Reg *p; if(ep != nil && !ep->debug) return; if(nchanlog == 32) nchanlog--; p = chanlog[nchanlog]; p[0] = dwc.regs->hfnum; p[1] = hc->hcchar; p[2] = hc->hcint; p[3] = hc->hctsiz; p[4] = hc->hcdma; nchanlog++; } static void logdump(Ep *ep) { Reg *p; int i; if(!ep->debug) return; p = chanlog[0]; for(i = 0; i < nchanlog; i++){ print("%5.5d.%5.5d %8.8ux %8.8ux %8.8ux %8.8ux\n", p[0]&0xFFFF, p[0]>>16, p[1], p[2], p[3], p[4]); p += 5; } nchanlog = 0; } static int chanio(Ep *ep, Hostchan *hc, int dir, int pid, void *a, int len) { Ctlr *ctlr; int nleft, n, nt, i, maxpkt, npkt; uint hcdma, hctsiz; ctlr = ep->hp->aux; maxpkt = ep->maxpkt; npkt = HOWMANY(len, ep->maxpkt); if(npkt == 0) npkt = 1; hc->hcchar = (hc->hcchar & ~Epdir) | dir; if(dir == Epin) n = ROUND(len, ep->maxpkt); else n = len; hc->hctsiz = n | npkt<hcdma = PADDR(a); nleft = len; logstart(ep); for(;;){ hcdma = hc->hcdma; hctsiz = hc->hctsiz; hc->hctsiz = hctsiz & ~Dopng; if(hc->hcchar&Chen){ dprint("ep%d.%d before chanio hcchar=%8.8ux\n", ep->dev->nb, ep->nb, hc->hcchar); hc->hcchar |= Chen | Chdis; while(hc->hcchar&Chen) ; hc->hcint = Chhltd; } if((i = hc->hcint) != 0){ dprint("ep%d.%d before chanio hcint=%8.8ux\n", ep->dev->nb, ep->nb, i); hc->hcint = i; } if(hc->hcsplt & Spltena){ qlock(&ctlr->split); sofwait(ctlr, hc - ctlr->regs->hchan); if((dwc.regs->hfnum & 1) == 0) hc->hcchar &= ~Oddfrm; else hc->hcchar |= Oddfrm; } hc->hcchar = (hc->hcchar &~ Chdis) | Chen; clog(ep, hc); if(ep->ttype == Tbulk && dir == Epin) i = chanwait(ep, ctlr, hc, /* Ack| */ Chhltd); else if(ep->ttype == Tintr && (hc->hcsplt & Spltena)) i = chanwait(ep, ctlr, hc, Chhltd); else i = chanwait(ep, ctlr, hc, Chhltd|Nak); clog(ep, hc); hc->hcint = i; if(hc->hcsplt & Spltena){ hc->hcsplt &= ~Compsplt; qunlock(&ctlr->split); } if((i & Xfercomp) == 0 && i != (Chhltd|Ack) && i != Chhltd){ if(i & Stall) error(Estalled); if(i & (Nyet|Frmovrun)) continue; if(i & Nak){ if(ep->ttype == Tintr) tsleep(&up->sleep, return0, 0, ep->pollival); else tsleep(&up->sleep, return0, 0, 1); continue; } logdump(ep); print("usbotg: ep%d.%d error intr %8.8ux\n", ep->dev->nb, ep->nb, i); if(i & ~(Chhltd|Ack)) error(Eio); if(hc->hcdma != hcdma) print("usbotg: weird hcdma %x->%x intr %x->%x\n", hcdma, hc->hcdma, i, hc->hcint); } n = hc->hcdma - hcdma; if(n == 0){ if((hc->hctsiz & Pktcnt) != (hctsiz & Pktcnt)) break; else continue; } if(dir == Epin && ep->ttype == Tbulk && n == nleft){ nt = (hctsiz & Xfersize) - (hc->hctsiz & Xfersize); if(nt != n){ if(n == ROUND(nt, 4)) n = nt; else print("usbotg: intr %8.8ux " "dma %8.8ux-%8.8ux " "hctsiz %8.8ux-%8.ux\n", i, hcdma, hc->hcdma, hctsiz, hc->hctsiz); } } if(n > nleft){ if(n != ROUND(nleft, 4)) dprint("too much: wanted %d got %d\n", len, len - nleft + n); n = nleft; } nleft -= n; if(nleft == 0 || (n % maxpkt) != 0) break; if((i & Xfercomp) && ep->ttype != Tctl) break; if(dir == Epout) dprint("too little: nleft %d hcdma %x->%x hctsiz %x->%x intr %x\n", nleft, hcdma, hc->hcdma, hctsiz, hc->hctsiz, i); } logdump(ep); return len - nleft; } static long multitrans(Ep *ep, Hostchan *hc, int rw, void *a, long n) { long sofar, m; sofar = 0; do{ m = n - sofar; if(m > ep->maxpkt) m = ep->maxpkt; m = chanio(ep, hc, rw == Read? Epin : Epout, ep->toggle[rw], (char*)a + sofar, m); ep->toggle[rw] = hc->hctsiz & Pid; sofar += m; }while(sofar < n && m == ep->maxpkt); return sofar; } static long eptrans(Ep *ep, int rw, void *a, long n) { Hostchan *hc; if(ep->clrhalt){ ep->clrhalt = 0; if(ep->mode != OREAD) ep->toggle[Write] = DATA0; if(ep->mode != OWRITE) ep->toggle[Read] = DATA0; } hc = chanalloc(ep); if(waserror()){ ep->toggle[rw] = hc->hctsiz & Pid; chanrelease(ep, hc); if(strcmp(up->errstr, Estalled) == 0) return 0; nexterror(); } chansetup(hc, ep); if(rw == Read && ep->ttype == Tbulk) n = multitrans(ep, hc, rw, a, n); else{ n = chanio(ep, hc, rw == Read? Epin : Epout, ep->toggle[rw], a, n); ep->toggle[rw] = hc->hctsiz & Pid; } chanrelease(ep, hc); poperror(); return n; } static long ctltrans(Ep *ep, uchar *req, long n) { Hostchan *hc; Epio *epio; Block *b; uchar *data; int datalen; epio = ep->aux; if(epio->cb != nil){ freeb(epio->cb); epio->cb = nil; } if(n < Rsetuplen) error(Ebadlen); if(req[Rtype] & Rd2h){ datalen = GET2(req+Rcount); if(datalen <= 0 || datalen > Maxctllen) error(Ebadlen); /* XXX cache madness */ epio->cb = b = allocb(ROUND(datalen, ep->maxpkt) + CACHELINESZ); b->wp = (uchar*)ROUND((uintptr)b->wp, CACHELINESZ); memset(b->wp, 0x55, b->lim - b->wp); cachedwbinvse(b->wp, b->lim - b->wp); data = b->wp; }else{ b = nil; datalen = n - Rsetuplen; data = req + Rsetuplen; } hc = chanalloc(ep); if(waserror()){ chanrelease(ep, hc); if(strcmp(up->errstr, Estalled) == 0) return 0; nexterror(); } chansetup(hc, ep); chanio(ep, hc, Epout, SETUP, req, Rsetuplen); if(req[Rtype] & Rd2h){ if(ep->dev->hub <= 1){ ep->toggle[Read] = DATA1; b->wp += multitrans(ep, hc, Read, data, datalen); }else b->wp += chanio(ep, hc, Epin, DATA1, data, datalen); chanio(ep, hc, Epout, DATA1, nil, 0); n = Rsetuplen; }else{ if(datalen > 0) chanio(ep, hc, Epout, DATA1, data, datalen); chanio(ep, hc, Epin, DATA1, nil, 0); n = Rsetuplen + datalen; } chanrelease(ep, hc); poperror(); return n; } static long ctldata(Ep *ep, void *a, long n) { Epio *epio; Block *b; epio = ep->aux; b = epio->cb; if(b == nil) return 0; if(n > BLEN(b)) n = BLEN(b); memmove(a, b->rp, n); b->rp += n; if(BLEN(b) == 0){ freeb(b); epio->cb = nil; } return n; } static void greset(Dwcregs *r, int bits) { r->grstctl |= bits; while(r->grstctl & bits) ; microdelay(10); } static void init(Hci *hp) { Ctlr *ctlr; Dwcregs *r; uint n, rx, tx, ptx; ctlr = hp->aux; r = ctlr->regs; ctlr->nchan = 1 + ((r->ghwcfg2 & Num_host_chan) >> ONum_host_chan); ctlr->chanintr = malloc(ctlr->nchan * sizeof(Rendez)); r->gahbcfg = 0; setpower(PowerUsb, 1); while((r->grstctl&Ahbidle) == 0) ; greset(r, Csftrst); r->gusbcfg |= Force_host_mode; tsleep(&up->sleep, return0, 0, 25); r->gahbcfg |= Dmaenable; n = (r->ghwcfg3 & Dfifo_depth) >> ODfifo_depth; rx = 0x306; tx = 0x100; ptx = 0x200; r->grxfsiz = rx; r->gnptxfsiz = rx | tx<sleep, return0, 0, 1); r->hptxfsiz = (rx + tx) | ptx << ODepth; greset(r, Rxfflsh); r->grstctl = TXF_ALL; greset(r, Txfflsh); dprint("usbotg: FIFO depth %d sizes rx/nptx/ptx %8.8ux %8.8ux %8.8ux\n", n, r->grxfsiz, r->gnptxfsiz, r->hptxfsiz); r->hport0 = Prtpwr|Prtconndet|Prtenchng|Prtovrcurrchng; r->gintsts = ~0; r->gintmsk = Hcintr; r->gahbcfg |= Glblintrmsk; } static void dump(Hci*) { } static void fiqintr(Ureg*, void *a) { Hci *hp; Ctlr *ctlr; Dwcregs *r; uint intr, haint, wakechan; int i; hp = a; ctlr = hp->aux; r = ctlr->regs; wakechan = 0; intr = r->gintsts; if(intr & Hcintr){ haint = r->haint & r->haintmsk; for(i = 0; haint; i++){ if(haint & 1){ if(chanintr(ctlr, i) == 0){ r->haintmsk &= ~(1<>= 1; } } if(intr & Sofintr){ r->gintsts = Sofintr; if((r->hfnum&7) != 6){ r->gintmsk &= ~Sofintr; wakechan |= ctlr->sofchan; ctlr->sofchan = 0; } } if(wakechan){ ctlr->wakechan |= wakechan; armtimerset(1); } } static void irqintr(Ureg*, void *a) { Ctlr *ctlr; uint wakechan; int i, x; ctlr = a; x = splfhi(); armtimerset(0); wakechan = ctlr->wakechan; ctlr->wakechan = 0; splx(x); for(i = 0; wakechan; i++){ if(wakechan & 1) wakeup(&ctlr->chanintr[i]); wakechan >>= 1; } } static void epopen(Ep *ep) { ddprint("usbotg: epopen ep%d.%d ttype %d\n", ep->dev->nb, ep->nb, ep->ttype); switch(ep->ttype){ case Tnone: error(Enotconf); case Tintr: assert(ep->pollival > 0); /* fall through */ case Tbulk: if(ep->toggle[Read] == 0) ep->toggle[Read] = DATA0; if(ep->toggle[Write] == 0) ep->toggle[Write] = DATA0; break; } ep->aux = malloc(sizeof(Epio)); if(ep->aux == nil) error(Enomem); } static void epclose(Ep *ep) { ddprint("usbotg: epclose ep%d.%d ttype %d\n", ep->dev->nb, ep->nb, ep->ttype); switch(ep->ttype){ case Tctl: freeb(((Epio*)ep->aux)->cb); /* fall through */ default: free(ep->aux); break; } } static long epread(Ep *ep, void *a, long n) { Epio *epio; Block *b; uchar *p; ulong elapsed; long nr; ddprint("epread ep%d.%d %ld\n", ep->dev->nb, ep->nb, n); epio = ep->aux; b = nil; qlock(epio); if(waserror()){ qunlock(epio); if(b) freeb(b); nexterror(); } switch(ep->ttype){ default: error(Egreg); case Tctl: nr = ctldata(ep, a, n); qunlock(epio); poperror(); return nr; case Tintr: elapsed = TK2MS(m->ticks) - epio->lastpoll; if(elapsed < ep->pollival) tsleep(&up->sleep, return0, 0, ep->pollival - elapsed); /* fall through */ case Tbulk: /* XXX cache madness */ b = allocb(ROUND(n, ep->maxpkt) + CACHELINESZ); p = (uchar*)ROUND((uintptr)b->base, CACHELINESZ); cachedwbinvse(p, n); nr = eptrans(ep, Read, p, n); epio->lastpoll = TK2MS(m->ticks); memmove(a, p, nr); qunlock(epio); freeb(b); poperror(); return nr; } } static long epwrite(Ep *ep, void *a, long n) { Epio *epio; Block *b; uchar *p; ulong elapsed; ddprint("epwrite ep%d.%d %ld\n", ep->dev->nb, ep->nb, n); epio = ep->aux; b = nil; qlock(epio); if(waserror()){ qunlock(epio); if(b) freeb(b); nexterror(); } switch(ep->ttype){ default: error(Egreg); case Tintr: elapsed = TK2MS(m->ticks) - epio->lastpoll; if(elapsed < ep->pollival) tsleep(&up->sleep, return0, 0, ep->pollival - elapsed); /* fall through */ case Tctl: case Tbulk: /* XXX cache madness */ b = allocb(n + CACHELINESZ); p = (uchar*)ROUND((uintptr)b->base, CACHELINESZ); memmove(p, a, n); cachedwbse(p, n); if(ep->ttype == Tctl) n = ctltrans(ep, p, n); else{ n = eptrans(ep, Write, p, n); epio->lastpoll = TK2MS(m->ticks); } qunlock(epio); freeb(b); poperror(); return n; } } static char* seprintep(char *s, char*, Ep*) { return s; } static int portenable(Hci *hp, int port, int on) { Ctlr *ctlr; Dwcregs *r; assert(port == 1); ctlr = hp->aux; r = ctlr->regs; dprint("usbotg enable=%d; sts %#x\n", on, r->hport0); if(!on) r->hport0 = Prtpwr | Prtena; tsleep(&up->sleep, return0, 0, Enabledelay); dprint("usbotg enable=%d; sts %#x\n", on, r->hport0); return 0; } static int portreset(Hci *hp, int port, int on) { Ctlr *ctlr; Dwcregs *r; int b, s; assert(port == 1); ctlr = hp->aux; r = ctlr->regs; dprint("usbotg reset=%d; sts %#x\n", on, r->hport0); if(!on) return 0; r->hport0 = Prtpwr | Prtrst; tsleep(&up->sleep, return0, 0, ResetdelayHS); r->hport0 = Prtpwr; tsleep(&up->sleep, return0, 0, Enabledelay); s = r->hport0; b = s & (Prtconndet|Prtenchng|Prtovrcurrchng); if(b != 0) r->hport0 = Prtpwr | b; dprint("usbotg reset=%d; sts %#x\n", on, s); if((s & Prtena) == 0) print("usbotg: host port not enabled after reset"); return 0; } static int portstatus(Hci *hp, int port) { Ctlr *ctlr; Dwcregs *r; int b, s; assert(port == 1); ctlr = hp->aux; r = ctlr->regs; s = r->hport0; b = s & (Prtconndet|Prtenchng|Prtovrcurrchng); if(b != 0) r->hport0 = Prtpwr | b; b = 0; if(s & Prtconnsts) b |= HPpresent; if(s & Prtconndet) b |= HPstatuschg; if(s & Prtena) b |= HPenable; if(s & Prtenchng) b |= HPchange; if(s & Prtovrcurract) b |= HPovercurrent; if(s & Prtsusp) b |= HPsuspend; if(s & Prtrst) b |= HPreset; if(s & Prtpwr) b |= HPpower; switch(s & Prtspd){ case HIGHSPEED: b |= HPhigh; break; case LOWSPEED: b |= HPslow; break; } return b; } static void shutdown(Hci*) { } static void setdebug(Hci*, int d) { debug = d; } static int reset(Hci *hp) { Ctlr *ctlr; uint id; ctlr = &dwc; if(ctlr->regs != nil) return -1; ctlr->regs = (Dwcregs*)USBREGS; id = ctlr->regs->gsnpsid; if((id>>16) != ('O'<<8 | 'T')) return -1; dprint("usbotg: rev %d.%3.3x\n", (id>>12)&0xF, id&0xFFF); intrenable(IRQtimerArm, irqintr, ctlr, 0, "dwc"); hp->aux = ctlr; hp->port = 0; hp->irq = IRQusb; hp->tbdf = 0; hp->nports = 1; hp->highspeed = 1; hp->init = init; hp->dump = dump; hp->interrupt = fiqintr; hp->epopen = epopen; hp->epclose = epclose; hp->epread = epread; hp->epwrite = epwrite; hp->seprintep = seprintep; hp->portenable = portenable; hp->portreset = portreset; hp->portstatus = portstatus; hp->shutdown = shutdown; hp->debug = setdebug; hp->type = "dwcotg"; return 0; } void usbdwclink(void) { addhcitype("dwcotg", reset); }