/* * Asix USB ether adapters * I got no documentation for it, thus the bits * come from other systems; it's likely this is * doing more than needed in some places and * less than required in others. */ #include #include #include #include #include "usb.h" #include "usbfs.h" #include "ether.h" enum { /* Asix commands */ Cswmii = 0x06, /* set sw mii */ Crmii = 0x07, /* read mii reg */ Cwmii = 0x08, /* write mii reg */ Chwmii = 0x0a, /* set hw mii */ Creeprom = 0x0b, /* read eeprom */ Cwdis = 0x0e, /* write disable */ Cwena = 0x0d, /* write enable */ Crrxctl = 0x0f, /* read rx ctl */ Cwrxctl = 0x10, /* write rx ctl */ Cwipg = 0x12, /* write ipg */ Crmac = 0x13, /* read mac addr */ Crphy = 0x19, /* read phy id */ Cwmedium = 0x1b, /* write medium mode */ Crgpio = 0x1e, /* read gpio */ Cwgpio = 0x1f, /* write gpios */ Creset = 0x20, /* reset */ Cwphy = 0x22, /* select phy */ /* reset codes */ Rclear = 0x00, Rprte = 0x04, Rprl = 0x08, Riprl = 0x20, Rippd = 0x40, Gpiogpo1en = 0x04, /* gpio1 enable */, Gpiogpo1 = 0x08, /* gpio1 value */ Gpiogpo2en = 0x10, /* gpio2 enable */ Gpiogpo2 = 0x20, /* gpio2 value */ Gpiorse = 0x80, /* gpio reload serial eeprom */ Pmask = 0x1F, Pembed = 0x10, /* embedded phy */ Mfd = 0x002, /* media */ Mac = 0x004, Mrfc = 0x010, Mtfc = 0x020, Mjfe = 0x040, Mre = 0x100, Mps = 0x200, Mall772 = Mfd|Mrfc|Mtfc|Mps|Mac|Mre, Mall178 = Mps|Mfd|Mac|Mrfc|Mtfc|Mjfe|Mre, Ipgdflt = 0x15|0x0c|0x12, /* default ipg0, 1, 2 */ Rxctlso = 0x80, Rxctlab = 0x08, Rxctlsep = 0x04, Rxctlamall = 0x02, /* all multicast */ Rxctlprom = 0x01, /* promiscuous */ /* MII */ Miibmcr = 0x00, /* basic mode ctrl reg. */ Bmcrreset = 0x8000, /* reset */ Bmcranena = 0x1000, /* auto neg. enable */ Bmcrar = 0x0200, /* announce restart */ Miiad = 0x04, /* advertise reg. */ Adcsma = 0x0001, Ad1000f = 0x0200, Ad1000h = 0x0100, Ad10h = 0x0020, Ad10f = 0x0040, Ad100h = 0x0080, Ad100f = 0x0100, Adpause = 0x0400, Adall = Ad10h|Ad10f|Ad100h|Ad100f, Miimctl = 0x14, /* marvell ctl */ Mtxdly = 0x02, Mrxdly = 0x80, Mtxrxdly = 0x82, Miic1000 = 0x09, }; static int asixset(Dev *d, int c, int v) { int r; int ec; r = Rh2d|Rvendor|Rdev; ec = usbcmd(d, r, c, v, 0, nil, 0); if(ec < 0) deprint(2, "%s: asixset %x %x: %r\n", argv0, c, v); return ec; } static int asixget(Dev *d, int c, uchar *buf, int l) { int r; int ec; r = Rd2h|Rvendor|Rdev; ec = usbcmd(d, r, c, 0, 0, buf, l); if(ec < 0) deprint(2, "%s: asixget %x: %r\n", argv0, c); return ec; } static int getgpio(Dev *d) { uchar c; if(asixget(d, Crgpio, &c, 1) < 0) return -1; return c; } static int getphy(Dev *d) { uchar buf[2]; if(asixget(d, Crphy, buf, sizeof(buf)) < 0) return -1; deprint(2, "%s: phy addr %#ux\n", argv0, buf[1]); return buf[1]; } static int getrxctl(Dev *d) { uchar buf[2]; int r; memset(buf, 0, sizeof(buf)); if(asixget(d, Crrxctl, buf, sizeof(buf)) < 0) return -1; r = GET2(buf); deprint(2, "%s: rxctl %#x\n", argv0, r); return r; } static int getmac(Dev *d, uchar buf[]) { if(asixget(d, Crmac, buf, Eaddrlen) < 0) return -1; return Eaddrlen; } static int miiread(Dev *d, int phy, int reg) { int r; uchar v[2]; r = Rd2h|Rvendor|Rdev; if(usbcmd(d, r, Crmii, phy, reg, v, 2) < 0){ dprint(2, "%s: miiwrite: %r\n", argv0); return -1; } r = GET2(v); if(r == 0xFFFF) return -1; return r; } static int miiwrite(Dev *d, int phy, int reg, int val) { int r; uchar v[2]; if(asixset(d, Cswmii, 0) < 0) return -1; r = Rh2d|Rvendor|Rdev; PUT2(v, val); if(usbcmd(d, r, Cwmii, phy, reg, v, 2) < 0){ deprint(2, "%s: miiwrite: %#x %#x %r\n", argv0, reg, val); return -1; } if(asixset(d, Chwmii, 0) < 0) return -1; return 0; } static int eepromread(Dev *d, int i) { int r; int ec; uchar buf[2]; r = Rd2h|Rvendor|Rdev; ec = usbcmd(d, r, Creeprom, i, 0, buf, sizeof(buf)); if(ec < 0) deprint(2, "%s: eepromread %d: %r\n", argv0, i); ec = GET2(buf); deprint(2, "%s: eeprom %#x = %#x\n", argv0, i, ec); if(ec == 0xFFFF) ec = -1; return ec; } /* * No doc. we are doing what Linux does as closely * as we can. */ static int ctlrinit(Ether *ether) { Dev *d; int i; int bmcr; int gpio; int ee17; int rc; d = ether->dev; switch(ether->cid){ case A8817x: case A88179: fprint(2, "%s: card known but not implemented\n", argv0); /* fall through */ default: return -1; case A88178: deprint(2, "%s: setting up A88178\n", argv0); gpio = getgpio(d); if(gpio < 0) return -1; deprint(2, "%s: gpio sts %#x\n", argv0, gpio); asixset(d, Cwena, 0); ee17 = eepromread(d, 0x0017); asixset(d, Cwdis, 0); asixset(d, Cwgpio, Gpiorse|Gpiogpo1|Gpiogpo1en); if((ee17 >> 8) != 1){ asixset(d, Cwgpio, 0x003c); asixset(d, Cwgpio, 0x001c); asixset(d, Cwgpio, 0x003c); }else{ asixset(d, Cwgpio, Gpiogpo1en); asixset(d, Cwgpio, Gpiogpo1|Gpiogpo1en); } asixset(d, Creset, Rclear); sleep(150); asixset(d, Creset, Rippd|Rprl); sleep(150); asixset(d, Cwrxctl, 0); if(getmac(d, ether->addr) < 0) return -1; ether->phy = getphy(d); if(ee17 < 0 || (ee17 & 0x7) == 0){ miiwrite(d, ether->phy, Miimctl, Mtxrxdly); sleep(60); } miiwrite(d, ether->phy, Miibmcr, Bmcrreset|Bmcranena); miiwrite(d, ether->phy, Miiad, Adall|Adcsma|Adpause); miiwrite(d, ether->phy, Miic1000, Ad1000f); bmcr = miiread(d, ether->phy, Miibmcr); if((bmcr & Bmcranena) != 0){ bmcr |= Bmcrar; miiwrite(d, ether->phy, Miibmcr, bmcr); } asixset(d, Cwmedium, Mall178); asixset(d, Cwrxctl, Rxctlso|Rxctlab); break; case A88772: deprint(2, "%s: setting up A88772\n", argv0); if(asixset(d, Cwgpio, Gpiorse|Gpiogpo2|Gpiogpo2en) < 0) return -1; ether->phy = getphy(d); dprint(2, "%s: phy %#x\n", argv0, ether->phy); if((ether->phy & Pmask) == Pembed){ /* embedded 10/100 ethernet */ rc = asixset(d, Cwphy, 1); }else rc = asixset(d, Cwphy, 0); if(rc < 0) return -1; if(asixset(d, Creset, Rippd|Rprl) < 0) return -1; sleep(150); if((ether->phy & Pmask) == Pembed) rc = asixset(d, Creset, Riprl); else rc = asixset(d, Creset, Rprte); if(rc < 0) return -1; sleep(150); rc = getrxctl(d); deprint(2, "%s: rxctl is %#x\n", argv0, rc); if(asixset(d, Cwrxctl, 0) < 0) return -1; if(getmac(d, ether->addr) < 0) return -1; if(asixset(d, Creset, Rprl) < 0) return -1; sleep(150); if(asixset(d, Creset, Riprl|Rprl) < 0) return -1; sleep(150); miiwrite(d, ether->phy, Miibmcr, Bmcrreset); miiwrite(d, ether->phy, Miiad, Adall|Adcsma); bmcr = miiread(d, ether->phy, Miibmcr); if((bmcr & Bmcranena) != 0){ bmcr |= Bmcrar; miiwrite(d, ether->phy, Miibmcr, bmcr); } if(asixset(d, Cwmedium, Mall772) < 0) return -1; if(asixset(d, Cwipg, Ipgdflt) < 0) return -1; if(asixset(d, Cwrxctl, Rxctlso|Rxctlab) < 0) return -1; deprint(2, "%s: final rxctl: %#x\n", argv0, getrxctl(d)); break; } if(etherdebug){ fprint(2, "%s: ether: phy %#x addr ", argv0, ether->phy); for(i = 0; i < sizeof(ether->addr); i++) fprint(2, "%02x", ether->addr[i]); fprint(2, "\n"); } return 0; } static long asixbread(Ether *e, Buf *bp) { ulong nr; ulong hd; Buf *rbp; rbp = e->aux; if(rbp == nil || rbp->ndata < 4){ rbp->rp = rbp->data; rbp->ndata = read(e->epin->dfd, rbp->rp, sizeof(bp->data)); if(rbp->ndata < 0) return -1; } if(rbp->ndata < 4){ werrstr("short frame"); deprint(2, "%s: asixbread got %d bytes\n", argv0, rbp->ndata); rbp->ndata = 0; return 0; } hd = GET4(rbp->rp); nr = hd & 0xFFFF; hd = (hd>>16) & 0xFFFF; if(nr != (~hd & 0xFFFF)){ if(0)deprint(2, "%s: asixread: bad header %#ulx %#ulx\n", argv0, nr, (~hd & 0xFFFF)); werrstr("bad usb packet header"); rbp->ndata = 0; return 0; } rbp->rp += 4; if(nr < 6 || nr > Epktlen){ if(nr < 6) werrstr("short frame"); else werrstr("long frame"); deprint(2, "%s: asixbread %r (%ld)\n", argv0, nr); rbp->ndata = 0; return 0; } bp->rp = bp->data + Hdrsize; memmove(bp->rp, rbp->rp, nr); bp->ndata = nr; rbp->rp += 4 + nr; rbp->ndata -= (4 + nr); return bp->ndata; } static long asixbwrite(Ether *e, Buf *bp) { ulong len; long n; deprint(2, "%s: asixbwrite %d bytes\n", argv0, bp->ndata); assert(bp->rp - bp->data >= Hdrsize); bp->ndata &= 0xFFFF; len = (0xFFFF0000 & ~(bp->ndata<<16)) | bp->ndata; bp->rp -= 4; PUT4(bp->rp, len); bp->ndata += 4; if((bp->ndata % e->epout->maxpkt) == 0){ PUT4(bp->rp+bp->ndata, 0xFFFF0000); bp->ndata += 4; } n = write(e->epout->dfd, bp->rp, bp->ndata); deprint(2, "%s: asixbwrite wrote %ld bytes\n", argv0, n); if(n <= 0) return n; return n; } static int asixpromiscuous(Ether *e, int on) { int rxctl; deprint(2, "%s: asixpromiscuous %d\n", argv0, on); rxctl = getrxctl(e->dev); if(on != 0) rxctl |= Rxctlprom; else rxctl &= ~Rxctlprom; return asixset(e->dev, Cwrxctl, rxctl); } static int asixmulticast(Ether *e, uchar *addr, int on) { int rxctl; USED(addr); USED(on); /* BUG: should write multicast filter */ rxctl = getrxctl(e->dev); if(e->nmcasts != 0) rxctl |= Rxctlamall; else rxctl &= ~Rxctlamall; deprint(2, "%s: asixmulticast %d\n", argv0, e->nmcasts); return asixset(e->dev, Cwrxctl, rxctl); } static void asixfree(Ether *ether) { deprint(2, "%s: aixfree %#p\n", argv0, ether); free(ether->aux); ether->aux = nil; } int asixreset(Ether *ether) { Cinfo *ip; Dev *dev; dev = ether->dev; for(ip = cinfo; ip->vid != 0; ip++) if(ip->vid == dev->usb->vid && ip->did == dev->usb->did){ ether->cid = ip->cid; if(ctlrinit(ether) < 0){ deprint(2, "%s: asix init failed: %r\n", argv0); return -1; } deprint(2, "%s: asix reset done\n", argv0); ether->name = "asix"; ether->aux = emallocz(sizeof(Buf), 1); ether->bufsize = Hdrsize+Maxpkt; ether->bread = asixbread; ether->bwrite = asixbwrite; ether->free = asixfree; ether->promiscuous = asixpromiscuous; ether->multicast = asixmulticast; ether->mbps = 100; /* BUG */ return 0; } return -1; }