/* * Crystal CS8900 ethernet controller * * Todo: * - promiscuous * * Copyright © 1998 Vita Nuova Limited. All rights reserved. * Revisions Copyright © 2000 Vita Nuova Holdings Limited. All rights reserved. */ #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" typedef struct Ctlr Ctlr; /* * The CS8900 can be addressed from either ISA I/O space * or ISA memory space at the following virtual addresses, * depending on the hardware's wiring. MEMORY controls * use of memory space. * The cs8900 address pins are shifted by 1 relative to the CPU. */ enum {//18000000 IsaIOBase = 0x0e000000, IsaMemBase = 0xe0000000, IOBase = 0x300, MemBase = 0xc0000, MEMORY = 0, /* set non-zero if memory mode to be used */ DORESET = 0, /* send soft-reset during initialisation */ DEBUG = 0, }; #define IOREG(r) (IsaIOBase+((IOBase+(r))<<2)) /* I/O accesses */ #define out16(port, val) (*((ushort *)IOREG(port)) = (val)) #define in16(port) *((ushort *)IOREG(port)) #define in8(port) *((uchar *)IOREG(port)) #define regIOw(reg, val) do {out16(PpPtr, (reg)|0x3000); out16(PpData, val);} while(0) #define regIOr(reg) (out16(PpPtr, (reg)|0x3000), in16(PpData)) #define regIOr1(reg) (out16(PpPtr, (reg)|0x3000), in16(PpData1)) /* Memory accesses */ #define REGW(reg, val) *((ushort *)IsaMemBase + MemBase + (reg)) = (val) #define REGR(reg) *((ushort *)IsaMemBase + MemBase + (reg)) enum { /* I/O Mode Register Offsets */ RxTxData = 0x00, /* receive/transmit data - port 0 */ RxTxData1 = 0x02, /* r/t data port 1 */ TxCmdIO = 0x04, /* transmit command */ TxLenIO = 0x06, /* transmit length */ IsqIO = 0x08, /* Interrupt status queue */ PpPtr = 0x0a, /* packet page pointer */ PpData = 0x0c, /* packet page data */ PpData1 = 0x0e, /* packet page data - port 1*/ }; enum { /* Memory Mode Register Offsets */ /* Bus Interface Registers */ Ern = 0x0000, /* EISA registration numberion */ Pic = 0x0002, /* Product identification code */ Iob = 0x0020, /* I/O base address */ Intr = 0x0022, /* interrupt number */ Mba = 0x002c, /* memory base address */ Ecr = 0x0040, /* EEPROM command register */ Edw = 0x0042, /* EEPROM data word */ Rbc = 0x0050, /* receive frame byte counter */ /* Status and Control Registers */ RxCfg = 0x0102, RxCtl = 0x0104, TxCfg = 0x0106, BufCfg = 0x010a, LineCtl = 0x0112, SelfCtl = 0x0114, BusCtl = 0x0116, TestCtl = 0x0118, Isq = 0x0120, RxEvent = 0x0124, TxEvent = 0x0128, BufEvent = 0x012c, RxMISS = 0x0130, TxCol = 0x0132, LineSt = 0x0134, SelfSt = 0x0136, BusSt = 0x0138, Tdr = 0x013c, /* Initiate Transmit Registers */ TxCmd = 0x0144, /* transmit command */ TxLen = 0x0146, /* transmit length */ /* Address Filter Registers */ IndAddr = 0x0158, /* individual address registers */ /* Frame Location */ RxStatus = 0x0400, /* receive status */ RxLen = 0x0402, /* receive length */ RxFrame = 0x0404, /* receive frame location */ TxFrame = 0x0a00, /* transmit frame location */ }; enum { /* Ecr */ Addr = 0x00ff, /* EEPROM word address (field) */ Opcode = 0x0300, /* command opcode (field) */ }; enum { /* Isq */ Regnum = 0x003f, /* register number held by Isq (field) */ IsqRxEvent = 0x04, IsqTxEvent = 0x08, IsqBufEvent = 0x0c, IsqRxMiss = 0x10, IsqTxCol = 0x12, RegContent = 0xffc0, /* register data contents (field) */ }; enum { /* RxCfg */ Skip_1 = 0x0040, StreamE = 0x0080, RxOKiE = 0x0100, RxDMAonly = 0x0200, AutoRxDMAE = 0x0400, BufferCRC = 0x0800, CRCerroriE = 0x1000, RuntiE = 0x2000, ExtradataiE = 0x4000, }; enum { /* RxEvent */ IAHash = 0x0040, Dribblebits = 0x0080, RxOK = 0x0100, Hashed = 0x0200, IndividualAdr = 0x0400, Broadcast = 0x0800, CRCerror = 0x1000, Runt = 0x2000, Extradata = 0x4000, }; enum { /* RxCtl */ IAHashA = 0x0040, PromiscuousA = 0x0080, RxOKA = 0x0100, MulticastA = 0x0200, IndividualA = 0x0400, BroadcastA = 0x0800, CRCerrorA = 0x1000, RuntA = 0x2000, ExtradataA = 0x4000, }; enum { /* TxCfg */ LossofCRSiE = 0x0040, SQEerroriE = 0x0080, TxOKiE = 0x0100, OutofWindowiE = 0x0200, JabberiE = 0x0400, AnycolliE = 0x0800, Coll16iE = 0x8000, }; enum { /* TxEvent */ LossofCRS = 0x0040, SQEerror = 0x0080, TxOK = 0x0100, OutofWindow = 0x0200, Jabber = 0x0400, NTxCols = 0x7800, /* number of Tx collisions (field) */ coll16 = 0x8000, }; enum { /* BufCfg */ SWintX = 0x0040, RxDMAiE = 0x0080, Rdy4TxiE = 0x0100, TxUnderruniE = 0x0200, RxMissiE = 0x0400, Rx128iE = 0x0800, TxColOvfiE = 0x1000, MissOvfloiE = 0x2000, RxDestiE = 0x8000, }; enum { /* BufEvent */ SWint = 0x0040, RxDMAFrame = 0x0080, Rdy4Tx = 0x0100, TxUnderrun = 0x0200, RxMiss = 0x0400, Rx128 = 0x0800, RxDest = 0x8000, }; enum { /* RxMiss */ MissCount = 0xffc0, }; enum { /* TxCol */ ColCount = 0xffc0, }; enum { /* LineCtl */ SerRxOn = 0x0040, SerTxOn = 0x0080, Iface = 0x0300, /* (field) 01 - AUI, 00 - 10BASE-T, 10 - Auto select */ ModBackoffE = 0x0800, PolarityDis = 0x1000, DefDis = 0x2000, LoRxSquelch = 0x4000, }; enum { /* LineSt */ LinkOK = 0x0080, AUI = 0x0100, TenBT = 0x0200, PolarityOK = 0x1000, CRS = 0x4000, }; enum { /* SelfCtl */ RESET = 0x0040, SWSuspend = 0x0100, HWSleepE = 0x0200, HWStandbyE = 0x0400, }; enum { /* SelfSt */ INITD = 0x0080, SIBUSY = 0x0100, EepromPresent = 0x0200, EepromOK = 0x0400, ElPresent = 0x0800, EeSize = 0x1000, }; enum { /* BusCtl */ ResetRxDMA = 0x0040, UseSA = 0x0200, MemoryE = 0x0400, DMABurst = 0x0800, EnableIRQ = 0x8000, }; enum { /* BusST */ TxBidErr = 0x0080, Rdy4TxNOW = 0x0100, }; enum { /* TestCtl */ FDX = 0x4000, /* full duplex */ }; enum { /* TxCmd */ TxStart = 0x00c0, /* bytes before transmit starts (field) */ TxSt5 = 0x0000, /* start after 5 bytes */ TxSt381 = 0x0040, /* start after 381 bytes */ TxSt1021 = 0x0080, /* start after 1021 bytes */ TxStAll = 0x00c0, /* start after the entire frame is in the cs8900 */ Force = 0x0100, Onecoll = 0x0200, InhibitCRC = 0x1000, TxPadDis = 0x2000, }; struct Ctlr { Lock; Block* waiting; /* waiting for space in FIFO */ ulong collisions; }; static void regw(int reg, int val) { if(DEBUG) print("r%4.4ux <- %4.4ux\n", reg, val); if(MEMORY){ REGW(reg, val); }else{ out16(PpPtr, reg); out16(PpData, val); } } static int regr(int reg) { int v; if(MEMORY) return REGR(reg); out16(PpPtr, reg); v = in16(PpData); if(DEBUG) print("r%4.4ux = %4.4ux\n", reg, v); return v; } /* * copy frames in and out, accounting for shorts aligned as longs in IO memory */ static void copypktin(void *ad, int len) { ushort *s, *d; int ns; if(!MEMORY){ d = ad; /* * contrary to data sheet DS271PP3 pages 77-78, * the data is not preceded by status & length * perhaps because it has been read directly. */ for(ns = len>>1; --ns >= 0;) *d++ = in16(RxTxData); if(len & 1) *(uchar*)d = in16(RxTxData); return; } d = ad; s = (ushort*)IsaMemBase + MemBase + RxFrame; for(ns = len>>1; --ns >= 0;){ *d++ = *s; s += 2; } if(len & 1) *(uchar*)d = *s; } static void copypktout(void *as, int len) { ushort *s, *d; int ns; if(!MEMORY){ s = as; ns = (len+1)>>1; while(--ns >= 0) out16(RxTxData, *s++); return; } s = as; d = (ushort*)IsaMemBase + MemBase + TxFrame; ns = (len+1)>>1; while(--ns >= 0){ *d = *s++; d += 2; } } 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 = malloc(READSTR); len = snprint(p, READSTR, "Overflow: %ud\n", ether->overflows); len += snprint(p+len, READSTR-len, "CRC Error: %ud\n", ether->crcs); snprint(p+len, READSTR-len, "Collision Seen: %lud\n", ctlr->collisions); n = readstr(offset, a, n, p); free(p); return n; } static void promiscuous(void* arg, int on) { USED(arg, on); } static void attach(Ether *ether) { int reg, s; ulong mask; /* BUG: intreable should set up the following */ mask = 1 << ether->irq; s = splhi(); GPIOREG->gfer &= ~mask; GPIOREG->grer |= mask; /* level triggered, active high */ GPIOREG->gpdr &= ~mask; GPIOREG->gafr &= ~mask; GPIOREG->gedr = mask; splx(s); /* enable transmit and receive */ reg = regr(BusCtl); regw(BusCtl, reg|EnableIRQ); reg = regr(LineCtl); regw(LineCtl, reg|SerRxOn|SerTxOn); if(DEBUG){ iprint("bus=%4.4ux line=%4.4ux\n", regr(BusCtl), regr(LineCtl)); iprint("rc=%4.4ux tc=%4.4ux bc=%4.4ux\n", regr(RxCfg), regr(TxCfg), regr(BufCfg)); } } static void txstart(Ether *ether, int dowait) { int len, status; Ctlr *ctlr; Block *b; ctlr = ether->ctlr; for(;;){ if((b = ctlr->waiting) == nil){ if((b = qget(ether->oq)) == nil) break; }else{ if(!dowait) break; ctlr->waiting = nil; } len = BLEN(b); if(MEMORY){ regw(TxCmd, TxSt381); regw(TxLen, len); }else{ out16(TxCmdIO, TxStAll); out16(TxLenIO, len); } status = regr(BusSt); if((status & Rdy4TxNOW) == 0) { ctlr->waiting = b; break; } /* * Copy the packet to the transmit buffer. */ copypktout(b->rp, len); freeb(b); } } static void transmit(Ether *ether) { Ctlr *ctlr; ctlr = ether->ctlr; ilock(ctlr); txstart(ether, 0); iunlock(ctlr); } static void interrupt(Ureg*, void *arg) { Ether *ether; Ctlr *ctlr; int len, events, status; Block *b; ether = arg; ctlr = ether->ctlr; GPIOREG->gedr = 1<irq; /* reset now, for level triggered */ lock(ctlr); while((events = (MEMORY?regr(Isq):in16(IsqIO))) != 0) { status = events&RegContent; if(DEBUG) iprint("status %4.4ux event %4.4ux\n", status, events); switch(events&Regnum) { case IsqBufEvent: if(status&Rdy4Tx) { if((b = ctlr->waiting) != nil){ ctlr->waiting = nil; copypktout(b->rp, BLEN(b)); freeb(b); /* wait for IsqTxEvent to send remaining packets in txstart */ }else txstart(ether, 0); } break; case IsqRxEvent: if(status&RxOK) { len = regr(RxLen); if(DEBUG) iprint("rxlen=%d\n", len); if((b = iallocb(len)) != 0) { copypktin(b->wp, len); b->wp += len; etheriq(ether, b, 1); } } break; case IsqTxEvent: if(status&TxOK) txstart(ether, 1); break; case IsqRxMiss: ether->overflows++; break; case IsqTxCol: ctlr->collisions++; break; } } unlock(ctlr); } static int reset(Ether* ether) { int i, reg; uchar ea[Eaddrlen]; delay(120); /* allow time for chip to reset */ *(ushort*)IsaIOBase = 0xDEAD; /* force rubbish on bus */ for(i=0; i<100; i++){ if(in16(PpPtr) == 0x3000) break; delay(1); } if(i>=100){ iprint("failed init: reg(0xA): %4.4ux, should be 0x3000\n", in16(PpPtr)); return -1; } /* * Identify the chip by reading the Pic register. * The EISA registration number is in the low word * and the product identification code in the high code. * The ERN for Crystal Semiconductor is 0x630e. * Bits 0-7 and 13-15 of the Pic should be zero for a CS8900. */ if(regIOr(Ern) != 0x630e || (regIOr(Pic) & 0xe0ff) != 0) return -1; if(ether->ctlr == nil) ether->ctlr = malloc(sizeof(Ctlr)); ether->mbps = 10; memset(ea, 0, Eaddrlen); if(memcmp(ea, ether->ea, Eaddrlen) == 0) return -1; /* ethernet address unknown: could take from EEPROM if there were one */ memmove(ea, ether->ea, Eaddrlen); if(DORESET){ /* * Reset the chip and ensure 16-bit mode operation */ regIOw(SelfCtl, RESET); delay(10); i=in8(PpPtr); USED(i); i=in8(PpPtr+1); USED(i); i=in8(PpPtr); USED(i); i=in8(PpPtr+1); USED(i); /* * Wait for initialisation and EEPROM reads to complete */ i=0; for(;;) { short st = regIOr(SelfSt); if((st&SIBUSY) == 0 && st&INITD) break; if(i++ > 1000000) panic("cs8900: initialisation failed"); } } if(MEMORY){ /* * Enable memory mode operation. */ regIOw(Mba, MemBase & 0xffff); regIOw(Mba+2, MemBase >> 16); regIOw(BusCtl, MemoryE|UseSA); } /* * Enable 10BASE-T half duplex, transmit in interrupt mode */ reg = regr(LineCtl); regw(LineCtl, reg&~Iface); reg = regr(TestCtl); if(ether->fullduplex) regw(TestCtl, reg|FDX); else regw(TestCtl, reg&~FDX); regw(BufCfg, Rdy4TxiE|TxUnderruniE); regw(TxCfg, TxOKiE|AnycolliE|LossofCRSiE|Coll16iE); regw(RxCfg, RxOKiE|CRCerroriE|RuntiE|ExtradataiE); regw(RxCtl, RxOKA|IndividualA|BroadcastA); for(i=0; iattach = attach; ether->transmit = transmit; ether->interrupt = interrupt; ether->ifstat = ifstat; ether->arg = ether; ether->promiscuous = promiscuous; return 0; } void ether8900link(void) { addethercard("CS8900", reset); }