summaryrefslogtreecommitdiff
path: root/os/cerf1110/ether8900.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
commit74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch)
treec6e220ba61db3a6ea4052e6841296d829654e664 /os/cerf1110/ether8900.c
parent46439007cf417cbd9ac8049bb4122c890097a0fa (diff)
20060303
Diffstat (limited to 'os/cerf1110/ether8900.c')
-rw-r--r--os/cerf1110/ether8900.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/os/cerf1110/ether8900.c b/os/cerf1110/ether8900.c
new file mode 100644
index 00000000..fc9ea12e
--- /dev/null
+++ b/os/cerf1110/ether8900.c
@@ -0,0 +1,702 @@
+/*
+ * Crystal CS8900 ethernet controller
+ *
+ * Todo:
+ * - promiscuous
+ *
+ * Copyright © 1998 Vita Nuova Limited. All rights reserved.
+ * Revisions Copyright © 2000,2003 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 = 0x08000000,
+ IsaMemBase = 0xe0000000,
+
+ IOBase = 0x300,
+ MemBase = 0xc0000,
+
+ MEMORY = 0, /* set non-zero if memory mode to be used */
+ DORESET = 1, /* send soft-reset during initialisation */
+ DEBUG = 0,
+};
+
+#define IOSHIFT 0 /* was 2 */
+#define IOREG(r) (IsaIOBase+((IOBase+(r))<<IOSHIFT))
+
+/* 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) */
+ EEread = 0x0200,
+ EEwrite = 0x0100,
+};
+
+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 */
+ Active3V = 0x0040,
+ 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,
+};
+
+enum { /* EEPROM format */
+ Edataoff = 0x1C, /* start of data (ether address) */
+ Edatalen = 0x14, /* data count in 16-bit words */
+};
+
+struct Ctlr {
+ Lock;
+ Block* waiting; /* waiting for space in FIFO */
+ int model;
+ int rev;
+
+ 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;
+
+ USED(ether);
+ /* 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;
+ ilock(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;
+ }
+ }
+ iunlock(ctlr);
+}
+
+static int
+eepromwait(void)
+{
+ int i;
+
+ for(i=0; i<100000; i++)
+ if((regIOr(SelfSt) & SIBUSY) == 0)
+ return 0;
+ return -1;
+}
+
+static int
+eepromrd(void *buf, int off, int n)
+{
+ int i;
+ ushort *p;
+
+ p = buf;
+ n /= 2;
+ for(i=0; i<n; i++){
+ if(eepromwait() < 0)
+ return -1;
+ regIOw(Ecr, EEread | (off+i));
+ if(eepromwait() < 0)
+ return -1;
+ p[i] = regIOr(Edw);
+ }
+ return 0;
+}
+
+static int
+reset(Ether* ether)
+{
+ int i, reg, easet;
+ uchar ea[Eaddrlen];
+ ushort buf[Edatalen];
+ Ctlr *ctlr;
+
+ if(!MEMORY)
+ mmuphysmap(IsaIOBase, 64*1024);
+
+ delay(120); /* allow time for chip to reset */
+
+ if(0){
+ *(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;
+ }
+ }
+iprint("8900: %4.4ux (selfst) %4.4ux (linest)\n", regIOr(SelfSt), regIOr(LineSt));
+iprint("8900: %4.4ux %4.4ux\n", regIOr(Ern), regIOr(Pic));
+
+ /*
+ * 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));
+ ctlr = ether->ctlr;
+
+ reg = regIOr(Pic);
+ ctlr->model = reg>>14;
+ ctlr->rev = (reg >> 8) & 0x1F;
+
+ ether->mbps = 10;
+
+ memset(ea, 0, Eaddrlen);
+ easet = memcmp(ea, ether->ea, Eaddrlen);
+ memset(buf, 0, sizeof(buf));
+ if(regIOr(SelfSt) & EepromPresent) { /* worth a look */
+ if(eepromrd(buf, Edataoff, sizeof(buf)) >= 0){
+ for(i=0; i<3; i++){
+ ether->ea[2*i] = buf[i];
+ ether->ea[2*i+1] = buf[i] >> 8;
+ }
+ easet = 1;
+ }else
+ iprint("cs8900: can't read EEPROM\n");
+ }
+ if(!easet){
+ iprint("cs8900: ethernet address not configured\n");
+ return -1;
+ }
+ 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; i<Eaddrlen; i+=2)
+ regw(IndAddr+i, ea[i] | (ea[i+1] << 8));
+
+ /* IRQ tied to INTRQ0 */
+ regw(Intr, 0);
+
+ /*
+ * Linkage to the generic ethernet driver.
+ */
+ ether->attach = attach;
+ ether->transmit = transmit;
+ ether->interrupt = interrupt;
+ ether->ifstat = ifstat;
+
+ ether->arg = ether;
+ ether->promiscuous = promiscuous;
+
+ ether->itype = BusGPIOrising; /* TO DO: this shouldn't be done here */
+
+ return 0;
+}
+
+void
+ether8900link(void)
+{
+ addethercard("CS8900", reset);
+}