/*
* FCCn ethernet
*/

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "imm.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"

#include "../ppc/ethermii.h"

#define DBG 1

enum {
       Nrdre           = 128,                  /* receive descriptor ring entries */
       Ntdre           = 128,                  /* transmit descriptor ring entries */

       Rbsize          = ETHERMAXTU+4,         /* ring buffer size (+4 for CRC) */
       Bufsize         = Rbsize+CACHELINESZ,   /* extra room for alignment */
};

enum {

       /* ether-specific Rx BD bits */
       RxMiss=         SBIT(7),
       RxeLG=          SBIT(10),
       RxeNO=          SBIT(11),
       RxeSH=          SBIT(12),
       RxeCR=          SBIT(13),
       RxeOV=          SBIT(14),
       RxeCL=          SBIT(15),
       RxError=        (RxeLG|RxeNO|RxeSH|RxeCR|RxeOV|RxeCL),  /* various error flags */

       /* ether-specific Tx BD bits */
       TxPad=          SBIT(1),        /* pad short frames */
       TxTC=           SBIT(5),        /* transmit CRC */
       TxeDEF=         SBIT(6),
       TxeHB=          SBIT(7),
       TxeLC=          SBIT(8),
       TxeRL=          SBIT(9),
       TxeUN=          SBIT(14),
       TxeCSL=         SBIT(15),

       /* psmr */
       CRCE=           BIT(24),        /* Ethernet CRC */
       FCE=            BIT(10),        /* flow control */
       PRO=            BIT(9),         /* promiscuous mode */
       FDE=            BIT(5),         /* full duplex ethernet */
       LPB=            BIT(3),         /* local protect bit */

       /* gfmr */
       ENET=           0xc,            /* ethernet mode */
       ENT=            BIT(27),
       ENR=            BIT(26),
       TCI=            BIT(2),

       /* FCC function code register */
       GBL=            0x20,
       BO=             0x18,
       EB=             0x10,           /* Motorola byte order */
       TC2=            0x04,
       DTB=            0x02,
       BDB=            0x01,

       /* FCC Event/Mask bits */
       GRA=            SBIT(8),
       RXC=            SBIT(9),
       TXC=            SBIT(10),
       TXE=            SBIT(11),
       RXF=            SBIT(12),
       BSY=            SBIT(13),
       TXB=            SBIT(14),
       RXB=            SBIT(15),
};

enum {          /* Mcr */
       MDIread =       0x60020000,     /* read opcode */
       MDIwrite =      0x50020000,     /* write opcode */
};

typedef struct Etherparam Etherparam;
struct Etherparam {
/*0x00*/        FCCparam;
/*0x3c*/        ulong   stat_buf;
/*0x40*/        ulong   cam_ptr;
/*0x44*/        ulong   cmask;
/*0x48*/        ulong   cpres;
/*0x4c*/        ulong   crcec;
/*0x50*/        ulong   alec;
/*0x54*/        ulong   disfc;
/*0x58*/        ushort  retlim;
/*0x5a*/        ushort  retcnt;
/*0x5c*/        ushort  p_per;
/*0x5e*/        ushort  boff_cnt;
/*0x60*/        ulong   gaddr[2];
/*0x68*/        ushort  tfcstat;
/*0x6a*/        ushort  tfclen;
/*0x6c*/        ulong   tfcptr;
/*0x70*/        ushort  mflr;
/*0x72*/        ushort  paddr[3];
/*0x78*/        ushort  ibd_cnt;
/*0x7a*/        ushort  ibd_start;
/*0x7c*/        ushort  ibd_end;
/*0x7e*/        ushort  tx_len;
/*0x80*/        uchar   ibd_base[32];
/*0xa0*/        ulong   iaddr[2];
/*0xa8*/        ushort  minflr;
/*0xaa*/        ushort  taddr[3];
/*0xb0*/        ushort  padptr;
/*0xb2*/        ushort  Rsvdb2;
/*0xb4*/        ushort  cf_range;
/*0xb6*/        ushort  max_b;
/*0xb8*/        ushort  maxd1;
/*0xba*/        ushort  maxd2;
/*0xbc*/        ushort  maxd;
/*0xbe*/        ushort  dma_cnt;
/*0xc0*/        ulong   octc;
/*0xc4*/        ulong   colc;
/*0xc8*/        ulong   broc;
/*0xcc*/        ulong   mulc;
/*0xd0*/        ulong   uspc;
/*0xd4*/        ulong   frgc;
/*0xd8*/        ulong   ospc;
/*0xdc*/        ulong   jbrc;
/*0xe0*/        ulong   p64c;
/*0xe4*/        ulong   p65c;
/*0xe8*/        ulong   p128c;
/*0xec*/        ulong   p256c;
/*0xf0*/        ulong   p512c;
/*0xf4*/        ulong   p1024c;
/*0xf8*/        ulong   cam_buf;
/*0xfc*/        ulong   Rsvdfc;
/*0x100*/
};

typedef struct Ctlr Ctlr;
struct Ctlr {
       Lock;
       int     fccid;
       int     port;
       ulong   pmdio;
       ulong   pmdck;
       int     init;
       int     active;
       int     duplex;         /* 1 == full */
       FCC*    fcc;

       Ring;
       Block*  rcvbufs[Nrdre];
       Mii*    mii;
       Timer;

       ulong   interrupts;     /* statistics */
       ulong   deferred;
       ulong   heartbeat;
       ulong   latecoll;
       ulong   retrylim;
       ulong   underrun;
       ulong   overrun;
       ulong   carrierlost;
       ulong   retrycount;
};

static  int     fccirq[] = {0x20, 0x21, 0x22};
static  int     fccid[] = {FCC1ID, FCC2ID, FCC3ID};

#ifdef DBG
ulong fccrhisto[16];
ulong fccthisto[16];
ulong fccrthisto[16];
ulong fcctrhisto[16];
ulong ehisto[0x80];
#endif

static int fccmiimir(Mii*, int, int);
static int fccmiimiw(Mii*, int, int, int);
static void fccltimer(Ureg*, Timer*);

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

       ctlr = ether->ctlr;
       ilock(ctlr);
       ctlr->active = 1;
       ctlr->fcc->gfmr |= ENR|ENT;
       iunlock(ctlr);
       ctlr->tmode = Tperiodic;
       ctlr->tf = fccltimer;
       ctlr->ta = ether;
       ctlr->tns = 5000000000LL;       /* 5 seconds */
       timeradd(ctlr);
}

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

       ctlr = ether->ctlr;
       ilock(ctlr);
       ctlr->active = 0;
       ctlr->fcc->gfmr &= ~(ENR|ENT);
       iunlock(ctlr);
       print("Ether closed\n");
}

static void
promiscuous(void* arg, int on)
{
       Ether *ether;
       Ctlr *ctlr;

       ether = (Ether*)arg;
       ctlr = ether->ctlr;

       ilock(ctlr);
       if(on || ether->nmaddr)
               ctlr->fcc->fpsmr |= PRO;
       else
               ctlr->fcc->fpsmr &= ~PRO;
       iunlock(ctlr);
}

static void
multicast(void* arg, uchar *addr, int on)
{
       Ether *ether;
       Ctlr *ctlr;

       USED(addr, on); /* if on, could SetGroupAddress; if !on, it's hard */

       ether = (Ether*)arg;
       ctlr = ether->ctlr;

       ilock(ctlr);
       if(ether->prom || ether->nmaddr)
               ctlr->fcc->fpsmr |= PRO;
       else
               ctlr->fcc->fpsmr &= ~PRO;
       iunlock(ctlr);
}

static void
txstart(Ether *ether)
{
       int len;
       Ctlr *ctlr;
       Block *b;
       BD *dre;

       ctlr = ether->ctlr;
       if(ctlr->init)
               return;
       while(ctlr->ntq < Ntdre-1){
               b = qget(ether->oq);
               if(b == 0)
                       break;

               dre = &ctlr->tdr[ctlr->tdrh];
               dczap(dre, sizeof(BD));
               if(dre->status & BDReady)
                       panic("ether: txstart");

               /*
                * Give ownership of the descriptor to the chip, increment the
                * software ring descriptor pointer and tell the chip to poll.
                */
               len = BLEN(b);
               if(ctlr->txb[ctlr->tdrh] != nil)
                       panic("fcc/ether: txstart");
               ctlr->txb[ctlr->tdrh] = b;
               if((ulong)b->rp&1)
                       panic("fcc/ether: txstart align");      /* TO DO: ensure alignment */
               dre->addr = PADDR(b->rp);
               dre->length = len;
               dcflush(b->rp, len);
               dcflush(dre, sizeof(BD));
               dre->status = (dre->status & BDWrap) | BDReady|TxPad|BDInt|BDLast|TxTC;
               dcflush(dre, sizeof(BD));
/*              ctlr->fcc->ftodr = 1<<15;       /* transmit now; Don't do this according to errata */
               ctlr->ntq++;
               ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre);
       }
}

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

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

static void
interrupt(Ureg*, void *arg)
{
       int len, status, rcvd, xmtd, restart;
       ushort events;
       Ctlr *ctlr;
       BD *dre;
       Block *b, *nb;
       Ether *ether = arg;

       ctlr = ether->ctlr;
       if(!ctlr->active)
               return; /* not ours */

       /*
        * Acknowledge all interrupts and whine about those that shouldn't
        * happen.
        */
       events = ctlr->fcc->fcce;
       ctlr->fcc->fcce = events;               /* clear events */

#ifdef DBG
       ehisto[events & 0x7f]++;
#endif

       ctlr->interrupts++;

       if(events & BSY)
               ctlr->overrun++;
       if(events & TXE)
               ether->oerrs++;

#ifdef DBG
       rcvd = xmtd = 0;
#endif
       /*
        * Receiver interrupt: run round the descriptor ring logging
        * errors and passing valid receive data up to the higher levels
        * until we encounter a descriptor still owned by the chip.
        */
       if(events & RXF){
               dre = &ctlr->rdr[ctlr->rdrx];
               dczap(dre, sizeof(BD));
               while(((status = dre->status) & BDEmpty) == 0){
                       rcvd++;
                       if(status & RxError || (status & (BDFirst|BDLast)) != (BDFirst|BDLast)){
                               if(status & (RxeLG|RxeSH))
                                       ether->buffs++;
                               if(status & RxeNO)
                                       ether->frames++;
                               if(status & RxeCR)
                                       ether->crcs++;
                               if(status & RxeOV)
                                       ether->overflows++;
                               print("eth rx: %ux\n", status);
                       }else{
                               /*
                                * We have a packet. Read it in.
                                */
                               len = dre->length-4;
                               b = ctlr->rcvbufs[ctlr->rdrx];
                               assert(dre->addr == PADDR(b->rp));
                               dczap(b->rp, len);
                               if(nb = iallocb(Bufsize)){
                                       b->wp += len;
                                       etheriq(ether, b);
                                       b = nb;
                                       b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1));
                                       b->wp = b->rp;
                                       ctlr->rcvbufs[ctlr->rdrx] = b;
                                       ctlr->rdr[ctlr->rdrx].addr = PADDR(b->wp);
                               }else
                                       ether->soverflows++;
                       }

                       /*
                        * Finished with this descriptor, reinitialise it,
                        * give it back to the chip, then on to the next...
                        */
                       dre->length = 0;
                       dre->status = (status & BDWrap) | BDEmpty | BDInt;
                       dcflush(dre, sizeof(BD));

                       ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
                       dre = &ctlr->rdr[ctlr->rdrx];
                       dczap(dre, sizeof(BD));
               }
       }

       /*
        * Transmitter interrupt: handle anything queued for a free descriptor.
        */
       if(events & (TXB|TXE)){
               ilock(ctlr);
               restart = 0;
               while(ctlr->ntq){
                       dre = &ctlr->tdr[ctlr->tdri];
                       dczap(dre, sizeof(BD));
                       status = dre->status;
                       if(status & BDReady)
                               break;
                       if(status & TxeDEF)
                               ctlr->deferred++;
                       if(status & TxeHB)
                               ctlr->heartbeat++;
                       if(status & TxeLC)
                               ctlr->latecoll++;
                       if(status & TxeRL)
                               ctlr->retrylim++;
                       if(status & TxeUN)
                               ctlr->underrun++;
                       if(status & TxeCSL)
                               ctlr->carrierlost++;
                       if(status & (TxeLC|TxeRL|TxeUN))
                               restart = 1;
                       ctlr->retrycount += (status>>2)&0xF;
                       b = ctlr->txb[ctlr->tdri];
                       if(b == nil)
                               panic("fcce/interrupt: bufp");
                       ctlr->txb[ctlr->tdri] = nil;
                       freeb(b);
                       ctlr->ntq--;
                       ctlr->tdri = NEXT(ctlr->tdri, Ntdre);
                       xmtd++;
               }

               if(restart){
                       ctlr->fcc->gfmr &= ~ENT;
                       delay(10);
                       ctlr->fcc->gfmr |= ENT;
                       cpmop(RestartTx, ctlr->fccid, 0xc);
               }
               txstart(ether);
               iunlock(ctlr);
       }
#ifdef DBG
       if(rcvd >= nelem(fccrhisto))
               rcvd = nelem(fccrhisto) - 1;
       if(xmtd >= nelem(fccthisto))
               xmtd = nelem(fccthisto) - 1;
       if(rcvd)
               fcctrhisto[xmtd]++;
       else
               fccthisto[xmtd]++;
       if(xmtd)
               fccrthisto[rcvd]++;
       else
               fccrhisto[rcvd]++;
#endif
}

static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
       char *p;
       int len, i, r;
       Ctlr *ctlr;
       MiiPhy *phy;

       if(n == 0)
               return 0;

       ctlr = ether->ctlr;

       p = malloc(READSTR);
       len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
       len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost);
       len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat);
       len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim);
       len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount);
       len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll);
       len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun);
       len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun);
       len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred);
       miistatus(ctlr->mii);
       phy = ctlr->mii->curphy;
       len += snprint(p+len, READSTR-len, "phy: link=%d, tfc=%d, rfc=%d, speed=%d, fd=%d\n",
               phy->link, phy->tfc, phy->rfc, phy->speed, phy->fd);

#ifdef DBG
       if(ctlr->mii != nil && ctlr->mii->curphy != nil){
               len += snprint(p+len, READSTR, "phy:   ");
               for(i = 0; i < NMiiPhyr; i++){
                       if(i && ((i & 0x07) == 0))
                               len += snprint(p+len, READSTR-len, "\n       ");
                       r = miimir(ctlr->mii, i);
                       len += snprint(p+len, READSTR-len, " %4.4uX", r);
               }
               snprint(p+len, READSTR-len, "\n");
       }
#endif
       snprint(p+len, READSTR-len, "\n");

       n = readstr(offset, a, n, p);
       free(p);

       return n;
}

/*
* This follows the MPC8260 user guide: section28.9's initialisation sequence.
*/
static int
fccsetup(Ctlr *ctlr, FCC *fcc, uchar *ea)
{
       int i;
       Etherparam *p;
       MiiPhy *phy;

       /* Turn Ethernet off */
       fcc->gfmr &= ~(ENR | ENT);

       ioplock();
       switch(ctlr->port) {
       default:
               iopunlock();
               return -1;
       case 0:
               /* Step 1 (Section 28.9), write the parallel ports */
               ctlr->pmdio = 0x01000000;
               ctlr->pmdck = 0x08000000;
               imm->port[0].pdir &= ~A1dir0;
               imm->port[0].pdir |= A1dir1;
               imm->port[0].psor &= ~A1psor0;
               imm->port[0].psor |= A1psor1;
               imm->port[0].ppar |= (A1dir0 | A1dir1);
               /* Step 2, Port C clocks */
               imm->port[2].psor &= ~0x00000c00;
               imm->port[2].pdir &= ~0x00000c00;
               imm->port[2].ppar |= 0x00000c00;
               imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
               imm->port[3].podr |= ctlr->pmdio;
               imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
               imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
               eieio();
               /* Step 3, Serial Interface clock routing */
               imm->cmxfcr &= ~0xff000000;     /* Clock mask */
               imm->cmxfcr |= 0x37000000;      /* Clock route */
               break;

       case 1:
               /* Step 1 (Section 28.9), write the parallel ports */
               ctlr->pmdio = 0x00400000;
               ctlr->pmdck = 0x00200000;
               imm->port[1].pdir &= ~B2dir0;
               imm->port[1].pdir |= B2dir1;
               imm->port[1].psor &= ~B2psor0;
               imm->port[1].psor |= B2psor1;
               imm->port[1].ppar |= (B2dir0 | B2dir1);
               /* Step 2, Port C clocks */
               imm->port[2].psor &= ~0x00003000;
               imm->port[2].pdir &= ~0x00003000;
               imm->port[2].ppar |= 0x00003000;

               imm->port[2].pdat |= (ctlr->pmdio | ctlr->pmdck);
               imm->port[2].podr |= ctlr->pmdio;
               imm->port[2].pdir |= (ctlr->pmdio | ctlr->pmdck);
               imm->port[2].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
               eieio();
               /* Step 3, Serial Interface clock routing */
               imm->cmxfcr &= ~0x00ff0000;
               imm->cmxfcr |= 0x00250000;
               break;

       case 2:
               /* Step 1 (Section 28.9), write the parallel ports */
               imm->port[1].pdir &= ~B3dir0;
               imm->port[1].pdir |= B3dir1;
               imm->port[1].psor &= ~B3psor0;
               imm->port[1].psor |= B3psor1;
               imm->port[1].ppar |= (B3dir0 | B3dir1);
               /* Step 2, Port C clocks */
               imm->port[2].psor &= ~0x0000c000;
               imm->port[2].pdir &= ~0x0000c000;
               imm->port[2].ppar |= 0x0000c000;
               imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
               imm->port[3].podr |= ctlr->pmdio;
               imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
               imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
               eieio();
               /* Step 3, Serial Interface clock routing */
               imm->cmxfcr &= ~0x0000ff00;
               imm->cmxfcr |= 0x00003700;
               break;
       }
       iopunlock();

       p = (Etherparam*)(m->immr->prmfcc + ctlr->port);
       memset(p, 0, sizeof(Etherparam));

       /* Step 4 */
       fcc->gfmr |= ENET;

       /* Step 5 */
       fcc->fpsmr = CRCE | FDE | LPB;  /* full duplex operation */
       ctlr->duplex = ~0;

       /* Step 6 */
       fcc->fdsr = 0xd555;

       /* Step 7, initialize parameter ram */
       p->rbase = PADDR(ctlr->rdr);
       p->tbase = PADDR(ctlr->tdr);
       p->rstate = (GBL | EB) << 24;
       p->tstate = (GBL | EB) << 24;

       p->cmask = 0xdebb20e3;
       p->cpres = 0xffffffff;

       p->retlim = 15; /* retry limit */

       p->mrblr = (Rbsize+0x1f)&~0x1f;         /* multiple of 32 */
       p->mflr = Rbsize;
       p->minflr = ETHERMINTU;
       p->maxd1 = (Rbsize+7) & ~7;
       p->maxd2 = (Rbsize+7) & ~7;

       for(i=0; i<Eaddrlen; i+=2)
               p->paddr[2-i/2] = (ea[i+1]<<8)|ea[i];

       /* Step 7, initialize parameter ram, configuration-dependent values */
       p->riptr = m->immr->fccextra[ctlr->port].ri - (uchar*)IMMR;
       p->tiptr = m->immr->fccextra[ctlr->port].ti - (uchar*)IMMR;
       p->padptr = m->immr->fccextra[ctlr->port].pad - (uchar*)IMMR;
       memset(m->immr->fccextra[ctlr->port].pad, 0x88, 0x20);

       /* Step 8, clear out events */
       fcc->fcce = ~0;

       /* Step 9, Interrupt enable */
       fcc->fccm = TXE | RXF | TXB;

       /* Step 10, Configure interrupt priority (not done here) */
       /* Step 11, Clear out current events */
       /* Step 12, Enable interrupts to the CP interrupt controller */

       /* Step 13, Issue the Init Tx and Rx command, specifying 0xc for ethernet*/
       cpmop(InitRxTx, fccid[ctlr->port], 0xc);

       /* Step 14, Link management */
       if((ctlr->mii = malloc(sizeof(Mii))) == nil)
               return -1;
       ctlr->mii->mir = fccmiimir;
       ctlr->mii->miw = fccmiimiw;
       ctlr->mii->ctlr = ctlr;

       if(mii(ctlr->mii, ~0) == 0 || (phy = ctlr->mii->curphy) == nil){
               free(ctlr->mii);
               ctlr->mii = nil;
               return -1;
       }
       miiane(ctlr->mii, ~0, ~0, ~0);
#ifdef DBG
       print("oui=%X, phyno=%d, ", phy->oui, phy->phyno);
       print("anar=%ux, ", phy->anar);
       print("fc=%ux, ", phy->fc);
       print("mscr=%ux, ", phy->mscr);

       print("link=%ux, ", phy->link);
       print("speed=%ux, ", phy->speed);
       print("fd=%ux, ", phy->fd);
       print("rfc=%ux, ", phy->rfc);
       print("tfc=%ux\n", phy->tfc);
#endif
       /* Step 15, Enable ethernet: done at attach time */
       return 0;
}

static int
reset(Ether* ether)
{
       uchar ea[Eaddrlen];
       Ctlr *ctlr;
       FCC *fcc;
       Block *b;
       int i;

       if(m->cpuhz < 24000000){
               print("%s ether: system speed must be >= 24MHz for ether use\n", ether->type);
               return -1;
       }

       if(ether->port > 3){
               print("%s ether: no FCC port %ld\n", ether->type, ether->port);
               return -1;
       }
       ether->irq = fccirq[ether->port];
       ether->tbdf = BusPPC;
       fcc = imm->fcc + ether->port;

       ctlr = malloc(sizeof(*ctlr));
       ether->ctlr = ctlr;
       memset(ctlr, 0, sizeof(*ctlr));
       ctlr->fcc = fcc;
       ctlr->port = ether->port;
       ctlr->fccid = fccid[ether->port];

       /* Ioringinit will allocate the buffer descriptors in normal memory
        * and NOT in Dual-Ported Ram, as prescribed by the MPC8260
        * PowerQUICC II manual (Section 28.6).  When they are allocated
        * in DPram and the Dcache is enabled, the processor will hang
        */
       if(ioringinit(ctlr, Nrdre, Ntdre, 0) < 0)
               panic("etherfcc init");
       for(i = 0; i < Nrdre; i++){
               b = iallocb(Bufsize);
               b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1));
               b->wp = b->rp;
               ctlr->rcvbufs[i] = b;
               ctlr->rdr[i].addr = PADDR(b->wp);
       }

       fccsetup(ctlr, fcc, ether->ea);

       ether->mbps = 100;      /* TO DO: could be 10mbps */
       ether->attach = attach;
       ether->transmit = transmit;
       ether->ifstat = ifstat;

       ether->arg = ether;
       ether->promiscuous = promiscuous;
       ether->multicast = multicast;

       /*
        * Until we know where to find it, insist that the plan9.ini
        * entry holds the Ethernet address.
        */
       memset(ea, 0, Eaddrlen);
       if(memcmp(ea, ether->ea, Eaddrlen) == 0){
               print("no ether address");
               return -1;
       }

       intrenable(ether->irq, interrupt, ether, ether->name);

       return 0;
}

void
etherfcclink(void)
{
       addethercard("fcc", reset);
}

static void
nanodelay(void)
{
       static int count;
       int i;

       for(i = 0; i < 500; i++)
               count++;
       return;
}

static
void miiwriteloop(Ctlr *ctlr, Port *port, int cnt, ulong cmd)
{
       int i;

       for(i = 0; i < cnt; i++){
               port->pdat &= ~ctlr->pmdck;
               if(cmd & BIT(i))
                       port->pdat |= ctlr->pmdio;
               else
                       port->pdat &= ~ctlr->pmdio;
               nanodelay();
               port->pdat |= ctlr->pmdck;
               nanodelay();
       }
}

static int
fccmiimiw(Mii *mii, int pa, int ra, int data)
{
       int x;
       Port *port;
       ulong cmd;
       Ctlr *ctlr;

       /*
        * MII Management Interface Write.
        */

       ctlr = mii->ctlr;
       port = imm->port + 3;
       cmd = MDIwrite | (pa<<(5+2+16))| (ra<<(2+16)) | (data & 0xffff);

       x = splhi();

       port->pdir |= (ctlr->pmdio|ctlr->pmdck);
       nanodelay();

       miiwriteloop(ctlr, port, 32, ~0);
       miiwriteloop(ctlr, port, 32, cmd);

       port->pdir |= (ctlr->pmdio|ctlr->pmdck);
       nanodelay();

       miiwriteloop(ctlr, port, 32, ~0);

       splx(x);
       return 1;
}

static int
fccmiimir(Mii *mii, int pa, int ra)
{
       int data, i, x;
       Port *port;
       ulong cmd;
       Ctlr *ctlr;

       ctlr = mii->ctlr;
       port = imm->port + 3;

       cmd = MDIread | pa<<(5+2+16) | ra<<(2+16);

       x = splhi();
       port->pdir |= (ctlr->pmdio|ctlr->pmdck);
       nanodelay();

       miiwriteloop(ctlr, port, 32, ~0);

       /* Clock out the first 14 MS bits of the command */
       miiwriteloop(ctlr, port, 14, cmd);

       /* Turn-around */
       port->pdat &= ~ctlr->pmdck;
       port->pdir &= ~ctlr->pmdio;
       nanodelay();

       /* For read, clock in 18 bits, use 16 */
       data = 0;
       for(i=0; i<18; i++){
               data <<= 1;
               if(port->pdat & ctlr->pmdio)
                       data |= 1;
               port->pdat |= ctlr->pmdck;
               nanodelay();
               port->pdat &= ~ctlr->pmdck;
               nanodelay();
       }
       port->pdir |= (ctlr->pmdio|ctlr->pmdck);
       nanodelay();
       miiwriteloop(ctlr, port, 32, ~0);
       splx(x);
       return data & 0xffff;
}

static void
fccltimer(Ureg*, Timer *t)
{
       Ether *ether;
       Ctlr *ctlr;
       MiiPhy *phy;
       ulong gfmr;

       ether = t->ta;
       ctlr = ether->ctlr;
       if(ctlr->mii == nil || ctlr->mii->curphy == nil)
               return;
       phy = ctlr->mii->curphy;
       if(miistatus(ctlr->mii) < 0){
               print("miistatus failed\n");
               return;
       }
       if(phy->link == 0){
               print("link lost\n");
               return;
       }
       ether->mbps = phy->speed;

       if(phy->fd != ctlr->duplex)
               print("set duplex\n");
       ilock(ctlr);
       gfmr = ctlr->fcc->gfmr;
       if(phy->fd != ctlr->duplex){
               ctlr->fcc->gfmr &= ~(ENR|ENT);
               if(phy->fd)
                       ctlr->fcc->fpsmr |= FDE | LPB;          /* full duplex operation */
               else
                       ctlr->fcc->fpsmr &= ~(FDE | LPB);       /* half duplex operation */
               ctlr->duplex = phy->fd;
       }
       ctlr->fcc->gfmr = gfmr;
       iunlock(ctlr);
}