/*
* USB host driver for BCM2835
* Synopsis DesignWare Core USB 2.0 OTG controller
*
* Copyright © 2012 Richard Miller <r.miller@acm.org>
*
* 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 char Enotconfig[] = "usb endpoint not configured";
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<<i)) == 0){
ctlr->chanbusy = bitmap | 1<<i;
qunlock(&ctlr->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<<i);
qunlock(&ctlr->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<<ODevaddr;
break;
}
hcc |= ep->maxpkt | 1<<OMulticnt | ep->nb<<OEpnum;
switch(ep->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:
hc->hcsplt = Spltena | POS_ALL | ep->dev->hub<<OHubaddr |
ep->dev->port;
break;
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<<n;
r->gintmsk |= Sofintr;
sleep(&ctlr->chanintr[n], sofdone, r);
splx(x);
}while((r->hfnum & 7) == 6);
}
static int
chandone(void *a)
{
Hostchan *hc;
hc = a;
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<<n;
hc->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<<n))
clog(nil, hc);
if((hc->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<<OPktcnt | pid;
hc->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)
continue;
if(i & Nak){
if(ep->ttype == Tintr)
tsleep(&up->sleep, return0, 0, ep->pollival);
else
tsleep(&up->sleep, return0, 0, 1);
continue;
}
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
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){
long sofar, m;
sofar = 0;
do{
m = n - sofar;
if(m > ep->maxpkt)
m = ep->maxpkt;
m = chanio(ep, hc, Epin, ep->toggle[rw],
(char*)a + sofar, m);
ep->toggle[rw] = hc->hctsiz & Pid;
sofar += m;
}while(sofar < n && m == ep->maxpkt);
n = sofar;
}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){
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<<ODepth;
tsleep(&up->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<<i);
wakechan |= 1<<i;
}
}
haint >>= 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(Enotconfig);
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);
}
|