
moved devusb to port, shifting the responsibility of how to enable interrupts to the arch specific hci driver.
353 lines
7.1 KiB
C
353 lines
7.1 KiB
C
/*
|
|
* Kirkwood-specific code for
|
|
* USB Enhanced Host Controller Interface (EHCI) driver
|
|
* High speed USB 2.0.
|
|
*/
|
|
|
|
#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 "usbehci.h"
|
|
//#include "uncached.h"
|
|
|
|
#define WINTARG(ctl) (((ctl) >> 4) & 017)
|
|
#define WINATTR(ctl) (((ctl) >> 8) & 0377)
|
|
#define WIN64KSIZE(ctl) (((ctl) >> 16) + 1)
|
|
|
|
#define SIZETO64KSIZE(size) ((size) / (64*1024) - 1)
|
|
|
|
enum {
|
|
Debug = 0,
|
|
};
|
|
|
|
typedef struct Kwusb Kwusb;
|
|
typedef struct Kwusbtt Kwusbtt;
|
|
typedef struct Usbwin Usbwin;
|
|
|
|
/* kirkwood usb transaction translator registers? (undocumented) */
|
|
struct Kwusbtt { /* at soc.ehci */
|
|
ulong id;
|
|
ulong hwgeneral;
|
|
ulong hwhost;
|
|
ulong hwdevice;
|
|
ulong hwtxbuf;
|
|
ulong hwrxbuf;
|
|
ulong hwtttxbuf;
|
|
ulong hwttrxbuf;
|
|
};
|
|
|
|
/* kirkwood usb bridge & phy registers */
|
|
struct Kwusb { /* at offset 0x300 from soc.ehci */
|
|
ulong bcs; /* bridge ctl & sts */
|
|
uchar _pad0[0x310-0x304];
|
|
|
|
ulong bic; /* bridge intr. cause */
|
|
ulong bim; /* bridge intr. mask */
|
|
ulong _pad1;
|
|
ulong bea; /* bridge error addr. */
|
|
struct Usbwin {
|
|
ulong ctl; /* see Winenable in io.h */
|
|
ulong base;
|
|
ulong _pad2[2];
|
|
} win[4];
|
|
ulong phycfg; /* phy config. */
|
|
uchar _pad3[0x400-0x364];
|
|
|
|
ulong pwrctl; /* power control */
|
|
uchar _pad4[0x410-0x404];
|
|
ulong phypll; /* phy pll control */
|
|
uchar _pad5[0x420-0x414];
|
|
ulong phytxctl; /* phy transmit control */
|
|
uchar _pad6[0x430-0x424];
|
|
ulong phyrxctl; /* phy receive control */
|
|
uchar _pad7[0x440-0x434];
|
|
ulong phyivref; /* phy ivref control */
|
|
};
|
|
|
|
static Ctlr* ctlrs[Nhcis];
|
|
|
|
static void
|
|
addrmapdump(void)
|
|
{
|
|
int i;
|
|
ulong ctl, targ, attr, size64k;
|
|
Kwusb *map;
|
|
Usbwin *win;
|
|
|
|
if (!Debug)
|
|
return;
|
|
map = (Kwusb *)(soc.ehci + 0x300);
|
|
for (i = 0; i < nelem(map->win); i++) {
|
|
win = &map->win[i];
|
|
ctl = win->ctl;
|
|
if (ctl & Winenable) {
|
|
targ = WINTARG(ctl);
|
|
attr = WINATTR(ctl);
|
|
size64k = WIN64KSIZE(ctl);
|
|
print("usbehci: address map window %d: "
|
|
"targ %ld attr %#lux size %,ld addr %#lux\n",
|
|
i, targ, attr, size64k * 64*1024, win->base);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* assumes ctlr is ilocked */
|
|
static void
|
|
ctlrreset(Ctlr *ctlr)
|
|
{
|
|
int i;
|
|
Eopio *opio;
|
|
|
|
opio = ctlr->opio;
|
|
opio->cmd |= Chcreset;
|
|
coherence();
|
|
/* wait for it to come out of reset */
|
|
for(i = 0; i < 100 && opio->cmd & Chcreset; i++)
|
|
delay(1);
|
|
if(i >= 100)
|
|
print("ehci %#p controller reset timed out\n", ctlr->capio);
|
|
/*
|
|
* Marvell errata FE-USB-340 workaround: 1 << 4 magic:
|
|
* disable streaming. Magic 3 (usb host mode) from the linux driver
|
|
* makes it work. Ick.
|
|
*/
|
|
opio->usbmode |= 1 << 4 | 3;
|
|
coherence();
|
|
}
|
|
|
|
/*
|
|
* configure window `win' as 256MB dram with attribute `attr' and
|
|
* base address
|
|
*/
|
|
static void
|
|
setaddrwin(Kwusb *kw, int win, int attr, ulong base)
|
|
{
|
|
kw->win[win].ctl = Winenable | Targdram << 4 | attr << 8 |
|
|
SIZETO64KSIZE(256*MB) << 16;
|
|
kw->win[win].base = base;
|
|
}
|
|
|
|
static void
|
|
ehcireset(Ctlr *ctlr)
|
|
{
|
|
int i, amp, txvdd;
|
|
ulong v;
|
|
Eopio *opio;
|
|
Kwusb *kw;
|
|
|
|
ilock(ctlr);
|
|
dprint("ehci %#p reset\n", ctlr->capio);
|
|
opio = ctlr->opio;
|
|
|
|
kw = (Kwusb *)(soc.ehci + 0x300);
|
|
kw->bic = 0;
|
|
kw->bim = (1<<4) - 1; /* enable all defined intrs */
|
|
ctlrreset(ctlr);
|
|
|
|
/*
|
|
* clear high 32 bits of address signals if it's 64 bits capable.
|
|
* This is probably not needed but it does not hurt and others do it.
|
|
*/
|
|
if((ctlr->capio->capparms & C64) != 0){
|
|
dprint("ehci: 64 bits\n");
|
|
opio->seg = 0;
|
|
}
|
|
|
|
/* requesting more interrupts per µframe may miss interrupts */
|
|
opio->cmd |= Citc8; /* 1 intr. per ms */
|
|
switch(opio->cmd & Cflsmask){
|
|
case Cfls1024:
|
|
ctlr->nframes = 1024;
|
|
break;
|
|
case Cfls512:
|
|
ctlr->nframes = 512;
|
|
break;
|
|
case Cfls256:
|
|
ctlr->nframes = 256;
|
|
break;
|
|
default:
|
|
panic("ehci: unknown fls %ld", opio->cmd & Cflsmask);
|
|
}
|
|
dprint("ehci: %d frames\n", ctlr->nframes);
|
|
|
|
/*
|
|
* set up the USB address map (bridge address decoding)
|
|
*/
|
|
for (i = 0; i < nelem(kw->win); i++)
|
|
kw->win[i].ctl = kw->win[i].base = 0;
|
|
coherence();
|
|
|
|
setaddrwin(kw, 0, Attrcs0, 0);
|
|
setaddrwin(kw, 1, Attrcs1, 256*MB);
|
|
coherence();
|
|
|
|
if (Debug)
|
|
if (kw->bcs & (1 << 4))
|
|
print("usbehci: not swapping bytes\n");
|
|
else
|
|
print("usbehci: swapping bytes\n");
|
|
addrmapdump(); /* verify sanity */
|
|
|
|
kw->pwrctl |= 1 << 0 | 1 << 1; /* Pu | PuPll */
|
|
coherence();
|
|
|
|
/*
|
|
* Marvell guideline GL-USB-160.
|
|
*/
|
|
kw->phypll |= 1 << 21; /* VCOCAL_START: PLL calibration */
|
|
coherence();
|
|
microdelay(100);
|
|
kw->phypll &= ~(1 << 21);
|
|
|
|
v = kw->phytxctl & ~(017 << 27 | 7); /* REG_EXT_FS_RCALL & AMP_2_0 */
|
|
switch (m->socrev) {
|
|
default:
|
|
print("usbehci: bad 6281 soc rev %d\n", m->socrev);
|
|
/* fall through */
|
|
case Socreva0:
|
|
amp = 4;
|
|
txvdd = 1;
|
|
break;
|
|
case Socreva1:
|
|
amp = 3;
|
|
txvdd = 3;
|
|
break;
|
|
}
|
|
/* REG_EXT_FS_RCALL_EN | REG_RCAL_START | AMP_2_0 */
|
|
kw->phytxctl = v | 1 << 26 | 1 << 12 | amp;
|
|
coherence();
|
|
microdelay(100);
|
|
kw->phytxctl &= ~(1 << 12);
|
|
|
|
v = kw->phyrxctl & ~(3 << 2 | 017 << 4); /* LPF_COEF_1_0 & SQ_THRESH_3_0 */
|
|
kw->phyrxctl = v | 1 << 2 | 8 << 4;
|
|
|
|
v = kw->phyivref & ~(3 << 8); /* TXVDD12 */
|
|
kw->phyivref = v | txvdd << 8;
|
|
coherence();
|
|
|
|
ehcirun(ctlr, 0);
|
|
ctlrreset(ctlr);
|
|
|
|
iunlock(ctlr);
|
|
}
|
|
|
|
static void
|
|
setdebug(Hci*, int d)
|
|
{
|
|
ehcidebug = d;
|
|
}
|
|
|
|
static void
|
|
shutdown(Hci *hp)
|
|
{
|
|
Ctlr *ctlr;
|
|
Eopio *opio;
|
|
|
|
ctlr = hp->aux;
|
|
ilock(ctlr);
|
|
ctlrreset(ctlr);
|
|
|
|
delay(100);
|
|
ehcirun(ctlr, 0);
|
|
|
|
opio = ctlr->opio;
|
|
opio->frbase = 0;
|
|
coherence();
|
|
iunlock(ctlr);
|
|
}
|
|
|
|
static void
|
|
findehcis(void) /* actually just use fixed addresses on sheeva */
|
|
{
|
|
int i;
|
|
Ctlr *ctlr;
|
|
static int already = 0;
|
|
|
|
if(already)
|
|
return;
|
|
already = 1;
|
|
|
|
ctlr = smalloc(sizeof(Ctlr));
|
|
/* the sheeva's usb 2.0 otg uses a superset of the ehci registers */
|
|
ctlr->capio = (Ecapio *)(soc.ehci + 0x100);
|
|
ctlr->opio = (Eopio *) (soc.ehci + 0x140);
|
|
dprint("usbehci: port %#p\n", ctlr->capio);
|
|
|
|
for(i = 0; i < Nhcis; i++)
|
|
if(ctlrs[i] == nil){
|
|
ctlrs[i] = ctlr;
|
|
break;
|
|
}
|
|
if(i == Nhcis)
|
|
print("ehci: bug: more than %d controllers\n", Nhcis);
|
|
}
|
|
|
|
static int
|
|
reset(Hci *hp)
|
|
{
|
|
static Lock resetlck;
|
|
int i;
|
|
Ctlr *ctlr;
|
|
Ecapio *capio;
|
|
|
|
ilock(&resetlck);
|
|
findehcis();
|
|
|
|
/*
|
|
* Any adapter matches if no hp->port is supplied,
|
|
* otherwise the ports must match.
|
|
*/
|
|
ctlr = nil;
|
|
for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){
|
|
ctlr = ctlrs[i];
|
|
if(ctlr->active == 0)
|
|
if(hp->port == 0 || hp->port == (uintptr)ctlr->capio){
|
|
ctlr->active = 1;
|
|
break;
|
|
}
|
|
}
|
|
iunlock(&resetlck);
|
|
if(ctlrs[i] == nil || i == Nhcis)
|
|
return -1;
|
|
|
|
hp->aux = ctlr;
|
|
hp->port = (uintptr)ctlr->capio;
|
|
hp->irq = IRQ0usb0;
|
|
hp->tbdf = 0;
|
|
|
|
capio = ctlr->capio;
|
|
hp->nports = capio->parms & Cnports;
|
|
|
|
ddprint("echi: %s, ncc %lud npcc %lud\n",
|
|
capio->parms & 0x10000 ? "leds" : "no leds",
|
|
(capio->parms >> 12) & 0xf, (capio->parms >> 8) & 0xf);
|
|
ddprint("ehci: routing %s, %sport power ctl, %d ports\n",
|
|
capio->parms & 0x40 ? "explicit" : "automatic",
|
|
capio->parms & 0x10 ? "" : "no ", hp->nports);
|
|
|
|
ehcireset(ctlr);
|
|
ehcimeminit(ctlr);
|
|
|
|
/*
|
|
* Linkage to the generic HCI driver.
|
|
*/
|
|
ehcilinkage(hp);
|
|
hp->shutdown = shutdown;
|
|
hp->debug = setdebug;
|
|
|
|
intrenable(Irqlo, hp->irq, hp->interrupt, hp, hp->type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
usbehcilink(void)
|
|
{
|
|
addhcitype("ehci", reset);
|
|
}
|