Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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 <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#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;
}