diff options
Diffstat (limited to 'os/cerf250/ether91c111.c')
| -rw-r--r-- | os/cerf250/ether91c111.c | 1056 |
1 files changed, 1056 insertions, 0 deletions
diff --git a/os/cerf250/ether91c111.c b/os/cerf250/ether91c111.c new file mode 100644 index 00000000..2a404d93 --- /dev/null +++ b/os/cerf250/ether91c111.c @@ -0,0 +1,1056 @@ +/* + * SMsC 91c111 ethernet controller + * Copyright © 2001,2004 Vita Nuova Holdings Limited. All rights reserved. + * + * TO DO: + * - use ethermii + * - use DMA where available + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/netif.h" + +#include "etherif.h" + +/* + * chip definitions + */ + +typedef struct Ctlr Ctlr; + +enum { + SMSC91C11x, + SMSC91C110, + SMSC91C111, + SMSC91C96, +}; + +struct Ctlr { + Lock; + uchar *base; + int type; + int rev; + int hasmii; + int phyad; + int bank; /* currently selected bank */ + Block* waiting; /* waiting for space in FIFO */ + + ulong collisions; + ulong toolongs; + ulong tooshorts; + ulong aligns; + ulong txerrors; + int oddworks; + int bus32bit; +}; + +#define MKREG(bank, off) ((bank << 8) | (off)) + +enum { + /* Bank 0 */ + Tcr= MKREG(0, 0), /* transmit control */ + TcrSwfdup= 1<<15, /* switched full duplex */ + TcrEphLoop= 1<<13, /* internal loopback */ + TcrStpSqet= 1<<12, /* stop transmission on SQET error */ + TcrFduplx= 1<<11, /* enable full duplex */ + TcrMonCsn= 1<<10, /* monitor collision (0 for MII operation) */ + TcrNoCRC= 1<<8, /* don't add CRC */ + TcrPadEn= 1<<7, /* pad short frames */ + TcrForceCol= 1<<2, /* force collision */ + TcrLoop= 1<<1, /* PHY loopback */ + TcrTxena= 1<<0, /* enable transmitter */ + Eph= MKREG(0, 2), /* there are more bits but we don't use them */ + EphLinkOk= 1<<14, + EphCtrRol= 1<<12, /* counter roll over; clear by reading Ecr */ + Rcr= MKREG(0, 4), /* receive control */ + RcrSoftRst= 1<<15, + RcrFiltCar= 1<<14, + RcrAbortEnb= 1<<13, + RcrStripCRC= 1<<9, + RcrRxEn= 1<<8, + RcrAlmul= 1<<2, /* ~=0, accept all multicast frames (=0, match multicast table) */ + RcrPrms= 1<<1, /* promiscuous mode */ + RcrRxAbort= 1<<0, /* set if receive frame longer than 2k bytes */ + Ecr= MKREG(0, 6), /* counter */ + EcrExcDeferred= 0xF<<12, /* excessively deferred Tx */ + EcrDeferred= 0xF<<8, /* deferred Tx */ + EcrMultCol= 0xF<<4, /* multiple collisions */ + EcrCollision= 0xF<<0, /* single collision */ + Mir= MKREG(0, 8), /* memory information */ + Mcr= MKREG(0, 0xA), /* memory config (91cxx) */ + Rpcr= Mcr, /* receive/phy control (91c111) */ + + /* Bank 1 */ + Config= MKREG(1, 0), + CfgMiiSelect= 1<<15, /* 91c110 */ + CfgEphPowerEn= CfgMiiSelect, /* =1, powered (after reset MMU); =0, low power mode (91c111) */ + CfgNoWait= 1<<12, /* don't request additional wait states */ + CfgSetSqlch= 1<<9, /* 91cxx */ + CfgGpcntrl= 1<<9, /* general purpose output (CNTRL), perhaps power-enable (91c111) */ + CfgAuiSelect= 1<<8, /* 91cxx */ + CfgExtPhy= 1<<8, /* enable external PHY/MII (91c111) */ + Cfg16Bit= 1<<7, /* 91cxx */ + BaseAddress= MKREG(1, 2), + Iaddr0_1= MKREG(1, 4), + Iaddr2_3= MKREG(1, 6), + Iaddr4_5= MKREG(1, 8), + Gpr= MKREG(1, 0xA), /* general purpose reg (EEPROM interface) */ + Control= MKREG(1, 0xC), /* control register */ + CtlRcvBad= 1<<14, /* allow bad CRC packets through */ + CtlAutoRelease= 1<<11, /* transmit pages released automatically w/out interrupt */ + CtlLeEnable= 1<<7, /* link error enable */ + CtlCrEnable= 1<<6, /* counter roll over enable */ + CtlTeEnable= 1<<5, /* transmit error enable */ + CtlEeSelect= 1<<2, /* EEPROM select */ + CtlReload= 1<<1, /* read EEPROM and update relevant registers */ + CtlStore= 1<<0, /* store relevant registers in EEPROM */ + + /* Bank 2 */ + Mmucr= MKREG(2, 0), /* MMU command */ + McrAllocTx= 1<<5, /* allocate space for outgoing packet */ + McrReset= 2<<5, /* reset to initial state */ + McrReadFIFO= 3<<5, /* remove frame from top of FIFO */ + McrRemove= 4<<5, /* remove and release top of Rx FIFO */ + McrFreeTx= 5<<5, /* release specific packet (eg, packets done Tx) */ + McrEnqueue= 6<<5, /* enqueue packet number to Tx FIFO */ + McrResetTx= 7<<5, /* reset both Tx FIFOs */ + McrBusy= 1<<0, + ArrPnr= MKREG(2, 2), /* Pnr (low byte), Arr (high byte) */ + ArrFailed= 1<<15, + FifoPorts= MKREG(2, 4), + FifoRxEmpty= 1<<15, + FifoTxEmpty= 1<<7, + Pointer= MKREG(2, 6), + PtrRcv= 1<<15, + PtrAutoIncr= 1<<14, + PtrRead= 1<<13, + PtrEtEn= 1<<12, + PtrNotEmpty= 1<<11, + Data= MKREG(2, 8), + Interrupt= MKREG(2, 0xC), /* status/ack (low byte), mask (high byte) */ + IntMii= 1<<7, /* PHY/MII state change */ + IntErcv= 1<<6, /* early receive interrupt (received > Ercv threshold) */ + IntEph= 1<<5, /* ethernet protocol interrupt */ + IntRxOvrn= 1<<4, /* overrun */ + IntAlloc= 1<<3, /* allocation complete */ + IntTxEmpty= 1<<2, /* TX FIFO now empty */ + IntTx= 1<<1, /* transmit done */ + IntRcv= 1<<0, /* packet received */ + IntrMask= MKREG(2, 0xD), + IntrMaskShift= 8, /* shift for Int... values to mask position in 16-bit register */ + IntrMaskField= 0xFF00, + + /* Bank 3 */ + Mt0_1= MKREG(3, 0), /* multicast table */ + Mt2_3= MKREG(3, 2), + Mt4_5= MKREG(3, 4), + Mt6_7= MKREG(3, 6), + Mgmt= MKREG(3, 8), /* management interface (MII) */ + MgmtMdo= 1<<0, /* MDO pin */ + MgmtMdi= 1<<1, /* MDI pin */ + MgmtMclk= 1<<2, /* drive MDCLK */ + MgmtMdoEn= 1<<3, /* MDO driven when high, tri-stated when low */ + Revision= MKREG(3, 0xA), + Ercv= MKREG(3, 0xC), /* early receive */ + + /* Bank 4 (91cxx only) */ + EcsrEcor= MKREG(4, 0), /* status and option registers */ + + /* all banks */ + BankSelect= MKREG(0, 0xe), +}; + +enum { + /* receive frame status word (p 38) */ + RsAlgnErr= 1<<15, + RsBroadcast= 1<<14, + RsBadCRC= 1<<13, + RsOddFrame= 1<<12, + RsTooLong= 1<<11, + RsTooShort= 1<<10, + RsMulticast= 1<<1, + RsError= RsBadCRC | RsAlgnErr | RsTooLong | RsTooShort, + + Framectlsize= 6, +}; + +static void miiw(Ctlr *ctlr, int regad, int val); +static int miir(Ctlr *ctlr, int regad); + +/* + * architecture dependent section - collected here in case + * we want to port the driver + */ + +#define PHYMIIADDR_91C110 3 +#define PHYMIIADDR_91C111 0 + +#define llregr(ctlr, reg) (*(ushort*)(ctlr->base + (reg))) +#define llregr32(ctlr, reg) (*(ulong*)(ctlr->base + (reg))) +#define llregw(ctlr, reg, val) (*(ushort*)(ctlr->base + (reg)) = (val)) + +static void +adinit(Ether *ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + // TODO: code to turn on device clocks + ctlr->base = (uchar*)mmuphysmap(PHYSCS1, 0x100000) + ether->port; +iprint("adinit: %8.8lux -> %8.8lux mcs0=%8.8lux\n", (ulong)ctlr->base, PADDR(ctlr->base), MEMCFGREG->msc0); +{ulong v; v = *(ulong*)ctlr->base; iprint("value=%8.8lux\n", v);} + ctlr->bus32bit = 1; +} + +static void +adsetfd(Ctlr *ctlr) +{ + miiw(ctlr, 0x18, miir(ctlr, 0x18) | (1 << 5)); +} + +/* + * architecture independent section + */ + +static ushort +regr(Ctlr *ctlr, int reg) +{ + int bank; + ushort val; + + bank = reg >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + val = llregr(ctlr, reg & 0xff); + return val; +} + +static ulong +regr32(Ctlr *ctlr, int reg) +{ + int bank; + ulong val; + + bank = reg >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + val = llregr32(ctlr, reg & 0xff); + return val; +} + +static void +regw(Ctlr *ctlr, int reg, ushort val) +{ + int bank; + + bank = reg >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + llregw(ctlr, reg & 0xff, val); +} + +static void +regwdatam(Ctlr *ctlr, ushort *data, int ns) +{ + int bank; + ushort *faddr; + + bank = Data >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + faddr = (ushort*)(ctlr->base + (Data & 0xff)); + while(ns-- > 0){ + *faddr = *data; + data++; + } +} + +static void +regrdatam(Ctlr *ctlr, void *data, int nb) +{ + int bank; + ushort *f, *t; + int laps, ns; + + bank = Data >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + + if((ulong)data & 3) + iprint("bad buffer alignment\n"); + + t = data; + f = (ushort*)(ctlr->base + (Data & 0xff)); + ns = nb >> 1; + laps = ns / 8; + switch(ns & 7){ /* Duff's device */ + do { + *t++ = *f; + case 7: *t++ = *f; + case 6: *t++ = *f; + case 5: *t++ = *f; + case 4: *t++ = *f; + case 3: *t++ = *f; + case 2: *t++ = *f; + case 1: *t++ = *f; + case 0: + ; + } while(laps-- > 0); + } +} + +static void +regrdatam32(Ctlr *ctlr, void *data, int nb) +{ + int bank; + ulong *f, *t; + int laps, nw; + + bank = Data >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + + if((ulong)data & 3) + iprint("bad buffer alignment\n"); + + t = data; + f = (ulong*)(ctlr->base + (Data & 0xff)); + nw = nb>>2; + laps = nw / 8; + switch(nw & 7){ /* Duff's device */ + do { + *t++ = *f; + case 7: *t++ = *f; + case 6: *t++ = *f; + case 5: *t++ = *f; + case 4: *t++ = *f; + case 3: *t++ = *f; + case 2: *t++ = *f; + case 1: *t++ = *f; + case 0: + ; + } while(laps-- > 0); + } +} + +static void +regor(Ctlr *ctlr, int reg, ushort val) +{ + int bank; + + bank = reg >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + reg &= 0xff; + llregw(ctlr, reg, llregr(ctlr, reg) | val); +} + +static void +regclear(Ctlr *ctlr, int reg, ushort val) +{ + int bank; + + bank = reg >> 8; + if(ctlr->bank != bank){ + ctlr->bank = bank; + llregw(ctlr, BankSelect, bank); + } + reg &= 0xff; + llregw(ctlr, reg, llregr(ctlr, reg) & ~val); +} + +static long +ifstat(Ether* ether, void* a, long n, ulong offset) +{ + Ctlr *ctlr; + char *p; + int len; + + if(n == 0) + return 0; + + ctlr = ether->ctlr; + p = smalloc(READSTR); + if(waserror()){ + free(p); + nexterror(); + } + len = snprint(p, READSTR, "Overflow: %ud\n", ether->overflows); + len += snprint(p+len, READSTR, "Soft Overflow: %ud\n", ether->soverflows); + len += snprint(p+len, READSTR, "Transmit Error: %lud\n", ctlr->txerrors); + len += snprint(p+len, READSTR-len, "CRC Error: %ud\n", ether->crcs); + len += snprint(p+len, READSTR-len, "Collision: %lud\n", ctlr->collisions); + len += snprint(p+len, READSTR-len, "Align: %lud\n", ctlr->aligns); + len += snprint(p+len, READSTR-len, "Too Long: %lud\n", ctlr->toolongs); + snprint(p+len, READSTR-len, "Too Short: %lud\n", ctlr->tooshorts); + + n = readstr(offset, a, n, p); + poperror(); + free(p); + + return n; +} + +static void +promiscuous(void* arg, int on) +{ + Ether *ether; + Ctlr *ctlr; + int r; + + ether = arg; + ctlr = ether->ctlr; + ilock(ctlr); + r = regr(ctlr, Rcr); + if(on) + r |= RcrPrms; + else + r &= ~RcrPrms; + regw(ctlr, Rcr, r); + iunlock(ctlr); +} + +static void +attach(Ether *ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + + /* + * enable transmit and receive + */ + regw(ctlr, Interrupt, (IntMii | IntTx | IntRcv | IntRxOvrn)<<IntrMaskShift); + regor(ctlr, Rcr, RcrRxEn); + regor(ctlr, Tcr, TcrTxena); +} + +static void +pointtotxpacket(Ctlr *ctlr, int pkt, int read) // read=PtrRead in failure case +{ + ushort junk; + + pkt &= 0x3F; + regw(ctlr, ArrPnr, pkt); + while(regr(ctlr, Pointer) & PtrNotEmpty) + ; + regw(ctlr, Pointer, read | PtrAutoIncr); + junk = llregr(ctlr, BankSelect); /* possible wait state */ + USED(junk); +} + +static void +pointtorxpacket(Ctlr *ctlr, int offset) +{ + ushort junk; + + regw(ctlr, Pointer, PtrRcv | PtrAutoIncr | PtrRead | offset); + junk = llregr(ctlr, BankSelect); /* possible wait state */ + USED(junk); +} + +static void +mmucommand(Ctlr *ctlr, ushort cmd) +{ + while(regr(ctlr, Mmucr) & McrBusy) // should signal free resource + ; + regw(ctlr, Mmucr, cmd); // do the work +} + +static void +txloadpacket(Ether *ether) +{ + Ctlr *ctlr; + int pkt; + Block *b; + ushort lastw; + int lenb, lenw; + int odd; + + ctlr = ether->ctlr; + b = ctlr->waiting; + ctlr->waiting = nil; + if(b == nil) + return; /* shouldn't happen */ + pkt = regr(ctlr, ArrPnr); /* get packet number presumably just allocated */ + if(pkt & 0xC0){ + print("smc91c111: invalid packet number\n"); + freeb(b); + return; + } + + pointtotxpacket(ctlr, pkt, 0); + + lenb = BLEN(b); + odd = lenb & 1; + lenw = lenb >> 1; + regw(ctlr, Data, 0); // status word padding + regw(ctlr, Data, (lenw << 1) + Framectlsize); + regwdatam(ctlr, (ushort*)b->rp, lenw); // put packet into 91cxxx memory + lastw = 0x1000; + if(odd){ + lastw |= 0x2000; /* odd byte flag in control byte */ + lastw |= b->rp[lenb - 1]; + } + regw(ctlr, Data, lastw); + mmucommand(ctlr, McrEnqueue); // chip now owns buff + freeb(b); + regw(ctlr, Interrupt, (regr(ctlr, Interrupt) & IntrMaskField) | (IntTxEmpty << IntrMaskShift)); +} + +static void +txstart(Ether *ether) +{ + Ctlr *ctlr; + int n; + + ctlr = ether->ctlr; + if(ctlr->waiting != nil) /* allocate pending; must wait for that */ + return; + for(;;){ + if((ctlr->waiting = qget(ether->oq)) == nil) + break; + /* ctlr->waiting is a new block to transmit: allocate space */ + n = (BLEN(ctlr->waiting) & ~1) + Framectlsize; /* Framectlsize includes odd byte, if any */ + mmucommand(ctlr, McrAllocTx | (n >> 8)); + if(regr(ctlr, ArrPnr) & ArrFailed){ + regw(ctlr, Interrupt, (regr(ctlr, Interrupt) & IntrMaskField) | (IntAlloc << IntrMaskShift)); + break; + } + txloadpacket(ether); + } +} + +static void +transmit(Ether *ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + ilock(ctlr); + txstart(ether); + iunlock(ctlr); +} + +static void +process(Ether *ether) +{ + Ctlr *ctlr; + int status, intrreg, intr, mask, fifo; + int pkt; + ulong data; + int count, len, alen; + Block *b; + + ctlr = ether->ctlr; + +Recheck: + intrreg = regr(ctlr, Interrupt); + regw(ctlr, Interrupt, 0); + mask = intrreg >> IntrMaskShift; + intr = intrreg & mask; + if(intr == 0){ + regw(ctlr, Interrupt, mask<<IntrMaskShift); + return; + } + + if(intr & IntAlloc){ + regw(ctlr, Interrupt, IntAlloc); + intr &= ~IntAlloc; + if(ctlr->waiting) + txloadpacket(ether); + mask &= ~IntAlloc; + mask |= IntTxEmpty; + } + + if(intr & IntRxOvrn){ + regw(ctlr, Interrupt, IntRxOvrn); + intr &= ~IntRxOvrn; + ether->overflows++; + } + if(intr & IntRcv){ + fifo = regr(ctlr, FifoPorts); + while((fifo & FifoRxEmpty) == 0){ + ether->inpackets++; + pointtorxpacket(ctlr, 0); + data = regr32(ctlr, Data); + status = data & 0xFFFF; + count = (data>>16) & 0x7FE; + if(status & RsBadCRC) + ether->crcs++; + else if(status & RsAlgnErr) + ether->frames++; + else if(status & (RsTooLong | RsTooShort)) + ether->buffs++; + else { + len = count - Framectlsize; + if(len < 0) + panic("smc:interrupt"); + if(ctlr->type == SMSC91C111 && !ctlr->oddworks) + len++; + else if(status & RsOddFrame) + len++; + alen = (len + 1) & ~1; + if(ctlr->bus32bit) + alen = (alen + 3) & ~3; + b = iallocb(alen); + if(b){ + (ctlr->bus32bit? regrdatam32: regrdatam)(ctlr, b->wp, alen); + b->wp += len; + etheriq(ether, b, 1); + }else + ether->soverflows++; + } + mmucommand(ctlr, McrRemove); + fifo = regr(ctlr, FifoPorts); + } + intr &= ~IntRcv; + } + if(intr & IntTx){ + /* some kind of failure */ + fifo = regr(ctlr, FifoPorts); + ctlr->txerrors++; + if((fifo & FifoTxEmpty) == 0){ + pkt = fifo & 0x3f; + pointtotxpacket(ctlr, pkt, PtrRead); + mmucommand(ctlr, McrFreeTx); + } + regw(ctlr, Interrupt, IntTx); + intr &= ~IntTx; + } + if(intr & IntTxEmpty){ + /* acknowledge and disable TX_EMPTY */ + regw(ctlr, Interrupt, IntTxEmpty); + mask &= ~IntTxEmpty; + intr &= ~IntTxEmpty; + } + if(intr) + panic("91c111: unhandled interrupts %.4ux\n", intr); + regw(ctlr, Interrupt, mask<<IntrMaskShift); + txstart(ether); + goto Recheck; +} + +static void +interrupt(Ureg*, void *arg) +{ + Ether *ether; + Ctlr *ctlr; + int bank; + + ether = arg; + ctlr = ether->ctlr; + ilock(ctlr); + bank = llregr(ctlr, BankSelect); + process(ether); + llregw(ctlr, BankSelect, bank); + ctlr->bank = bank; + iunlock(ctlr); +} + +#define MIIDELAY 5 + +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(regr(ctlr, Mgmt) & MgmtMdi) + data |= (1 << i); + microdelay(MIIDELAY); + regw(ctlr, Mgmt, MgmtMclk); + microdelay(MIIDELAY); + regw(ctlr, Mgmt, 0); + microdelay(MIIDELAY); + } + + 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 = MgmtMdoEn | MgmtMdo; + else + mdo = MgmtMdoEn; + regw(ctlr, Mgmt, mdo); + microdelay(MIIDELAY); + regw(ctlr, Mgmt, mdo | MgmtMclk); + microdelay(MIIDELAY); + regw(ctlr, Mgmt, mdo); + microdelay(MIIDELAY); + } +} + +static int +miir(Ctlr *ctlr, int regad) +{ + int data; + + /* + * Preamble; + * ST+OP+PHYAD+REGAD; + * TA + 16 data bits. + */ + miimdo(ctlr, 0xFFFFFFFF, 32); + miimdo(ctlr, 0x1800 | (ctlr->phyad << 5) | regad, 14); + data = miimdi(ctlr, 18); + regw(ctlr, Mgmt, 0); + microdelay(MIIDELAY); + + return data & 0xFFFF; +} + +static void +miiw(Ctlr* ctlr, 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)) | (ctlr->phyad << (5 + 2 +16)) | (regad << (2 + 16)) | (0x02 << 16); + miimdo(ctlr, data, 32); + regw(ctlr, Mgmt, 0); + microdelay(MIIDELAY); +} + +static void +miinegostatus(Ctlr *ctlr, int *speed, int *full) +{ + int reg; + + switch(ctlr->type){ + case SMSC91C110: + reg = miir(ctlr, 25); + if((reg & (1<<4)) == 0) + break; + *speed = (reg & (1 << 5))? 100: 10; + *full = (reg & (1 << 6)) != 0; + return; + case SMSC91C111: + reg = miir(ctlr, 18); + *speed = (reg & (1 << 7))? 100: 10; + *full = (reg & (1 << 6)) != 0; + return; + } + *speed = 0; + *full = 0; +} + +void +dump111phyregs(Ctlr *ctlr) +{ + int x; + for(x = 0; x < 6; x++) + iprint("reg%d 0x%.4ux\n", x, miir(ctlr, x)); + for(x = 16; x <= 20; x++) + iprint("reg%d 0x%.4ux\n", x, miir(ctlr, x)); +} + +static void +miireset(Ctlr *ctlr) +{ + miiw(ctlr, 0, 0x8000); + while(miir(ctlr, 0) & 0x8000) + ; + delay(100); +} + +static int +miinegotiate(Ctlr *ctlr, int modes) +{ + ulong now, timeout; + int success; + int reg4; + + // Taken from TRM - don't argue + + miireset(ctlr); + miiw(ctlr, 0, 0); + regw(ctlr, Rpcr, 0x800 | (4 << 2)); + delay(50); + reg4 = miir(ctlr, 4); + reg4 &= ~(0x1f << 5); + reg4 |= ((modes & 0x1f) << 5); + miiw(ctlr, 4, reg4); + miir(ctlr, 18); // clear the status output so we can tell which bits got set... + miiw(ctlr, 0, 0x3300); + now = timer_start(); + timeout = ms2tmr(3000); + success = 0; + while(!success && (timer_start() - now) < timeout){ + ushort status; + status = miir(ctlr, 1); + if(status & (1 << 5)) + success = 1; + if(status & (1 << 4)){ + success = 0; + miiw(ctlr, 0, 0x3300); + } + } + return success; +} + +static int +ether91c111reset(Ether* ether) +{ + int i; + char *p; + uchar ea[Eaddrlen]; + Ctlr *ctlr; + ushort rev; + + if(ether->ctlr == nil){ + ether->ctlr = malloc(sizeof(Ctlr)); + if(ether->ctlr == nil) + return -1; + } + + ctlr = ether->ctlr; + ctlr->bank = -1; + + /* + * do architecture dependent intialisation + */ + adinit(ether); + + regw(ctlr, Rcr, RcrSoftRst); + regw(ctlr, Config, CfgEphPowerEn|CfgNoWait|Cfg16Bit); + delay(4*20); // rkw - (750us for eeprom alone)4x just to be ultra conservative 10 for linux. + regw(ctlr, Rcr, 0); // rkw - now remove reset and let the sig's fly. + regw(ctlr, Tcr, TcrSwfdup); + + regw(ctlr, Control, CtlAutoRelease | CtlTeEnable); + mmucommand(ctlr, McrReset); // rkw - reset the mmu + delay(5); + + /* + * Identify the chip by reading... + * 1) the bank select register - the top byte will be 0x33 + * 2) changing the bank to see if it reads back appropriately + * 3) check revision register for code 9 + */ + if((llregr(ctlr, BankSelect) >> 8) != 0x33){ + gopanic: + free(ctlr); + return -1; + } + + llregw(ctlr, BankSelect, 0xfffb); + if((llregr(ctlr, BankSelect) & 0xff07) != 0x3303) + goto gopanic; + + rev = regr(ctlr, Revision); + + if((rev >> 8) != 0x33) + goto gopanic; + + rev &= 0xff; + switch(rev){ + case 0x40: + /* 91c96 */ + ctlr->type = SMSC91C96; + ctlr->oddworks = 1; + break; + case 0x90: + ctlr->type = SMSC91C11x; + ctlr->hasmii = 1; + /* 91c110/9c111 */ + /* 91c111s are supposed to be revision one, but it's not the case */ + // See man page 112, revision history. rev not incremented till 08/01 + ctlr->oddworks = 0; // dont know if it works at this point + break; + case 0x91: + ctlr->type = SMSC91C111; + ctlr->hasmii = 1; + ctlr->oddworks = 1; + break; + default: + iprint("ether91c111: chip 0x%.1ux detected\n", rev); + goto gopanic; + } + + memset(ea, 0, sizeof(ea)); + if(memcmp(ether->ea, ea, Eaddrlen) == 0) + panic("ethernet address not set"); +#ifdef YYY + if((rev == 0x90) || (rev == 0x91)) // assuming no eeprom setup for these + panic("ethernet address not set in environment"); + for(i = 0; i < Eaddrlen; i += 2){ + ushort w; + w = regr(ctlr, Iaddr0_1 + i); + iprint("0x%.4ux\n", w); + ea[i] = w; + ea[i + 1] = w >> 8; + } + }else{ + for(i = 0; i < 6; i++){ + char buf[3]; + buf[0] = p[i * 2]; + buf[1] = p[i * 2 + 1]; + buf[2] = 0; + ea[i] = strtol(buf, 0, 16); + } + } + memmove(ether->ea, ea, Eaddrlen); +#endif + + /* + * set the local address + */ + for(i=0; i<Eaddrlen; i+=2) + regw(ctlr, Iaddr0_1 + i, ether->ea[i] | (ether->ea[i+1] << 8)); + + /* + * initialise some registers + */ + regw(ctlr, Rcr, RcrRxEn | RcrAbortEnb | RcrStripCRC); // strip can now be used again + + if(rev == 0x90){ // its either a 110 or a 111 rev A at this point + int reg2, reg3; + /* + * how to tell the difference? + * the standard MII dev + */ + ctlr->phyad = PHYMIIADDR_91C110; + ctlr->type = SMSC91C110; + ctlr->oddworks = 1; // assume a 110 + reg2 = miir(ctlr, 2); // check if a 111 RevA + if(reg2 <= 0){ + ctlr->phyad = PHYMIIADDR_91C111; + ctlr->type = SMSC91C111; + reg2 = miir(ctlr, 2); + ctlr->oddworks = 0; // RevA + } + if(reg2 > 0){ + reg3 = miir(ctlr, 3); + iprint("reg2 0x%.4ux reg3 0x%.4ux\n", reg2, reg3); + } + else + panic("ether91c111: can't find phy on MII\n"); + } + + if(ctlr->type == SMSC91C110) + regor(ctlr, Config, CfgMiiSelect); + if(rev == 0x40){ + regor(ctlr, Config, CfgSetSqlch); + regclear(ctlr, Config, CfgAuiSelect); + regor(ctlr, Config, Cfg16Bit); + } + + if(ctlr->type == SMSC91C111){ + int modes; + char *ethermodes; + + miiw(ctlr, 0, 0x1000); /* clear MII_DIS and enable AUTO_NEG */ +// miiw(ctlr, 16, miir(ctlr, 16) | 0x8000); + // Rpcr set in INIT. + ethermodes=nil; /* was getconf("ethermodes"); */ + if(ethermodes == nil) + modes = 0xf; + else { + char *s; + char *args[10]; + int nargs; + int x; + + s = strdup(ethermodes); + if(s == nil) + panic("ether91c111reset: no memory for ethermodes"); + nargs = getfields(s, args, nelem(args), 1, ","); + modes = 0; + for(x = 0; x < nargs; x++){ + if(cistrcmp(args[x], "10HD") == 0) + modes |= 1; + else if(cistrcmp(args[x], "10FD") == 0) + modes |= 2; + else if(cistrcmp(args[x], "100HD") == 0) + modes |= 4; + else if(cistrcmp(args[x], "100FD") == 0) + modes |= 8; + } + free(s); + } + if(!miinegotiate(ctlr, modes)){ + iprint("ether91c111: negotiation timed out\n"); + return -1; + } + } + + if(ctlr->hasmii) + miinegostatus(ctlr, ðer->mbps, ðer->fullduplex); + else if(regr(ctlr, Eph) & EphLinkOk){ + ether->mbps = 10; + ether->fullduplex = 0; + } + else { + ether->mbps = 0; + ether->fullduplex = 0; + } + + if(ether->fullduplex && ctlr->type == SMSC91C110){ + // application note 79 + regor(ctlr, Tcr, TcrFduplx); + // application note 85 + adsetfd(ctlr); + } + + iprint("91c111 enabled: %dmbps %s\n", ether->mbps, ether->fullduplex ? "FDX" : "HDX"); + if(rev == 0x40){ + iprint("EcsrEcor 0x%.4ux\n", regr(ctlr, EcsrEcor)); + regor(ctlr, EcsrEcor, 1); + } + + /* + * Linkage to the generic ethernet driver. + */ + ether->attach = attach; + ether->transmit = transmit; + ether->interrupt = interrupt; + ether->ifstat = ifstat; + + ether->arg = ether; + ether->promiscuous = promiscuous; + + return 0; +} + +void +ether91c111link(void) +{ + addethercard("91c111", ether91c111reset); +} |
