|  | /* | 
|  | Etherboot DEC Tulip driver | 
|  | adapted by Ken Yap from | 
|  |  | 
|  | FreeBSD netboot DEC 21143 driver | 
|  |  | 
|  | Author: David Sharp | 
|  | date: Nov/98 | 
|  |  | 
|  | Known to work on DEC DE500 using 21143-PC chipset. | 
|  | Even on cards with the same chipset there can be | 
|  | incompatablity problems with the way media selection | 
|  | and status LED settings are done.  See comments below. | 
|  |  | 
|  | Some code fragments were taken from verious places, | 
|  | Ken Yap's etherboot, FreeBSD's if_de.c, and various | 
|  | Linux related files.  DEC's manuals for the 21143 and | 
|  | SROM format were very helpful.  The Linux de driver | 
|  | development page has a number of links to useful | 
|  | related information.  Have a look at: | 
|  | ftp://cesdis.gsfc.nasa.gov/pub/linux/drivers/tulip-devel.html | 
|  |  | 
|  | */ | 
|  |  | 
|  | #include "etherboot.h" | 
|  | #include "nic.h" | 
|  | #include "pci.h" | 
|  | #include "cards.h" | 
|  | #include "otulip.h" | 
|  |  | 
|  | static unsigned short vendor, dev_id; | 
|  | static unsigned short ioaddr; | 
|  | static unsigned int *membase; | 
|  | static unsigned char srom[1024]; | 
|  |  | 
|  | #define BUFLEN 1536     /* must be longword divisable */ | 
|  | /* buffers must be longword aligned */ | 
|  |  | 
|  | /* transmit descriptor and buffer */ | 
|  | static struct txdesc txd; | 
|  |  | 
|  | /* receive descriptor(s) and buffer(s) */ | 
|  | #define NRXD 4 | 
|  | static struct rxdesc rxd[NRXD]; | 
|  | static int rxd_tail = 0; | 
|  | #ifndef	USE_INTERNAL_BUFFER | 
|  | #define rxb ((char *)0x10000 - NRXD * BUFLEN) | 
|  | #define txb ((char *)0x10000 - NRXD * BUFLEN - BUFLEN) | 
|  | #else | 
|  | static unsigned char rxb[NRXD * BUFLEN]; | 
|  | static unsigned char txb[BUFLEN]; | 
|  | #endif | 
|  |  | 
|  | static unsigned char ehdr[ETHER_HDR_SIZE];    /* buffer for ethernet header */ | 
|  |  | 
|  | enum tulip_offsets { | 
|  | CSR0=0,    CSR1=0x08, CSR2=0x10, CSR3=0x18, CSR4=0x20, CSR5=0x28, | 
|  | CSR6=0x30, CSR7=0x38, CSR8=0x40, CSR9=0x48, CSR10=0x50, CSR11=0x58, | 
|  | CSR12=0x60, CSR13=0x68, CSR14=0x70, CSR15=0x78 }; | 
|  |  | 
|  |  | 
|  | /***************************************************************************/ | 
|  | /* 21143 specific stuff  */ | 
|  | /***************************************************************************/ | 
|  |  | 
|  | /* XXX assume 33MHz PCI bus,  this is not very accurate and should be | 
|  | used only with gross over estimations of required delay times unless | 
|  | you tune UADJUST to your specific processor and I/O subsystem */ | 
|  |  | 
|  | #define UADJUST 870 | 
|  | static void udelay(unsigned long usec) { | 
|  | unsigned long i; | 
|  | for (i=((usec*UADJUST)/33)+1; i>0; i--) (void) TULIP_CSR_READ(csr_0); | 
|  | } | 
|  |  | 
|  | /* The following srom related code was taken from FreeBSD's if_de.c */ | 
|  | /* with minor alterations to make it work here.  the Linux code is */ | 
|  | /* better but this was easier to use */ | 
|  |  | 
|  | static void delay_300ns(void) | 
|  | { | 
|  | int idx; | 
|  | for (idx = (300 / 33) + 1; idx > 0; idx--) | 
|  | (void) TULIP_CSR_READ(csr_busmode); | 
|  | } | 
|  |  | 
|  | #define EMIT do { TULIP_CSR_WRITE(csr_srom_mii, csr); delay_300ns(); } while (0) | 
|  |  | 
|  | static void srom_idle(void) | 
|  | { | 
|  | unsigned bit, csr; | 
|  |  | 
|  | csr  = SROMSEL ; EMIT; | 
|  | csr  = SROMSEL | SROMRD; EMIT; | 
|  | csr ^= SROMCS; EMIT; | 
|  | csr ^= SROMCLKON; EMIT; | 
|  | /* | 
|  | * Write 25 cycles of 0 which will force the SROM to be idle. | 
|  | */ | 
|  | for (bit = 3 + SROM_BITWIDTH + 16; bit > 0; bit--) { | 
|  | csr ^= SROMCLKOFF; EMIT;    /* clock low; data not valid */ | 
|  | csr ^= SROMCLKON; EMIT;     /* clock high; data valid */ | 
|  | } | 
|  | csr ^= SROMCLKOFF; EMIT; | 
|  | csr ^= SROMCS; EMIT; | 
|  | csr  = 0; EMIT; | 
|  | } | 
|  |  | 
|  | static void srom_read(void) | 
|  | { | 
|  | unsigned idx; | 
|  | const unsigned bitwidth = SROM_BITWIDTH; | 
|  | const unsigned cmdmask = (SROMCMD_RD << bitwidth); | 
|  | const unsigned msb = 1 << (bitwidth + 3 - 1); | 
|  | unsigned lastidx = (1 << bitwidth) - 1; | 
|  |  | 
|  | srom_idle(); | 
|  |  | 
|  | for (idx = 0; idx <= lastidx; idx++) { | 
|  | unsigned lastbit, data, bits, bit, csr; | 
|  | csr  = SROMSEL ;                EMIT; | 
|  | csr  = SROMSEL | SROMRD;        EMIT; | 
|  | csr ^= SROMCSON;                EMIT; | 
|  | csr ^=            SROMCLKON;    EMIT; | 
|  |  | 
|  | lastbit = 0; | 
|  | for (bits = idx|cmdmask, bit = bitwidth + 3; bit > 0; bit--, bits <<= 1) | 
|  | { | 
|  | const unsigned thisbit = bits & msb; | 
|  | csr ^= SROMCLKOFF; EMIT;    /* clock low; data not valid */ | 
|  | if (thisbit != lastbit) { | 
|  | csr ^= SROMDOUT; EMIT;  /* clock low; invert data */ | 
|  | } else { | 
|  | EMIT; | 
|  | } | 
|  | csr ^= SROMCLKON; EMIT;     /* clock high; data valid */ | 
|  | lastbit = thisbit; | 
|  | } | 
|  | csr ^= SROMCLKOFF; EMIT; | 
|  |  | 
|  | for (data = 0, bits = 0; bits < 16; bits++) { | 
|  | data <<= 1; | 
|  | csr ^= SROMCLKON; EMIT;     /* clock high; data valid */ | 
|  | data |= TULIP_CSR_READ(csr_srom_mii) & SROMDIN ? 1 : 0; | 
|  | csr ^= SROMCLKOFF; EMIT;    /* clock low; data not valid */ | 
|  | } | 
|  | srom[idx*2] = data & 0xFF; | 
|  | srom[idx*2+1] = data >> 8; | 
|  | csr  = SROMSEL | SROMRD; EMIT; | 
|  | csr  = 0; EMIT; | 
|  | } | 
|  | srom_idle(); | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | ETH_RESET - Reset adapter | 
|  | ***************************************************************************/ | 
|  | static void tulip_reset(struct nic *nic) | 
|  | { | 
|  | int x,cnt=2; | 
|  |  | 
|  | outl(0x00000001, ioaddr + CSR0); | 
|  | udelay(1000); | 
|  | /* turn off reset and set cache align=16lword, burst=unlimit */ | 
|  | outl(0x01A08000, ioaddr + CSR0); | 
|  |  | 
|  | /* for some reason the media selection does not take | 
|  | the first time se it is repeated.  */ | 
|  |  | 
|  | while(cnt--) { | 
|  | /* stop TX,RX processes */ | 
|  | if (cnt == 1) | 
|  | outl(0x32404000, ioaddr + CSR6); | 
|  | else | 
|  | outl(0x32000040, ioaddr + CSR6); | 
|  |  | 
|  | /* XXX - media selection is vendor specific and hard coded right | 
|  | here.  This should be fixed to use the hints in the SROM and | 
|  | allow media selection by the user at runtime.  MII support | 
|  | should also be added.  Support for chips other than the | 
|  | 21143 should be added here as well  */ | 
|  |  | 
|  | /* start  set to 10Mbps half-duplex */ | 
|  |  | 
|  | /* setup SIA */ | 
|  | outl(0x0, ioaddr + CSR13);              /* reset SIA */ | 
|  | outl(0x7f3f, ioaddr + CSR14); | 
|  | outl(0x8000008, ioaddr + CSR15); | 
|  | outl(0x0, ioaddr + CSR13); | 
|  | outl(0x1, ioaddr + CSR13); | 
|  | outl(0x2404000, ioaddr + CSR6); | 
|  |  | 
|  | /* initalize GP */ | 
|  | outl(0x8af0008, ioaddr + CSR15); | 
|  | outl(0x50008, ioaddr + CSR15); | 
|  |  | 
|  | /* end  set to 10Mbps half-duplex */ | 
|  |  | 
|  | if (vendor == PCI_VENDOR_ID_MACRONIX && dev_id == PCI_DEVICE_ID_MX987x5) { | 
|  | /* do stuff for MX98715 */ | 
|  | outl(0x01a80000, ioaddr + CSR6); | 
|  | outl(0xFFFFFFFF, ioaddr + CSR14); | 
|  | outl(0x00001000, ioaddr + CSR12); | 
|  | } | 
|  |  | 
|  | outl(0x0, ioaddr + CSR7);       /* disable interrupts */ | 
|  |  | 
|  | /* construct setup packet which is used by the 21143 to | 
|  | program its CAM to recognize interesting MAC addresses */ | 
|  |  | 
|  | memset(&txd, 0, sizeof(struct txdesc)); | 
|  | txd.buf1addr = &txb[0]; | 
|  | txd.buf2addr = &txb[0];         /* just in case */ | 
|  | txd.buf1sz   = 192;             /* setup packet must be 192 bytes */ | 
|  | txd.buf2sz   = 0; | 
|  | txd.control  = 0x020;           /* setup packet */ | 
|  | txd.status   = 0x80000000;      /* give ownership to 21143 */ | 
|  |  | 
|  | /* construct perfect filter frame */ | 
|  | /* with mac address as first match */ | 
|  | /* and broadcast address for all others */ | 
|  |  | 
|  | for(x=0;x<192;x++) txb[x] = 0xff; | 
|  | txb[0] = nic->node_addr[0]; | 
|  | txb[1] = nic->node_addr[1]; | 
|  | txb[4] = nic->node_addr[2]; | 
|  | txb[5] = nic->node_addr[3]; | 
|  | txb[8] = nic->node_addr[4]; | 
|  | txb[9] = nic->node_addr[5]; | 
|  | outl((unsigned long)&txd, ioaddr + CSR4);        /* set xmit buf */ | 
|  | outl(0x2406000, ioaddr + CSR6);         /* start transmiter */ | 
|  |  | 
|  | udelay(50000);  /* wait for the setup packet to be processed */ | 
|  |  | 
|  | } | 
|  |  | 
|  | /* setup receive descriptor */ | 
|  | { | 
|  | int x; | 
|  | for(x=0;x<NRXD;x++) { | 
|  | memset(&rxd[x], 0, sizeof(struct rxdesc)); | 
|  | rxd[x].buf1addr = &rxb[x * BUFLEN]; | 
|  | rxd[x].buf2addr = 0;        /* not used */ | 
|  | rxd[x].buf1sz   = BUFLEN; | 
|  | rxd[x].buf2sz   = 0;        /* not used */ | 
|  | rxd[x].control  = 0x0; | 
|  | rxd[x].status   = 0x80000000;       /* give ownership it to 21143 */ | 
|  | } | 
|  | rxd[NRXD - 1].control  = 0x008;       /* Set Receive end of ring on la | 
|  | st descriptor */ | 
|  | rxd_tail = 0; | 
|  | } | 
|  |  | 
|  | /* tell DC211XX where to find rx descriptor list */ | 
|  | outl((unsigned long)&rxd[0], ioaddr + CSR3); | 
|  | /* start the receiver */ | 
|  | outl(0x2406002, ioaddr + CSR6); | 
|  |  | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | ETH_TRANSMIT - Transmit a frame | 
|  | ***************************************************************************/ | 
|  | static const char padmap[] = { | 
|  | 0, 3, 2, 1}; | 
|  |  | 
|  | static void tulip_transmit(struct nic *nic, const char *d, unsigned int t, unsigned int s, const char *p) | 
|  | { | 
|  | unsigned long time; | 
|  |  | 
|  | /* setup ethernet header */ | 
|  |  | 
|  | memcpy(ehdr, d, ETHER_ADDR_SIZE); | 
|  | memcpy(&ehdr[ETHER_ADDR_SIZE], nic->node_addr, ETHER_ADDR_SIZE); | 
|  | ehdr[ETHER_ADDR_SIZE*2] = (t >> 8) & 0xff; | 
|  | ehdr[ETHER_ADDR_SIZE*2+1] = t & 0xff; | 
|  |  | 
|  | /* setup the transmit descriptor */ | 
|  |  | 
|  | memset(&txd, 0, sizeof(struct txdesc)); | 
|  |  | 
|  | txd.buf1addr = &ehdr[0];        /* ethernet header */ | 
|  | txd.buf1sz   = ETHER_HDR_SIZE; | 
|  |  | 
|  | txd.buf2addr = p;               /* packet to transmit */ | 
|  | txd.buf2sz   = s; | 
|  |  | 
|  | txd.control  = 0x188;           /* LS+FS+TER */ | 
|  |  | 
|  | txd.status   = 0x80000000;      /* give it to 21143 */ | 
|  |  | 
|  | outl(inl(ioaddr + CSR6) & ~0x00004000, ioaddr + CSR6); | 
|  | outl((unsigned long)&txd, ioaddr + CSR4); | 
|  | outl(inl(ioaddr + CSR6) | 0x00004000, ioaddr + CSR6); | 
|  |  | 
|  | /*   Wait for transmit to complete before returning.  not well tested. | 
|  |  | 
|  | time = currticks(); | 
|  | while(txd.status & 0x80000000) { | 
|  | if (currticks() - time > 20) { | 
|  | printf("transmit timeout.\n"); | 
|  | break; | 
|  | } | 
|  | } | 
|  | */ | 
|  |  | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | ETH_POLL - Wait for a frame | 
|  | ***************************************************************************/ | 
|  | static int tulip_poll(struct nic *nic) | 
|  | { | 
|  | if (rxd[rxd_tail].status & 0x80000000) return 0; | 
|  |  | 
|  | nic->packetlen = (rxd[rxd_tail].status & 0x3FFF0000) >> 16; | 
|  |  | 
|  | /* copy packet to working buffer */ | 
|  | /* XXX - this copy could be avoided with a little more work | 
|  | but for now we are content with it because the optimised | 
|  | memcpy(, , ) is quite fast */ | 
|  |  | 
|  | memcpy(nic->packet, rxb + rxd_tail * BUFLEN, nic->packetlen); | 
|  |  | 
|  | /* return the descriptor and buffer to recieve ring */ | 
|  | rxd[rxd_tail].status = 0x80000000; | 
|  | rxd_tail++; | 
|  | if (rxd_tail == NRXD) rxd_tail = 0; | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static void tulip_disable(struct nic *nic) | 
|  | { | 
|  | /* nothing for the moment */ | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | ETH_PROBE - Look for an adapter | 
|  | ***************************************************************************/ | 
|  | struct nic *otulip_probe(struct nic *nic, unsigned short *io_addrs, struct pci_device *pci) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | if (io_addrs == 0 || *io_addrs == 0) | 
|  | return (0); | 
|  | vendor = pci->vendor; | 
|  | dev_id = pci->dev_id; | 
|  | ioaddr = *io_addrs; | 
|  | membase = (unsigned int *)pci->membase; | 
|  |  | 
|  | /* wakeup chip */ | 
|  | pcibios_write_config_dword(0,pci->devfn,0x40,0x00000000); | 
|  |  | 
|  | /* Stop the chip's Tx and Rx processes. */ | 
|  | /* outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6); */ | 
|  | /* Clear the missed-packet counter. */ | 
|  | /* (volatile int)inl(ioaddr + CSR8); */ | 
|  |  | 
|  | srom_read(); | 
|  |  | 
|  | for (i=0; i < 6; i++) | 
|  | nic->node_addr[i] = srom[20+i]; | 
|  |  | 
|  | printf("Tulip %b:%b:%b:%b:%b:%b at ioaddr 0x%x\n", | 
|  | srom[20],srom[21],srom[22],srom[23],srom[24],srom[25], | 
|  | ioaddr); | 
|  |  | 
|  | tulip_reset(nic); | 
|  |  | 
|  | nic->reset = tulip_reset; | 
|  | nic->poll = tulip_poll; | 
|  | nic->transmit = tulip_transmit; | 
|  | nic->disable = tulip_disable; | 
|  | return nic; | 
|  | } |