summaryrefslogtreecommitdiff
path: root/os/cerf250/ether91c111.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/cerf250/ether91c111.c')
-rw-r--r--os/cerf250/ether91c111.c1056
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, &ether->mbps, &ether->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);
+}