| /* |
| Driver for the National Semiconductor DP83810 Ethernet controller. |
| |
| Portions Copyright (C) 2001 Inprimis Technologies, Inc. |
| http://www.inprimis.com/ |
| |
| This driver is based (heavily) on the Linux driver for this chip |
| which is copyright 1999-2001 by Donald Becker. |
| |
| This software has no warranties expressed or implied for any |
| purpose. |
| |
| This software may be used and distributed according to the terms of |
| the GNU General Public License (GPL), incorporated herein by reference. |
| Drivers based on or derived from this code fall under the GPL and must |
| retain the authorship, copyright and license notice. This file is not |
| a complete program and may only be used when the entire operating |
| system is licensed under the GPL. License for under other terms may be |
| available. Contact the original author for details. |
| |
| The original author may be reached as becker@scyld.com, or at |
| Scyld Computing Corporation |
| 410 Severn Ave., Suite 210 |
| Annapolis MD 21403 |
| */ |
| |
| |
| typedef unsigned char u8; |
| typedef signed char s8; |
| typedef unsigned short u16; |
| typedef signed short s16; |
| typedef unsigned int u32; |
| typedef signed int s32; |
| |
| #include "etherboot.h" |
| #include "nic.h" |
| #include "pci.h" |
| |
| #undef virt_to_bus |
| #define virt_to_bus(x) ((unsigned long)x) |
| #define cpu_to_le32(val) (val) |
| #define le32_to_cpu(val) (val) |
| #define virt_to_le32desc(addr) cpu_to_le32(virt_to_bus(addr)) |
| #define le32desc_to_virt(addr) bus_to_virt(le32_to_cpu(addr)) |
| |
| #define TX_RING_SIZE 1 |
| #define RX_RING_SIZE 4 |
| #define TIME_OUT 1000000 |
| #define PKT_BUF_SZ 1536 |
| |
| /* Offsets to the device registers. */ |
| enum register_offsets { |
| ChipCmd=0x00, ChipConfig=0x04, EECtrl=0x08, PCIBusCfg=0x0C, |
| IntrStatus=0x10, IntrMask=0x14, IntrEnable=0x18, |
| TxRingPtr=0x20, TxConfig=0x24, |
| RxRingPtr=0x30, RxConfig=0x34, |
| WOLCmd=0x40, PauseCmd=0x44, RxFilterAddr=0x48, RxFilterData=0x4C, |
| BootRomAddr=0x50, BootRomData=0x54, StatsCtrl=0x5C, StatsData=0x60, |
| RxPktErrs=0x60, RxMissed=0x68, RxCRCErrs=0x64, |
| }; |
| |
| /* Bit in ChipCmd. */ |
| enum ChipCmdBits { |
| ChipReset=0x100, RxReset=0x20, TxReset=0x10, RxOff=0x08, RxOn=0x04, |
| TxOff=0x02, TxOn=0x01, |
| }; |
| |
| /* Bits in the interrupt status/mask registers. */ |
| enum intr_status_bits { |
| IntrRxDone=0x0001, IntrRxIntr=0x0002, IntrRxErr=0x0004, IntrRxEarly=0x0008, |
| IntrRxIdle=0x0010, IntrRxOverrun=0x0020, |
| IntrTxDone=0x0040, IntrTxIntr=0x0080, IntrTxErr=0x0100, |
| IntrTxIdle=0x0200, IntrTxUnderrun=0x0400, |
| StatsMax=0x0800, LinkChange=0x4000, WOLPkt=0x2000, |
| RxResetDone=0x1000000, TxResetDone=0x2000000, |
| IntrPCIErr=0x00f00000, IntrNormalSummary=0x0251, IntrAbnormalSummary=0xED20, |
| }; |
| |
| /* Bits in the RxMode register. */ |
| enum rx_mode_bits { |
| AcceptErr=0x20, AcceptRunt=0x10, AcceptBroadcast=0xC0000000, |
| AcceptMulticast=0x00200000, AcceptAllMulticast=0x20000000, |
| AcceptAllPhys=0x10000000, AcceptMyPhys=0x08000000, |
| }; |
| |
| /* Bits in network_desc.status */ |
| enum desc_status_bits { |
| DescOwn=0x80000000, DescMore=0x40000000, DescIntr=0x20000000, |
| DescNoCRC=0x10000000, |
| DescPktOK=0x08000000, RxTooLong=0x00400000, |
| }; |
| |
| /* The Rx and Tx buffer descriptors. */ |
| struct netdev_desc { |
| u32 next_desc; |
| s32 cmd_status; |
| u32 addr; |
| }; |
| |
| static struct FA311_DEV { |
| unsigned int ioaddr; |
| unsigned short vendor; |
| unsigned short device; |
| unsigned int cur_rx; |
| unsigned int cur_tx; |
| unsigned int rx_buf_sz; |
| volatile struct netdev_desc *rx_head_desc; |
| volatile struct netdev_desc rx_ring[RX_RING_SIZE] __attribute__ ((aligned (4))); |
| volatile struct netdev_desc tx_ring[TX_RING_SIZE] __attribute__ ((aligned (4))); |
| } fa311_dev; |
| |
| static int eeprom_read(long ioaddr, int location); |
| static void init_ring(struct FA311_DEV *dev); |
| static void fa311_reset(struct nic *nic); |
| static int fa311_poll(struct nic *nic); |
| static void fa311_transmit(struct nic *nic, const char *d, unsigned int t, unsigned int s, const char *p); |
| static void fa311_disable(struct nic *nic); |
| |
| static char rx_packet[PKT_BUF_SZ * RX_RING_SIZE] __attribute__ ((aligned (4))); |
| static char tx_packet[PKT_BUF_SZ * TX_RING_SIZE] __attribute__ ((aligned (4))); |
| |
| struct nic * fa311_probe(struct nic *nic, unsigned short *io_addrs, struct pci_device *pci) |
| { |
| int prev_eedata; |
| int i; |
| int duplex; |
| int tx_config; |
| int rx_config; |
| unsigned char macaddr[6]; |
| unsigned char mactest; |
| unsigned char pci_bus = 0; |
| struct FA311_DEV* dev = &fa311_dev; |
| |
| if (io_addrs == 0 || *io_addrs == 0) |
| return (0); |
| memset(dev, 0, sizeof(*dev)); |
| dev->vendor = pci->vendor; |
| dev->device = pci->dev_id; |
| dev->ioaddr = pci->membase; |
| |
| /* Work around the dropped serial bit. */ |
| prev_eedata = eeprom_read(dev->ioaddr, 6); |
| for (i = 0; i < 3; i++) { |
| int eedata = eeprom_read(dev->ioaddr, i + 7); |
| macaddr[i*2] = (eedata << 1) + (prev_eedata >> 15); |
| macaddr[i*2+1] = eedata >> 7; |
| prev_eedata = eedata; |
| } |
| mactest = 0; |
| for (i = 0; i < 6; i++) |
| mactest |= macaddr[i]; |
| if (mactest == 0) |
| return (0); |
| for (i = 0; i < 6; i++) |
| nic->node_addr[i] = macaddr[i]; |
| printf("%! ", nic->node_addr); |
| |
| adjust_pci_device(pci); |
| |
| fa311_reset(nic); |
| |
| nic->reset = fa311_reset; |
| nic->disable = fa311_disable; |
| nic->poll = fa311_poll; |
| nic->transmit = fa311_transmit; |
| |
| init_ring(dev); |
| |
| writel(virt_to_bus(dev->rx_ring), dev->ioaddr + RxRingPtr); |
| writel(virt_to_bus(dev->tx_ring), dev->ioaddr + TxRingPtr); |
| |
| for (i = 0; i < 6; i += 2) |
| { |
| writel(i, dev->ioaddr + RxFilterAddr); |
| writew(macaddr[i] + (macaddr[i+1] << 8), |
| dev->ioaddr + RxFilterData); |
| } |
| |
| /* Initialize other registers. */ |
| /* Configure for standard, in-spec Ethernet. */ |
| if (readl(dev->ioaddr + ChipConfig) & 0x20000000) |
| { /* Full duplex */ |
| tx_config = 0xD0801002; |
| rx_config = 0x10000020; |
| } |
| else |
| { |
| tx_config = 0x10801002; |
| rx_config = 0x0020; |
| } |
| writel(tx_config, dev->ioaddr + TxConfig); |
| writel(rx_config, dev->ioaddr + RxConfig); |
| |
| duplex = readl(dev->ioaddr + ChipConfig) & 0x20000000 ? 1 : 0; |
| if (duplex) { |
| rx_config |= 0x10000000; |
| tx_config |= 0xC0000000; |
| } else { |
| rx_config &= ~0x10000000; |
| tx_config &= ~0xC0000000; |
| } |
| writew(tx_config, dev->ioaddr + TxConfig); |
| writew(rx_config, dev->ioaddr + RxConfig); |
| |
| writel(AcceptBroadcast | AcceptAllMulticast | AcceptMyPhys, |
| dev->ioaddr + RxFilterAddr); |
| |
| writel(RxOn | TxOn, dev->ioaddr + ChipCmd); |
| writel(4, dev->ioaddr + StatsCtrl); /* Clear Stats */ |
| return nic; |
| |
| } |
| |
| static void fa311_reset(struct nic *nic) |
| { |
| u32 chip_config; |
| struct FA311_DEV* dev = &fa311_dev; |
| |
| /* Reset the chip to erase previous misconfiguration. */ |
| outl(ChipReset, dev->ioaddr + ChipCmd); |
| |
| if ((readl(dev->ioaddr + ChipConfig) & 0xe000) != 0xe000) |
| { |
| chip_config = readl(dev->ioaddr + ChipConfig); |
| } |
| } |
| |
| static int fa311_poll(struct nic *nic) |
| { |
| s32 desc_status; |
| int to; |
| int entry; |
| int retcode; |
| struct FA311_DEV* dev = &fa311_dev; |
| |
| retcode = 0; |
| entry = dev->cur_rx; |
| to = TIME_OUT; |
| while (to != 0) |
| { |
| desc_status = dev->rx_ring[entry].cmd_status; |
| if ((desc_status & DescOwn) != 0) |
| break; |
| else |
| --to; |
| } |
| if (to != 0) |
| { |
| readl(dev->ioaddr + IntrStatus); /* clear interrrupt bits */ |
| /* driver owns the next entry it's a new packet. Send it up. */ |
| if ((desc_status & (DescMore|DescPktOK|RxTooLong)) == DescPktOK) |
| { |
| nic->packetlen = (desc_status & 0x0fff) - 4; /* Omit CRC size. */ |
| memcpy(nic->packet, (char*)(dev->rx_ring[entry].addr), nic->packetlen); |
| retcode = 1; |
| } |
| /* Give the descriptor back to the chip */ |
| dev->rx_ring[entry].cmd_status = cpu_to_le32(dev->rx_buf_sz); |
| dev->cur_rx++; |
| if (dev->cur_rx >= RX_RING_SIZE) |
| dev->cur_rx = 0; |
| dev->rx_head_desc = &dev->rx_ring[dev->cur_rx]; |
| } |
| /* Restart Rx engine if stopped. */ |
| writel(RxOn, dev->ioaddr + ChipCmd); |
| return retcode; |
| } |
| |
| static void fa311_transmit(struct nic *nic, const char *destaddr, unsigned int type, unsigned int len, const char *data) |
| { |
| unsigned short nstype; |
| s32 desc_status; |
| int to; |
| int entry; |
| char* txp; |
| unsigned char* s; |
| struct FA311_DEV* dev = &fa311_dev; |
| |
| /* Calculate the next Tx descriptor entry. */ |
| entry = dev->cur_tx; |
| txp = (char*)(dev->tx_ring[entry].addr); |
| |
| memcpy(txp, destaddr, ETH_ALEN); |
| memcpy(txp + ETH_ALEN, nic->node_addr, ETH_ALEN); |
| nstype = htons(type); |
| memcpy(txp + 12, (char*)&nstype, 2); |
| memcpy(txp + ETH_HLEN, data, len); |
| len += ETH_HLEN; |
| /* pad frame */ |
| if (len < ETH_ZLEN) |
| { |
| s = (unsigned char*)(txp+len); |
| while (s < (unsigned char*)(txp+ETH_ZLEN)) |
| *s++ = 0; |
| len = ETH_ZLEN; |
| } |
| dev->tx_ring[entry].cmd_status = cpu_to_le32(DescOwn | len); |
| dev->cur_tx++; |
| if (dev->cur_tx >= TX_RING_SIZE) |
| dev->cur_tx = 0; |
| |
| /* Wake the potentially-idle transmit channel. */ |
| writel(TxOn, dev->ioaddr + ChipCmd); |
| |
| /* wait for tranmission to complete */ |
| to = TIME_OUT; |
| while (to != 0) |
| { |
| desc_status = dev->tx_ring[entry].cmd_status; |
| if ((desc_status & DescOwn) == 0) |
| break; |
| else |
| --to; |
| } |
| |
| readl(dev->ioaddr + IntrStatus); /* clear interrrupt bits */ |
| return; |
| } |
| |
| static void fa311_disable(struct nic *nic) |
| { |
| struct FA311_DEV* dev = &fa311_dev; |
| |
| /* Stop the chip's Tx and Rx processes. */ |
| writel(RxOff | TxOff, dev->ioaddr + ChipCmd); |
| } |
| |
| |
| /* Read the EEPROM and MII Management Data I/O (MDIO) interfaces. |
| The EEPROM code is for the common 93c06/46 EEPROMs with 6 bit addresses. */ |
| |
| /* Delay between EEPROM clock transitions. |
| No extra delay is needed with 33Mhz PCI, but future 66Mhz access may need |
| a delay. Note that pre-2.0.34 kernels had a cache-alignment bug that |
| made udelay() unreliable. |
| The old method of using an ISA access as a delay, __SLOW_DOWN_IO__, is |
| depricated. |
| */ |
| #define eeprom_delay(ee_addr) inl(ee_addr) |
| |
| enum EEPROM_Ctrl_Bits { |
| EE_ShiftClk=0x04, EE_DataIn=0x01, EE_ChipSelect=0x08, EE_DataOut=0x02, |
| }; |
| #define EE_Write0 (EE_ChipSelect) |
| #define EE_Write1 (EE_ChipSelect | EE_DataIn) |
| |
| /* The EEPROM commands include the alway-set leading bit. */ |
| enum EEPROM_Cmds { |
| EE_WriteCmd=(5 << 6), EE_ReadCmd=(6 << 6), EE_EraseCmd=(7 << 6), |
| }; |
| |
| |
| static int eeprom_read(long addr, int location) |
| { |
| int i; |
| int retval = 0; |
| int ee_addr = addr + EECtrl; |
| int read_cmd = location | EE_ReadCmd; |
| writel(EE_Write0, ee_addr); |
| |
| /* Shift the read command bits out. */ |
| for (i = 10; i >= 0; i--) { |
| short dataval = (read_cmd & (1 << i)) ? EE_Write1 : EE_Write0; |
| writel(dataval, ee_addr); |
| eeprom_delay(ee_addr); |
| writel(dataval | EE_ShiftClk, ee_addr); |
| eeprom_delay(ee_addr); |
| } |
| writel(EE_ChipSelect, ee_addr); |
| eeprom_delay(ee_addr); |
| |
| for (i = 0; i < 16; i++) { |
| writel(EE_ChipSelect | EE_ShiftClk, ee_addr); |
| eeprom_delay(ee_addr); |
| retval |= (readl(ee_addr) & EE_DataOut) ? 1 << i : 0; |
| writel(EE_ChipSelect, ee_addr); |
| eeprom_delay(ee_addr); |
| } |
| |
| /* Terminate the EEPROM access. */ |
| writel(EE_Write0, ee_addr); |
| writel(0, ee_addr); |
| return retval; |
| } |
| |
| /* Initialize the Rx and Tx rings, along with various 'dev' bits. */ |
| static void init_ring(struct FA311_DEV *dev) |
| { |
| int i; |
| |
| dev->cur_rx = 0; |
| dev->cur_tx = 0; |
| |
| dev->rx_buf_sz = PKT_BUF_SZ; |
| dev->rx_head_desc = &dev->rx_ring[0]; |
| |
| /* Initialize all Rx descriptors. */ |
| for (i = 0; i < RX_RING_SIZE; i++) { |
| dev->rx_ring[i].next_desc = virt_to_le32desc(&dev->rx_ring[i+1]); |
| dev->rx_ring[i].cmd_status = DescOwn; |
| } |
| /* Mark the last entry as wrapping the ring. */ |
| dev->rx_ring[i-1].next_desc = virt_to_le32desc(&dev->rx_ring[0]); |
| |
| /* Fill in the Rx buffers. Handle allocation failure gracefully. */ |
| for (i = 0; i < RX_RING_SIZE; i++) { |
| dev->rx_ring[i].addr = (u32)(&rx_packet[PKT_BUF_SZ * i]); |
| dev->rx_ring[i].cmd_status = cpu_to_le32(dev->rx_buf_sz); |
| } |
| |
| for (i = 0; i < TX_RING_SIZE; i++) { |
| dev->tx_ring[i].next_desc = virt_to_le32desc(&dev->tx_ring[i+1]); |
| dev->tx_ring[i].cmd_status = 0; |
| } |
| dev->tx_ring[i-1].next_desc = virt_to_le32desc(&dev->tx_ring[0]); |
| |
| for (i = 0; i < TX_RING_SIZE; i++) |
| dev->tx_ring[i].addr = (u32)(&tx_packet[PKT_BUF_SZ * i]); |
| return; |
| } |
| |