ref: 43de7b8419fa3582af6df803473ef1bde2ce5c23
dir: /os/cerf250/ether91c111.c/
/*
* 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);
}