/*
* Digital Semiconductor DECchip 2114x PCI Fast Ethernet LAN Controller.
* To do:
*      thresholds;
*      ring sizing;
*      handle more error conditions;
*      tidy setup packet mess;
*      push initialisation back to attach;
*      full SROM decoding.
*/
#include "all.h"
#include "io.h"
#include "mem.h"

#include "../ip/ip.h"
#include "etherif.h"

#define DEBUG           (0)
#define debug           if(DEBUG)print

enum {
       Nrde            = 64,
       Ntde            = 64,
};

#define Rbsz            ROUNDUP(sizeof(Enpkt)+4, 4)

enum {                                  /* CRS0 - Bus Mode */
       Swr             = 0x00000001,   /* Software Reset */
       Bar             = 0x00000002,   /* Bus Arbitration */
       Dsl             = 0x0000007C,   /* Descriptor Skip Length (field) */
       Ble             = 0x00000080,   /* Big/Little Endian */
       Pbl             = 0x00003F00,   /* Programmable Burst Length (field) */
       Cal             = 0x0000C000,   /* Cache Alignment (field) */
       Cal8            = 0x00004000,   /* 8 longword boundary alignment */
       Cal16           = 0x00008000,   /* 16 longword boundary alignment */
       Cal32           = 0x0000C000,   /* 32 longword boundary alignment */
       Tap             = 0x000E0000,   /* Transmit Automatic Polling (field) */
       Dbo             = 0x00100000,   /* Descriptor Byte Ordering Mode */
       Rml             = 0x00200000,   /* Read Multiple */
};

enum {                                  /* CSR[57] - Status and Interrupt Enable */
       Ti              = 0x00000001,   /* Transmit Interrupt */
       Tps             = 0x00000002,   /* Transmit Process Stopped */
       Tu              = 0x00000004,   /* Transmit buffer Unavailable */
       Tjt             = 0x00000008,   /* Transmit Jabber Timeout */
       Unf             = 0x00000020,   /* transmit UNderFlow */
       Ri              = 0x00000040,   /* Receive Interrupt */
       Ru              = 0x00000080,   /* Receive buffer Unavailable */
       Rps             = 0x00000100,   /* Receive Process Stopped */
       Rwt             = 0x00000200,   /* Receive Watchdog Timeout */
       Eti             = 0x00000400,   /* Early Transmit Interrupt */
       Gte             = 0x00000800,   /* General purpose Timer Expired */
       Fbe             = 0x00002000,   /* Fatal Bit Error */
       Ais             = 0x00008000,   /* Abnormal Interrupt Summary */
       Nis             = 0x00010000,   /* Normal Interrupt Summary */
       Rs              = 0x000E0000,   /* Receive process State (field) */
       Ts              = 0x00700000,   /* Transmit process State (field) */
       Eb              = 0x03800000,   /* Error bits */
};

enum {                                  /* CSR6 - Operating Mode */
       Hp              = 0x00000001,   /* Hash/Perfect receive filtering mode */
       Sr              = 0x00000002,   /* Start/stop Receive */
       Ho              = 0x00000004,   /* Hash-Only filtering mode */
       Pb              = 0x00000008,   /* Pass Bad frames */
       If              = 0x00000010,   /* Inverse Filtering */
       Sb              = 0x00000020,   /* Start/stop Backoff counter */
       Pr              = 0x00000040,   /* Promiscuous Mode */
       Pm              = 0x00000080,   /* Pass all Multicast */
       Fd              = 0x00000200,   /* Full Duplex mode */
       Om              = 0x00000C00,   /* Operating Mode (field) */
       Fc              = 0x00001000,   /* Force Collision */
       St              = 0x00002000,   /* Start/stop Transmission Command */
       Tr              = 0x0000C000,   /* ThReshold control bits (field) */
       Tr128           = 0x00000000,
       Tr256           = 0x00004000,
       Tr512           = 0x00008000,
       Tr1024          = 0x0000C000,
       Ca              = 0x00020000,   /* CApture effect enable */
       Ps              = 0x00040000,   /* Port Select */
       Hbd             = 0x00080000,   /* HeartBeat Disable */
       Imm             = 0x00100000,   /* IMMediate mode */
       Sf              = 0x00200000,   /* Store and Forward */
       Ttm             = 0x00400000,   /* Transmit Threshold Mode */
       Pcs             = 0x00800000,   /* PCS function */
       Scr             = 0x01000000,   /* SCRambler mode */
       Mbo             = 0x02000000,   /* Must Be One */
       Ra              = 0x40000000,   /* Receive All */
       Sc              = 0x80000000,   /* Special Capture effect enable */

       TrMODE          = Tr512,        /* default transmission threshold */
};

enum {                                  /* CSR9 - ROM and MII Management */
       Scs             = 0x00000001,   /* serial ROM chip select */
       Sclk            = 0x00000002,   /* serial ROM clock */
       Sdi             = 0x00000004,   /* serial ROM data in */
       Sdo             = 0x00000008,   /* serial ROM data out */
       Ss              = 0x00000800,   /* serial ROM select */
       Wr              = 0x00002000,   /* write */
       Rd              = 0x00004000,   /* read */

       Mdc             = 0x00010000,   /* MII management clock */
       Mdo             = 0x00020000,   /* MII management write data */
       Mii             = 0x00040000,   /* MII management operation mode (W) */
       Mdi             = 0x00080000,   /* MII management data in */
};

enum {                                  /* CSR12 - General-Purpose Port */
       Gpc             = 0x00000100,   /* General Purpose Control */
};

typedef struct Des {
       int     status;
       int     control;
       ulong   addr;
       Msgbuf* mb;
} Des;

enum {                                  /* status */
       Of              = 0x00000001,   /* Rx: OverFlow */
       Ce              = 0x00000002,   /* Rx: CRC Error */
       Db              = 0x00000004,   /* Rx: Dribbling Bit */
       Re              = 0x00000008,   /* Rx: Report on MII Error */
       Rw              = 0x00000010,   /* Rx: Receive Watchdog */
       Ft              = 0x00000020,   /* Rx: Frame Type */
       Cs              = 0x00000040,   /* Rx: Collision Seen */
       Tl              = 0x00000080,   /* Rx: Frame too Long */
       Ls              = 0x00000100,   /* Rx: Last deScriptor */
       Fs              = 0x00000200,   /* Rx: First deScriptor */
       Mf              = 0x00000400,   /* Rx: Multicast Frame */
       Rf              = 0x00000800,   /* Rx: Runt Frame */
       Dt              = 0x00003000,   /* Rx: Data Type (field) */
       De              = 0x00004000,   /* Rx: Descriptor Error */
       Fl              = 0x3FFF0000,   /* Rx: Frame Length (field) */
       Ff              = 0x40000000,   /* Rx: Filtering Fail */

       Def             = 0x00000001,   /* Tx: DEFerred */
       Uf              = 0x00000002,   /* Tx: UnderFlow error */
       Lf              = 0x00000004,   /* Tx: Link Fail report */
       Cc              = 0x00000078,   /* Tx: Collision Count (field) */
       Hf              = 0x00000080,   /* Tx: Heartbeat Fail */
       Ec              = 0x00000100,   /* Tx: Excessive Collisions */
       Lc              = 0x00000200,   /* Tx: Late Collision */
       Nc              = 0x00000400,   /* Tx: No Carrier */
       Lo              = 0x00000800,   /* Tx: LOss of carrier */
       To              = 0x00004000,   /* Tx: Transmission jabber timeOut */

       Es              = 0x00008000,   /* [RT]x: Error Summary */
       Own             = 0x80000000,   /* [RT]x: OWN bit */
};

enum {                                  /* control */
       Bs1             = 0x000007FF,   /* [RT]x: Buffer 1 Size */
       Bs2             = 0x003FF800,   /* [RT]x: Buffer 2 Size */

       Ch              = 0x01000000,   /* [RT]x: second address CHained */
       Er              = 0x02000000,   /* [RT]x: End of Ring */

       Ft0             = 0x00400000,   /* Tx: Filtering Type 0 */
       Dpd             = 0x00800000,   /* Tx: Disabled PaDding */
       Ac              = 0x04000000,   /* Tx: Add CRC disable */
       Set             = 0x08000000,   /* Tx: SETup packet */
       Ft1             = 0x10000000,   /* Tx: Filtering Type 1 */
       Fseg            = 0x20000000,   /* Tx: First SEGment */
       Lseg            = 0x40000000,   /* Tx: Last SEGment */
       Ic              = 0x80000000,   /* Tx: Interrupt on Completion */
};

enum {                                  /* PHY registers */
       Bmcr            = 0,            /* Basic Mode Control */
       Bmsr            = 1,            /* Basic Mode Status */
       Phyidr1         = 2,            /* PHY Identifier #1 */
       Phyidr2         = 3,            /* PHY Identifier #2 */
       Anar            = 4,            /* Auto-Negotiation Advertisment */
       Anlpar          = 5,            /* Auto-Negotiation Link Partner Ability */
       Aner            = 6,            /* Auto-Negotiation Expansion */
};

enum {                                  /* Variants */
       Tulip0          = (0x0009<<16)|0x1011,
       Tulip3          = (0x0019<<16)|0x1011,
       Pnic            = (0x0002<<16)|0x11AD,
       Pnic2           = (0xC115<<16)|0x11AD,
};

typedef struct Ctlr Ctlr;
typedef struct Ctlr {
       int     port;
       Pcidev* pcidev;
       Ctlr*   next;
       int     active;
       int     id;                     /* (pcidev->did<<16)|pcidev->vid */

       uchar   srom[128];
       uchar*  sromea;                 /* MAC address */
       uchar*  leaf;
       int     sct;                    /* selected connection type */
       int     k;                      /* info block count */
       uchar*  infoblock[16];
       int     sctk;                   /* sct block index */
       int     curk;                   /* current block index */
       uchar*  type5block;

       int     phy[32];                /* logical to physical map */
       int     phyreset;               /* reset bitmap */
       int     curphyad;
       int     fdx;
       int     ttm;

       uchar   fd;                     /* option */
       int     medium;                 /* option */

       int     csr6;                   /* CSR6 - operating mode */
       int     mask;                   /* CSR[57] - interrupt mask */
       int     mbps;

       Lock    lock;

       Des*    rdr;                    /* receive descriptor ring */
       int     nrdr;                   /* size of rdr */
       int     rdrx;                   /* index into rdr */

       Lock    tlock;
       Des*    tdr;                    /* transmit descriptor ring */
       int     ntdr;                   /* size of tdr */
       int     tdrh;                   /* host index into tdr */
       int     tdri;                   /* interface index into tdr */
       int     ntq;                    /* descriptors active */
       int     ntqmax;
       Msgbuf* setupmb;

       ulong   of;                     /* receive statistics */
       ulong   ce;
       ulong   cs;
       ulong   tl;
       ulong   rf;
       ulong   de;

       ulong   ru;
       ulong   rps;
       ulong   rwt;

       ulong   uf;                     /* transmit statistics */
       ulong   ec;
       ulong   lc;
       ulong   nc;
       ulong   lo;
       ulong   to;

       ulong   tps;
       ulong   tu;
       ulong   tjt;
       ulong   unf;
} Ctlr;

static Ctlr* ctlrhead;
static Ctlr* ctlrtail;

#define csr32r(c, r)    (inl((c)->port+((r)*8)))
#define csr32w(c, r, l) (outl((c)->port+((r)*8), (ulong)(l)))

static void
attach(Ether* ether)
{
       Ctlr *ctlr;

       ctlr = ether->ctlr;
       ilock(&ctlr->lock);
       if(!(ctlr->csr6 & Sr)){
               ctlr->csr6 |= Sr;
               csr32w(ctlr, 6, ctlr->csr6);
       }
       iunlock(&ctlr->lock);
}

static void
txstart(Ether* ether)
{
       Ctlr *ctlr;
       Msgbuf *mb;
       Des *des;
       int control;

       ctlr = ether->ctlr;
       while(ctlr->ntq < (ctlr->ntdr-1)){
               if(ctlr->setupmb){
                       mb = ctlr->setupmb;
                       ctlr->setupmb = 0;
                       control = Ic|Set|mb->count;
               }
               else{
                       mb = etheroq(ether);
                       if(mb == nil)
                               break;
                       control = Ic|Lseg|Fseg|mb->count;
               }

               ctlr->tdr[PREV(ctlr->tdrh, ctlr->ntdr)].control &= ~Ic;
               des = &ctlr->tdr[ctlr->tdrh];
               des->mb = mb;
               des->addr = PADDR(mb->data);
               des->control |= control;
               ctlr->ntq++;
               coherence();
               des->status = Own;
               csr32w(ctlr, 1, 0);
               ctlr->tdrh = NEXT(ctlr->tdrh, ctlr->ntdr);
       }

       if(ctlr->ntq > ctlr->ntqmax)
               ctlr->ntqmax = ctlr->ntq;
}

static void
transmit(Ether* ether)
{
       Ctlr *ctlr;

       ctlr = ether->ctlr;
       ilock(&ctlr->tlock);
       txstart(ether);
       iunlock(&ctlr->tlock);
}

static void
interrupt(Ureg*, void* arg)
{
       Ctlr *ctlr;
       Ether *ether;
       int len, status;
       Des *des;
       Msgbuf *mb, *rmb;

       ether = arg;
       ctlr = ether->ctlr;

       while((status = csr32r(ctlr, 5)) & (Nis|Ais)){
               /*
                * Acknowledge the interrupts and mask-out
                * the ones that are implicitly handled.
                */
               csr32w(ctlr, 5, status);
               status &= (ctlr->mask & ~(Nis|Ti));

               if(status & Ais){
                       if(status & Tps)
                               ctlr->tps++;
                       if(status & Tu)
                               ctlr->tu++;
                       if(status & Tjt)
                               ctlr->tjt++;
                       if(status & Ru)
                               ctlr->ru++;
                       if(status & Rps)
                               ctlr->rps++;
                       if(status & Rwt)
                               ctlr->rwt++;
                       status &= ~(Ais|Rwt|Rps|Ru|Tjt|Tu|Tps);
               }

               /*
                * Received packets.
                */
               if(status & Ri){
                       des = &ctlr->rdr[ctlr->rdrx];
                       while(!(des->status & Own)){
                               if(des->status & Es){
                                       if(des->status & Of)
                                               ctlr->of++;
                                       if(des->status & Ce)
                                               ctlr->ce++;
                                       if(des->status & Cs)
                                               ctlr->cs++;
                                       if(des->status & Tl)
                                               ctlr->tl++;
                                       if(des->status & Rf)
                                               ctlr->rf++;
                                       if(des->status & De)
                                               ctlr->de++;
                               }
                               else if(mb = mballoc(Rbsz, 0, Mbeth1)){
                                       rmb = des->mb;
                                       rmb->count = ((des->status & Fl)>>16)-4;
                                       etheriq(ether, rmb);
                                       des->mb = mb;
                                       des->addr = PADDR(mb->data);
                               }

                               des->control &= Er;
                               des->control |= Rbsz;
                               coherence();
                               des->status = Own;

                               ctlr->rdrx = NEXT(ctlr->rdrx, ctlr->nrdr);
                               des = &ctlr->rdr[ctlr->rdrx];
                       }
                       status &= ~Ri;
               }

               /*
                * Check the transmit side:
                *      check for Transmit Underflow and Adjust
                *      the threshold upwards;
                *      free any transmitted buffers and try to
                *      top-up the ring.
                */
               if(status & Unf){
                       ctlr->unf++;
                       ilock(&ctlr->lock);
                       csr32w(ctlr, 6, ctlr->csr6 & ~St);
                       switch(ctlr->csr6 & Tr){
                       case Tr128:
                               len = Tr256;
                               break;
                       case Tr256:
                               len = Tr512;
                               break;
                       case Tr512:
                               len = Tr1024;
                               break;
                       default:
                       case Tr1024:
                               len = Sf;
                               break;
                       }
                       ctlr->csr6 = (ctlr->csr6 & ~Tr)|len;
                       csr32w(ctlr, 6, ctlr->csr6);
                       iunlock(&ctlr->lock);
                       csr32w(ctlr, 5, Tps);
                       status &= ~(Unf|Tps);
               }

               ilock(&ctlr->tlock);
               while(ctlr->ntq){
                       des = &ctlr->tdr[ctlr->tdri];
                       if(des->status & Own)
                               break;

                       if(des->status & Es){
                               if(des->status & Uf)
                                       ctlr->uf++;
                               if(des->status & Ec)
                                       ctlr->ec++;
                               if(des->status & Lc)
                                       ctlr->lc++;
                               if(des->status & Nc)
                                       ctlr->nc++;
                               if(des->status & Lo)
                                       ctlr->lo++;
                               if(des->status & To)
                                       ctlr->to++;
                       }

                       mbfree(des->mb);
                       des->control &= Er;

                       ctlr->ntq--;
                       ctlr->tdri = NEXT(ctlr->tdri, ctlr->ntdr);
               }
               txstart(ether);
               iunlock(&ctlr->tlock);

               /*
                * Anything left not catered for?
                */
               if(status)
                       panic("#l%d: status %8.8uX\n", ether->ctlrno, status);
       }
}

static void
ctlrinit(Ether* ether)
{
       Ctlr *ctlr;
       Des *des;
       Msgbuf *mb;
       int i;
       uchar bi[Easize*2];

       ctlr = ether->ctlr;

       /*
        * Allocate and initialise the receive ring;
        * allocate and initialise the transmit ring;
        * unmask interrupts and start the transmit side;
        * create and post a setup packet to initialise
        * the physical ethernet address.
        */
       ctlr->rdr = ialloc(ctlr->nrdr*sizeof(Des), 8*sizeof(ulong));
       for(des = ctlr->rdr; des < &ctlr->rdr[ctlr->nrdr]; des++){
               des->mb = mballoc(Rbsz, 0, Mbeth2);
               des->status = Own;
               des->control = Rbsz;
               des->addr = PADDR(des->mb->data);
       }
       ctlr->rdr[ctlr->nrdr-1].control |= Er;
       ctlr->rdrx = 0;
       csr32w(ctlr, 3, PADDR(ctlr->rdr));

       ctlr->tdr = ialloc(ctlr->ntdr*sizeof(Des), 8*sizeof(ulong));
       ctlr->tdr[ctlr->ntdr-1].control |= Er;
       ctlr->tdrh = 0;
       ctlr->tdri = 0;
       csr32w(ctlr, 4, PADDR(ctlr->tdr));

       /*
        * Clear any bits in the Status Register (CSR5) as
        * the PNIC has a different reset value from a true 2114x.
        */
       ctlr->mask = Nis|Ais|Fbe|Rwt|Rps|Ru|Ri|Unf|Tjt|Tps|Ti;
       csr32w(ctlr, 5, ctlr->mask);
       csr32w(ctlr, 7, ctlr->mask);
       ctlr->csr6 |= St;
       csr32w(ctlr, 6, ctlr->csr6);

       for(i = 0; i < Easize/2; i++){
               bi[i*4] = ether->ea[i*2];
               bi[i*4+1] = ether->ea[i*2+1];
               bi[i*4+2] = ether->ea[i*2+1];
               bi[i*4+3] = ether->ea[i*2];
       }
       mb = mballoc(Easize*2*16, 0, Mbeth3);
       memset(mb->data, 0xFF, sizeof(bi));
       for(i = sizeof(bi); i < sizeof(bi)*16; i += sizeof(bi))
               memmove(mb->data+i, bi, sizeof(bi));
       mb->count = sizeof(bi)*16;

       ctlr->setupmb = mb;
       transmit(ether);
}

static void
csr9w(Ctlr* ctlr, int data)
{
       csr32w(ctlr, 9, data);
       microdelay(1);
}

static int
miimdi(Ctlr* ctlr, int n)
{
       int data, i;

       /*
        * Read n bits from the MII Management Register.
        */
       data = 0;
       for(i = n-1; i >= 0; i--){
               if(csr32r(ctlr, 9) & Mdi)
                       data |= (1<<i);
               csr9w(ctlr, Mii|Mdc);
               csr9w(ctlr, Mii);
       }
       csr9w(ctlr, 0);

       return data;
}

static void
miimdo(Ctlr* ctlr, int bits, int n)
{
       int i, mdo;

       /*
        * Write n bits to the MII Management Register.
        */
       for(i = n-1; i >= 0; i--){
               if(bits & (1<<i))
                       mdo = Mdo;
               else
                       mdo = 0;
               csr9w(ctlr, mdo);
               csr9w(ctlr, mdo|Mdc);
               csr9w(ctlr, mdo);
       }
}

static int
miir(Ctlr* ctlr, int phyad, int regad)
{
       int data, i;

       if(ctlr->id == Pnic){
               i = 1000;
               csr32w(ctlr, 20, 0x60020000|(phyad<<23)|(regad<<18));
               do{
                       microdelay(1);
                       data = csr32r(ctlr, 20);
               }while((data & 0x80000000) && --i);

               if(i == 0)
                       return -1;
               return data & 0xFFFF;
       }

       /*
        * Preamble;
        * ST+OP+PHYAD+REGAD;
        * TA + 16 data bits.
        */
       miimdo(ctlr, 0xFFFFFFFF, 32);
       miimdo(ctlr, 0x1800|(phyad<<5)|regad, 14);
       data = miimdi(ctlr, 18);

       if(data & 0x10000)
               return -1;

       return data & 0xFFFF;
}

static void
miiw(Ctlr* ctlr, int phyad, int regad, int data)
{
       /*
        * Preamble;
        * ST+OP+PHYAD+REGAD+TA + 16 data bits;
        * Z.
        */
       miimdo(ctlr, 0xFFFFFFFF, 32);
       data &= 0xFFFF;
       data |= (0x05<<(5+5+2+16))|(phyad<<(5+2+16))|(regad<<(2+16))|(0x02<<16);
       miimdo(ctlr, data, 32);
       csr9w(ctlr, Mdc);
       csr9w(ctlr, 0);
}

static int
sromr(Ctlr* ctlr, int r)
{
       int i, op, data;

       if(ctlr->id == Pnic){
               i = 1000;
               csr32w(ctlr, 19, 0x600|r);
               do{
                       microdelay(1);
                       data = csr32r(ctlr, 19);
               }while((data & 0x80000000) && --i);

               return csr32r(ctlr, 9) & 0xFFFF;
       }

       /*
        * This sequence for reading a 16-bit register 'r'
        * in the EEPROM is taken straight from Section
        * 7.4 of the 21140 Hardware Reference Manual.
        */
       csr9w(ctlr, Rd|Ss);
       csr9w(ctlr, Rd|Ss|Scs);
       csr9w(ctlr, Rd|Ss|Sclk|Scs);
       csr9w(ctlr, Rd|Ss);

       op = 0x06;
       for(i = 3-1; i >= 0; i--){
               data = Rd|Ss|(((op>>i) & 0x01)<<2)|Scs;
               csr9w(ctlr, data);
               csr9w(ctlr, data|Sclk);
               csr9w(ctlr, data);
       }

       for(i = 6-1; i >= 0; i--){
               data = Rd|Ss|(((r>>i) & 0x01)<<2)|Scs;
               csr9w(ctlr, data);
               csr9w(ctlr, data|Sclk);
               csr9w(ctlr, data);
       }

       data = 0;
       for(i = 16-1; i >= 0; i--){
               csr9w(ctlr, Rd|Ss|Sclk|Scs);
               if(csr32r(ctlr, 9) & Sdo)
                       data |= (1<<i);
               csr9w(ctlr, Rd|Ss|Scs);
       }

       csr9w(ctlr, 0);

       return data & 0xFFFF;
}

static void
softreset(Ctlr* ctlr)
{
       /*
        * Soft-reset the controller and initialise bus mode.
        * Delay should be >= 50 PCI cycles (2×S @ 25MHz).
        */
       csr32w(ctlr, 0, Swr);
       microdelay(10);
       csr32w(ctlr, 0, Rml|Cal16);
       delay(1);
}

static int
type5block(Ctlr* ctlr, uchar* block)
{
       int csr15, i, len;

       /*
        * Reset or GPR sequence. Reset should be once only,
        * before the GPR sequence.
        * Note 'block' is not a pointer to the block head but
        * a pointer to the data in the block starting at the
        * reset length value so type5block can be used for the
        * sequences contained in type 1 and type 3 blocks.
        * The SROM docs state the 21140 type 5 block is the
        * same as that for the 21143, but the two controllers
        * use different registers and sequence-element lengths
        * so the 21140 code here is a guess for a real type 5
        * sequence.
        */
       len = *block++;
       if(ctlr->id != Tulip3){
               for(i = 0; i < len; i++){
                       csr32w(ctlr, 12, *block);
                       block++;
               }
               return len;
       }

       for(i = 0; i < len; i++){
               csr15 = *block++<<16;
               csr15 |= *block++<<24;
               csr32w(ctlr, 15, csr15);
               debug("%8.8uX ", csr15);
       }
       return 2*len;
}

static int
typephylink(Ctlr* ctlr, uchar*)
{
       int an, bmcr, bmsr, csr6, x;

       /*
        * Fail if
        *      auto-negotiataion enabled but not complete;
        *      no valid link established.
        */
       bmcr = miir(ctlr, ctlr->curphyad, Bmcr);
       miir(ctlr, ctlr->curphyad, Bmsr);
       bmsr = miir(ctlr, ctlr->curphyad, Bmsr);
       debug("bmcr 0x%2.2uX bmsr 0x%2.2uX\n", bmcr, bmsr);
       if(((bmcr & 0x1000) && !(bmsr & 0x0020)) || !(bmsr & 0x0004))
               return 0;

       if(bmcr & 0x1000){
               an = miir(ctlr, ctlr->curphyad, Anar);
               an &= miir(ctlr, ctlr->curphyad, Anlpar) & 0x3E0;
               debug("an 0x%2.uX 0x%2.2uX 0x%2.2uX\n",
                       miir(ctlr, ctlr->curphyad, Anar),
                       miir(ctlr, ctlr->curphyad, Anlpar),
                       an);

               if(an & 0x0100)
                       x = 0x4000;
               else if(an & 0x0080)
                       x = 0x2000;
               else if(an & 0x0040)
                       x = 0x1000;
               else if(an & 0x0020)
                       x = 0x0800;
               else
                       x = 0;
       }
       else if((bmcr & 0x2100) == 0x2100)
               x = 0x4000;
       else if(bmcr & 0x2000){
               /*
                * If FD capable, force it if necessary.
                */
               if((bmsr & 0x4000) && ctlr->fd){
                       miiw(ctlr, ctlr->curphyad, Bmcr, 0x2100);
                       x = 0x4000;
               }
               else
                       x = 0x2000;
       }
       else if(bmcr & 0x0100)
               x = 0x1000;
       else
               x = 0x0800;

       csr6 = Sc|Mbo|Hbd|Ps|Ca|Sb|TrMODE;
       if(ctlr->fdx & x)
               csr6 |= Fd;
       if(ctlr->ttm & x)
               csr6 |= Ttm;
       debug("csr6 0x%8.8uX 0x%8.8uX 0x%8.8luX\n",
               csr6, ctlr->csr6, csr32r(ctlr, 6));
       if(csr6 != ctlr->csr6){
               ctlr->csr6 = csr6;
               csr32w(ctlr, 6, csr6);
       }

       return 1;
}

static int
typephymode(Ctlr* ctlr, uchar* block, int wait)
{
       uchar *p;
       int len, mc, nway, phyx, timeo;

       if(DEBUG){
               int i;

               len = (block[0] & ~0x80)+1;
               for(i = 0; i < len; i++)
                       debug("%2.2uX ", block[i]);
               debug("\n");
       }

       if(block[1] == 1)
               len = 1;
       else if(block[1] == 3)
               len = 2;
       else
               return -1;

       /*
        * Snarf the media capabilities, nway advertisment,
        * FDX and TTM bitmaps.
        */
       p = &block[5+len*block[3]+len*block[4+len*block[3]]];
       mc = *p++;
       mc |= *p++<<8;
       nway = *p++;
       nway |= *p++<<8;
       ctlr->fdx = *p++;
       ctlr->fdx |= *p++<<8;
       ctlr->ttm = *p++;
       ctlr->ttm |= *p<<8;
       debug("mc %4.4uX nway %4.4uX fdx %4.4uX ttm %4.4uX\n",
               mc, nway, ctlr->fdx, ctlr->ttm);
       USED(mc);

       phyx = block[2];
       ctlr->curphyad = ctlr->phy[phyx];

       ctlr->csr6 = 0;//Sc|Mbo|Hbd|Ps|Ca|Sb|TrMODE;
       //csr32w(ctlr, 6, ctlr->csr6);
       if(typephylink(ctlr, block))
               return 0;

       if(!(ctlr->phyreset & (1<<phyx))){
               debug("reset seq: len %d: ", block[3]);
               if(ctlr->type5block)
                       type5block(ctlr, &ctlr->type5block[2]);
               else
                       type5block(ctlr, &block[4+len*block[3]]);
               debug("\n");
               ctlr->phyreset |= (1<<phyx);
       }

       /*
        * GPR sequence.
        */
       debug("gpr seq: len %d: ", block[3]);
       type5block(ctlr, &block[3]);
       debug("\n");

       ctlr->csr6 = 0;//Sc|Mbo|Hbd|Ps|Ca|Sb|TrMODE;
       //csr32w(ctlr, 6, ctlr->csr6);
       if(typephylink(ctlr, block))
               return 0;

       /*
        * Turn off auto-negotiation, set the auto-negotiation
        * advertisment register then start the auto-negotiation
        * process again.
        */
       miiw(ctlr, ctlr->curphyad, Bmcr, 0);
       miiw(ctlr, ctlr->curphyad, Anar, nway|1);
       miiw(ctlr, ctlr->curphyad, Bmcr, 0x1000);

       if(!wait)
               return 0;

       for(timeo = 0; timeo < 30; timeo++){
               if(typephylink(ctlr, block))
                       return 0;
               delay(100);
       }

       return -1;
}

static int
type0link(Ctlr* ctlr, uchar* block)
{
       int m, polarity, sense;

       m = (block[3]<<8)|block[2];
       sense = 1<<((m & 0x000E)>>1);
       if(m & 0x0080)
               polarity = sense;
       else
               polarity = 0;

       return (csr32r(ctlr, 12) & sense)^polarity;
}

static int
type0mode(Ctlr* ctlr, uchar* block, int wait)
{
       int csr6, m, timeo;

       csr6 = Sc|Mbo|Hbd|Ca|Sb|TrMODE;
debug("type0: medium 0x%uX, fd %d: 0x%2.2uX 0x%2.2uX 0x%2.2uX 0x%2.2uX\n",
   ctlr->medium, ctlr->fd, block[0], block[1], block[2], block[3]);
       switch(block[0]){
       default:
               break;

       case 0x04:                      /* 10BASE-TFD */
       case 0x05:                      /* 100BASE-TXFD */
       case 0x08:                      /* 100BASE-FXFD */
               /*
                * Don't attempt full-duplex
                * unless explicitly requested.
                */
               if(!ctlr->fd)
                       return -1;
               csr6 |= Fd;
               break;
       }

       m = (block[3]<<8)|block[2];
       if(m & 0x0001)
               csr6 |= Ps;
       if(m & 0x0010)
               csr6 |= Ttm;
       if(m & 0x0020)
               csr6 |= Pcs;
       if(m & 0x0040)
               csr6 |= Scr;

       csr32w(ctlr, 12, block[1]);
       microdelay(10);
       csr32w(ctlr, 6, csr6);
       ctlr->csr6 = csr6;

       if(!wait)
               return 0;

       for(timeo = 0; timeo < 30; timeo++){
               if(type0link(ctlr, block))
                       return 0;
               delay(100);
       }

       return -1;
}

static int
mediaxx(Ether* ether, int wait)
{
       Ctlr* ctlr;
       uchar *block;

       ctlr = ether->ctlr;
       block = ctlr->infoblock[ctlr->curk];
       if(block[0] & 0x80){
               switch(block[1]){
               default:
                       return -1;
               case 0:
                       if(ctlr->medium >= 0 && block[2] != ctlr->medium)
                               return 0;
/* need this test? */   if(ctlr->sct != 0x0800 && (ctlr->sct & 0x3F) != block[2])
                               return 0;
                       if(type0mode(ctlr, block+2, wait))
                               return 0;
                       break;
               case 1:
                       if(typephymode(ctlr, block, wait))
                               return 0;
                       break;
               case 3:
                       if(typephymode(ctlr, block, wait))
                               return 0;
                       break;
               }
       }
       else{
               if(ctlr->medium >= 0 && block[0] != ctlr->medium)
                       return 0;
/* need this test? */if(ctlr->sct != 0x0800 && (ctlr->sct & 0x3F) != block[0])
                       return 0;
               if(type0mode(ctlr, block, wait))
                       return 0;
       }

       if(ctlr->csr6){
               if(!(ctlr->csr6 & Ps) || (ctlr->csr6 & Ttm))
                       return 10;
               return 100;
       }

       return 0;
}

static int
media(Ether* ether, int wait)
{
       Ctlr* ctlr;
       int k, mbps;

       ctlr = ether->ctlr;
       for(k = 0; k < ctlr->k; k++){
               mbps = mediaxx(ether, wait);
               if(mbps > 0)
                       return mbps;
               if(ctlr->curk == 0)
                       ctlr->curk = ctlr->k-1;
               else
                       ctlr->curk--;
       }

       return 0;
}

static char* mediatable[9] = {
       "10BASE-T",                             /* TP */
       "10BASE-2",                             /* BNC */
       "10BASE-5",                             /* AUI */
       "100BASE-TX",
       "10BASE-TFD",
       "100BASE-TXFD",
       "100BASE-T4",
       "100BASE-FX",
       "100BASE-FXFD",
};

static uchar en1207[] = {               /* Accton EN1207-COMBO */
       0x00, 0x00, 0xE8,               /* [0]  vendor ethernet code */
       0x00,                           /* [3]  spare */

       0x00, 0x08,                     /* [4]  connection (LSB+MSB = 0x0800) */
       0x1F,                           /* [6]  general purpose control */
       2,                              /* [7]  block count */

       0x00,                           /* [8]  media code (10BASE-TX) */
       0x0B,                           /* [9]  general purpose port data */
       0x9E, 0x00,                     /* [10] command (LSB+MSB = 0x009E) */

       0x03,                           /* [8]  media code (100BASE-TX) */
       0x1B,                           /* [9]  general purpose port data */
       0x6D, 0x00,                     /* [10] command (LSB+MSB = 0x006D) */

                                       /* There is 10BASE-2 as well, but... */
};

static uchar ana6910fx[] = {            /* Adaptec (Cogent) ANA-6910FX */
       0x00, 0x00, 0x92,               /* [0]  vendor ethernet code */
       0x00,                           /* [3]  spare */

       0x00, 0x08,                     /* [4]  connection (LSB+MSB = 0x0800) */
       0x3F,                           /* [6]  general purpose control */
       1,                              /* [7]  block count */

       0x07,                           /* [8]  media code (100BASE-FX) */
       0x03,                           /* [9]  general purpose port data */
       0x2D, 0x00                      /* [10] command (LSB+MSB = 0x000D) */
};

static uchar smc9332[] = {              /* SMC 9332 */
       0x00, 0x00, 0xC0,               /* [0]  vendor ethernet code */
       0x00,                           /* [3]  spare */

       0x00, 0x08,                     /* [4]  connection (LSB+MSB = 0x0800) */
       0x1F,                           /* [6]  general purpose control */
       2,                              /* [7]  block count */

       0x00,                           /* [8]  media code (10BASE-TX) */
       0x00,                           /* [9]  general purpose port data */
       0x9E, 0x00,                     /* [10] command (LSB+MSB = 0x009E) */

       0x03,                           /* [8]  media code (100BASE-TX) */
       0x09,                           /* [9]  general purpose port data */
       0x6D, 0x00,                     /* [10] command (LSB+MSB = 0x006D) */
};

static uchar* leaf21140[] = {
       en1207,                         /* Accton EN1207-COMBO */
       ana6910fx,                      /* Adaptec (Cogent) ANA-6910FX */
       smc9332,                        /* SMC 9332 */
       nil,
};

/*
* Copied to ctlr->srom at offset 20.
*/
static uchar leafpnic[] = {
       0x00, 0x00, 0x00, 0x00,         /* MAC address */
       0x00, 0x00,
       0x00,                           /* controller 0 device number */
       0x1E, 0x00,                     /* controller 0 info leaf offset */
       0x00,                           /* reserved */
       0x00, 0x08,                     /* selected connection type */
       0x00,                           /* general purpose control */
       0x01,                           /* block count */

       0x8C,                           /* format indicator and count */
       0x01,                           /* block type */
       0x00,                           /* PHY number */
       0x00,                           /* GPR sequence length */
       0x00,                           /* reset sequence length */
       0x00, 0x78,                     /* media capabilities */
       0xE0, 0x01,                     /* Nway advertisment */
       0x00, 0x50,                     /* FDX bitmap */
       0x00, 0x18,                     /* TTM bitmap */
};

static int
srom(Ctlr* ctlr)
{
       int i, k, oui, phy, x;
       uchar *p;

       /*
        * This is a partial decoding of the SROM format described in
        * 'Digital Semiconductor 21X4 Serial ROM Format, Version 4.05,
        * 2-Mar-98'. Only the 2114[03] are handled, support for other
        * controllers can be added as needed.
        */
       for(i = 0; i < sizeof(ctlr->srom)/2; i++){
               x = sromr(ctlr, i);
               ctlr->srom[2*i] = x;
               ctlr->srom[2*i+1] = x>>8;
       }

       /*
        * There are 2 SROM layouts:
        *      e.g. Digital EtherWORKS station address at offset 20;
        *                              this complies with the 21140A SROM
        *                              application note from Digital;
        *      e.g. SMC9332            station address at offset 0 followed by
        *                              2 additional bytes, repeated at offset
        *                              6; the 8 bytes are also repeated in
        *                              reverse order at offset 8.
        * To check which it is, read the SROM and check for the repeating
        * patterns of the non-compliant cards; if that fails use the one at
        * offset 20.
        */
       ctlr->sromea = ctlr->srom;
       for(i = 0; i < 8; i++){
               x = ctlr->srom[i];
               if(x != ctlr->srom[15-i] || x != ctlr->srom[16+i]){
                       ctlr->sromea = &ctlr->srom[20];
                       break;
               }
       }

       /*
        * Fake up the SROM for the PNIC.
        * It looks like a 21140 with a PHY.
        * The MAC address is byte-swapped in the orginal SROM data.
        */
       if(ctlr->id == Pnic){
               memmove(&ctlr->srom[20], leafpnic, sizeof(leafpnic));
               for(i = 0; i < Easize; i += 2){
                       ctlr->srom[20+i] = ctlr->srom[i+1];
                       ctlr->srom[20+i+1] = ctlr->srom[i];
               }
       }

       /*
        * Next, try to find the info leaf in the SROM for media detection.
        * If it's a non-conforming card try to match the vendor ethernet code
        * and point p at a fake info leaf with compact 21140 entries.
        */
       if(ctlr->sromea == ctlr->srom){
               p = nil;
               for(i = 0; leaf21140[i] != nil; i++){
                       if(memcmp(leaf21140[i], ctlr->sromea, 3) == 0){
                               p = &leaf21140[i][4];
                               break;
                       }
               }
               if(p == nil)
                       return -1;
       }
       else
               p = &ctlr->srom[(ctlr->srom[28]<<8)|ctlr->srom[27]];

       /*
        * Set up the info needed for later media detection.
        * For the 21140, set the general-purpose mask in CSR12.
        * The info block entries are stored in order of increasing
        * precedence, so detection will work backwards through the
        * stored indexes into ctlr->srom.
        * If an entry is found which matches the selected connection
        * type, save the index. Otherwise, start at the last entry.
        * If any MII entries are found (type 1 and 3 blocks), scan
        * for PHYs.
        */
       ctlr->leaf = p;
       ctlr->sct = *p++;
       ctlr->sct |= *p++<<8;
       if(ctlr->id != Tulip3){
               csr32w(ctlr, 12, Gpc|*p++);
               delay(200);
       }
       ctlr->k = *p++;
       if(ctlr->k >= nelem(ctlr->infoblock))
               ctlr->k = nelem(ctlr->infoblock)-1;
       ctlr->sctk = ctlr->k-1;
       phy = 0;
       for(k = 0; k < ctlr->k; k++){
               ctlr->infoblock[k] = p;
               /*
                * The RAMIX PMC665 has a badly-coded SROM,
                * hence the test for 21143 and type 3.
                */
               if((*p & 0x80) || (ctlr->id == Tulip3 && *(p+1) == 3)){
                       *p |= 0x80;
                       if(*(p+1) == 1 || *(p+1) == 3)
                               phy = 1;
                       if(*(p+1) == 5)
                               ctlr->type5block = p;
                       p += (*p & ~0x80)+1;
               }
               else{
                       debug("type0: 0x%2.2uX 0x%2.2uX 0x%2.2uX 0x%2.2uX\n",
                               p[0], p[1], p[2], p[3]);
                       if(ctlr->sct != 0x0800 && *p == (ctlr->sct & 0xFF))
                               ctlr->sctk = k;
                       p += 4;
               }
       }
       ctlr->curk = ctlr->sctk;
       debug("sct 0x%uX medium 0x%uX k %d curk %d phy %d\n",
               ctlr->sct, ctlr->medium, ctlr->k, ctlr->curk, phy);

       if(phy){
               x = 0;
               for(k = 0; k < nelem(ctlr->phy); k++){
                       if((oui = miir(ctlr, k, 2)) == -1 || oui == 0)
                               continue;
                       if(DEBUG){
                               oui = (oui & 0x3FF)<<6;
                               oui |= miir(ctlr, k, 3)>>10;
                               miir(ctlr, k, 1);
                               debug("phy%d: index %d oui %uX reg1 %uX\n",
                                       x, k, oui, miir(ctlr, k, 1));
                               USED(oui);
                       }
                       ctlr->phy[x] = k;
               }
       }

       ctlr->fd = 0;
       ctlr->medium = -1;

       return 0;
}

static void
dec2114xpci(void)
{
       Ctlr *ctlr;
       Pcidev *p;
       int x;

       p = nil;
       while(p = pcimatch(p, 0, 0)){
               if(p->ccru != ((0x02<<8)|0x00))
                       continue;
               switch((p->did<<16)|p->vid){
               default:
                       continue;

               case Tulip3:                    /* 21143 */
                       /*
                        * Exit sleep mode.
                        */
                       x = pcicfgr32(p, 0x40);
                       x &= ~0xc0000000;
                       pcicfgw32(p, 0x40, x);
                       /*FALLTHROUGH*/

               case Pnic:                      /* PNIC */
               case Pnic2:                     /* PNIC-II */
               case Tulip0:                    /* 21140 */
                       break;
               }

               /*
                * bar[0] is the I/O port register address and
                * bar[1] is the memory-mapped register address.
                */
               ctlr = ialloc(sizeof(Ctlr), 0);
               ctlr->port = p->mem[0].bar & ~0x01;
               ctlr->pcidev = p;
               ctlr->id = (p->did<<16)|p->vid;

               /*
                * Some cards (e.g. ANA-6910FX) seem to need the Ps bit
                * set or they don't always work right after a hardware
                * reset.
                */
               csr32w(ctlr, 6, Mbo|Ps);
               softreset(ctlr);

               if(srom(ctlr)){
                       //free(ctlr);
                       break;
               }

               switch(ctlr->id){
               default:
                       break;

               case Pnic:                      /* PNIC */
                       /*
                        * Turn off the jabber timer.
                        */
                       csr32w(ctlr, 15, 0x00000001);
                       break;
               }

               if(ctlrhead != nil)
                       ctlrtail->next = ctlr;
               else
                       ctlrhead = ctlr;
               ctlrtail = ctlr;
       }
}

int
ether21140reset(Ether* ether)
{
       Ctlr *ctlr;
       int i, x;
       uchar ea[Easize];
       static int scandone;

       if(scandone == 0){
               dec2114xpci();
               scandone = 1;
       }

       /*
        * Any adapter matches if no ether->port is supplied,
        * otherwise the ports must match.
        */
       for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){
               if(ctlr->active)
                       continue;
               if(ether->port == 0 || ether->port == ctlr->port){
                       ctlr->active = 1;
                       break;
               }
       }
       if(ctlr == nil)
               return -1;

       ether->ctlr = ctlr;
       ether->port = ctlr->port;
       ether->irq = ctlr->pcidev->intl;
       ether->tbdf = ctlr->pcidev->tbdf;

       /*
        * Check if the adapter's station address is to be overridden.
        * If not, read it from the EEPROM and set in ether->ea prior to
        * loading the station address in the hardware.
        */
       memset(ea, 0, Easize);
       if(memcmp(ea, ether->ea, Easize) == 0)
               memmove(ether->ea, ctlr->sromea, Easize);

       /*
        * Look for a medium override in case there's no autonegotiation
        * (no MII) or the autonegotiation fails.
        */
       for(i = 0; i < ether->nopt; i++){
               if(cistrcmp(ether->opt[i], "FD") == 0){
                       ctlr->fd = 1;
                       continue;
               }
               for(x = 0; x < nelem(mediatable); x++){
                       debug("compare <%s> <%s>\n", mediatable[x],
                               ether->opt[i]);
                       if(cistrcmp(mediatable[x], ether->opt[i]))
                               continue;
                       ctlr->medium = x;

                       switch(ctlr->medium){
                       default:
                               ctlr->fd = 0;
                               break;

                       case 0x04:              /* 10BASE-TFD */
                       case 0x05:              /* 100BASE-TXFD */
                       case 0x08:              /* 100BASE-FXFD */
                               ctlr->fd = 1;
                               break;
                       }
                       break;
               }
       }

       ether->mbps = media(ether, 1);

       /*
        * Initialise descriptor rings, ethernet address.
        */
       ctlr->nrdr = Nrde;
       ctlr->ntdr = Ntde;
       pcisetbme(ctlr->pcidev);
       ctlrinit(ether);

       /*
        * Linkage to the generic ethernet driver.
        */
       ether->attach = attach;
       ether->transmit = transmit;
       ether->interrupt = interrupt;

       return 0;
}