summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/gpxe/src/drivers/net/natsemi.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/syslinux-4.02/gpxe/src/drivers/net/natsemi.c')
-rw-r--r--contrib/syslinux-4.02/gpxe/src/drivers/net/natsemi.c609
1 files changed, 609 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/gpxe/src/drivers/net/natsemi.c b/contrib/syslinux-4.02/gpxe/src/drivers/net/natsemi.c
new file mode 100644
index 0000000..db3f320
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/drivers/net/natsemi.c
@@ -0,0 +1,609 @@
+/*
+ natsemi.c - gPXE driver for the NatSemi DP8381x series.
+
+ Based on:
+
+ natsemi.c: An Etherboot driver for the NatSemi DP8381x series.
+
+ Copyright (C) 2001 Entity Cyber, Inc.
+
+ This development of this Etherboot driver was funded by
+
+ Sicom Systems: http://www.sicompos.com/
+
+ Author: Marty Connor <mdc@etherboot.org>
+ Adapted from a Linux driver which was written by Donald Becker
+
+ This software may be used and distributed according to the terms
+ of the GNU Public License (GPL), incorporated herein by reference.
+
+ Original Copyright Notice:
+
+ Written/copyright 1999-2001 by Donald Becker.
+
+ 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
+
+ Support information and updates available at
+ http://www.scyld.com/network/netsemi.html
+
+ References:
+
+ http://www.scyld.com/expert/100mbps.html
+ http://www.scyld.com/expert/NWay.html
+ Datasheet is available from:
+ http://www.national.com/pf/DP/DP83815.html
+
+*/
+
+FILE_LICENCE ( GPL_ANY );
+
+/* Revision History */
+
+/*
+ 02 Jul 2007 Udayan Kumar 1.2 ported the driver from etherboot to gPXE API.
+ Fully rewritten,adapting the old driver.
+ Added a circular buffer for transmit and receive.
+ transmit routine will not wait for transmission to finish.
+ poll routine deals with it.
+ 13 Dec 2003 Tim Legge 1.1 Enabled Multicast Support
+ 29 May 2001 Marty Connor 1.0 Initial Release. Tested with Netgear FA311 and FA312 boards
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gpxe/io.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <unistd.h>
+#include <gpxe/pci.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ethernet.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/spi_bit.h>
+#include <gpxe/threewire.h>
+#include <gpxe/nvo.h>
+#include "natsemi.h"
+
+/* Function Prototypes: */
+
+static int natsemi_spi_read_bit ( struct bit_basher *, unsigned int );
+static void natsemi_spi_write_bit ( struct bit_basher *,unsigned int, unsigned long );
+static void natsemi_init_eeprom ( struct natsemi_private * );
+static int natsemi_probe (struct pci_device *pci, const struct pci_device_id *id);
+static void natsemi_reset (struct net_device *netdev);
+static int natsemi_open (struct net_device *netdev);
+static int natsemi_transmit (struct net_device *netdev, struct io_buffer *iobuf);
+static void natsemi_poll (struct net_device *netdev);
+static void natsemi_close (struct net_device *netdev);
+static void natsemi_irq (struct net_device *netdev, int enable);
+static void natsemi_remove (struct pci_device *pci);
+
+/** natsemi net device operations */
+static struct net_device_operations natsemi_operations = {
+ .open = natsemi_open,
+ .close = natsemi_close,
+ .transmit = natsemi_transmit,
+ .poll = natsemi_poll,
+ .irq = natsemi_irq,
+};
+
+static int natsemi_spi_read_bit ( struct bit_basher *basher,
+ unsigned int bit_id ) {
+ struct natsemi_private *np = container_of ( basher, struct natsemi_private,
+ spibit.basher );
+ uint8_t mask = natsemi_ee_bits[bit_id];
+ uint8_t eereg;
+
+ eereg = inb ( np->ioaddr + EE_REG );
+ return ( eereg & mask );
+}
+
+static void natsemi_spi_write_bit ( struct bit_basher *basher,
+ unsigned int bit_id, unsigned long data ) {
+ struct natsemi_private *np = container_of ( basher, struct natsemi_private,
+ spibit.basher );
+ uint8_t mask = natsemi_ee_bits[bit_id];
+ uint8_t eereg;
+
+ eereg = inb ( np->ioaddr + EE_REG );
+ eereg &= ~mask;
+ eereg |= ( data & mask );
+ outb ( eereg, np->ioaddr + EE_REG );
+}
+
+static struct bit_basher_operations natsemi_basher_ops = {
+ .read = natsemi_spi_read_bit,
+ .write = natsemi_spi_write_bit,
+};
+
+/* It looks that this portion of EEPROM can be used for
+ * non-volatile stored options. Data sheet does not talk about this region.
+ * Currently it is not working. But with some efforts it can.
+ */
+static struct nvo_fragment natsemi_nvo_fragments[] = {
+ { 0x0c, 0x68 },
+ { 0, 0 }
+};
+
+/*
+ * Set up for EEPROM access
+ *
+ * @v NAT NATSEMI NIC
+ */
+static void natsemi_init_eeprom ( struct natsemi_private *np ) {
+
+ /* Initialise three-wire bus
+ */
+ np->spibit.basher.op = &natsemi_basher_ops;
+ np->spibit.bus.mode = SPI_MODE_THREEWIRE;
+ np->spibit.endianness = SPI_BIT_LITTLE_ENDIAN;
+ init_spi_bit_basher ( &np->spibit );
+
+ /*natsemi DP 83815 only supports at93c46
+ */
+ init_at93c46 ( &np->eeprom, 16 );
+ np->eeprom.bus = &np->spibit.bus;
+ np->nvo.nvs = &np->eeprom.nvs;
+ np->nvo.fragments = natsemi_nvo_fragments;
+}
+
+/**
+ * Probe PCI device
+ *
+ * @v pci PCI device
+ * @v id PCI ID
+ * @ret rc Return status code
+ */
+static int natsemi_probe (struct pci_device *pci,
+ const struct pci_device_id *id __unused) {
+ struct net_device *netdev;
+ struct natsemi_private *np = NULL;
+ uint8_t ll_addr_encoded[MAX_LL_ADDR_LEN];
+ uint8_t last=0,last1=0;
+ uint8_t prev_bytes[2];
+ int i;
+ int rc;
+
+ /* Allocate net device
+ */
+ netdev = alloc_etherdev (sizeof (*np));
+ if (! netdev)
+ return -ENOMEM;
+
+ netdev_init (netdev, &natsemi_operations);
+ np = netdev->priv;
+ pci_set_drvdata (pci, netdev);
+ netdev->dev = &pci->dev;
+ memset (np, 0, sizeof (*np));
+ np->ioaddr = pci->ioaddr;
+
+ adjust_pci_device (pci);
+
+ natsemi_reset (netdev);
+ natsemi_init_eeprom ( np );
+ nvs_read ( &np->eeprom.nvs, EE_MAC-1, prev_bytes, 1 );
+ nvs_read ( &np->eeprom.nvs, EE_MAC, ll_addr_encoded, ETH_ALEN );
+
+ /* decoding the MAC address read from NVS
+ * and save it in netdev->ll_addr
+ */
+ last = prev_bytes[1] >> 7;
+ for ( i = 0 ; i < ETH_ALEN ; i++ ) {
+ last1 = ll_addr_encoded[i] >> 7;
+ netdev->hw_addr[i] = ll_addr_encoded[i] << 1 | last;
+ last = last1;
+ }
+
+ /* Mark as link up; we don't yet handle link state */
+ netdev_link_up ( netdev );
+
+ if ((rc = register_netdev (netdev)) != 0)
+ goto err_register_netdev;
+
+ return 0;
+
+err_register_netdev:
+
+ natsemi_reset (netdev);
+ netdev_put (netdev);
+ return rc;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci PCI device
+ */
+static void natsemi_remove (struct pci_device *pci) {
+ struct net_device *netdev = pci_get_drvdata (pci);
+
+ unregister_netdev (netdev);
+ natsemi_reset (netdev);
+ netdev_nullify ( netdev );
+ netdev_put (netdev);
+}
+
+/**
+ * Reset NIC
+ *
+ * @v NATSEMI NIC
+ *
+ * Issues a hardware reset and waits for the reset to complete.
+ */
+static void natsemi_reset (struct net_device *netdev)
+{
+ struct natsemi_private *np = netdev->priv;
+ int i;
+ u32 cfg;
+ u32 wcsr;
+ u32 rfcr;
+ u16 pmatch[3];
+ u16 sopass[3];
+
+ natsemi_irq (netdev, 0);
+
+ /*
+ * Resetting the chip causes some registers to be lost.
+ * Natsemi suggests NOT reloading the EEPROM while live, so instead
+ * we save the state that would have been loaded from EEPROM
+ * on a normal power-up (see the spec EEPROM map).
+ */
+
+ /* CFG */
+ cfg = inl (np->ioaddr + ChipConfig) & CFG_RESET_SAVE;
+
+ /* WCSR */
+ wcsr = inl (np->ioaddr + WOLCmd) & WCSR_RESET_SAVE;
+
+ /* RFCR */
+ rfcr = inl (np->ioaddr + RxFilterAddr) & RFCR_RESET_SAVE;
+
+ /* PMATCH */
+ for (i = 0; i < 3; i++) {
+ outl(i*2, np->ioaddr + RxFilterAddr);
+ pmatch[i] = inw(np->ioaddr + RxFilterData);
+ }
+
+ /* SOPAS */
+ for (i = 0; i < 3; i++) {
+ outl(0xa+(i*2), np->ioaddr + RxFilterAddr);
+ sopass[i] = inw(np->ioaddr + RxFilterData);
+ }
+
+ /* now whack the chip */
+ outl(ChipReset, np->ioaddr + ChipCmd);
+ for (i=0; i<NATSEMI_HW_TIMEOUT; i++) {
+ if (! (inl (np->ioaddr + ChipCmd) & ChipReset))
+ break;
+ udelay(5);
+ }
+ if (i == NATSEMI_HW_TIMEOUT) {
+ DBG ("natsemi_reset: reset did not complete in %d usec.\n", i*5);
+ }
+
+ /* restore CFG */
+ cfg |= inl(np->ioaddr + ChipConfig) & ~CFG_RESET_SAVE;
+ cfg &= ~(CfgExtPhy | CfgPhyDis);
+ outl (cfg, np->ioaddr + ChipConfig);
+
+ /* restore WCSR */
+ wcsr |= inl (np->ioaddr + WOLCmd) & ~WCSR_RESET_SAVE;
+ outl (wcsr, np->ioaddr + WOLCmd);
+
+ /* read RFCR */
+ rfcr |= inl (np->ioaddr + RxFilterAddr) & ~RFCR_RESET_SAVE;
+
+ /* restore PMATCH */
+ for (i = 0; i < 3; i++) {
+ outl (i*2, np->ioaddr + RxFilterAddr);
+ outw (pmatch[i], np->ioaddr + RxFilterData);
+ }
+ for (i = 0; i < 3; i++) {
+ outl (0xa+(i*2), np->ioaddr + RxFilterAddr);
+ outw (sopass[i], np->ioaddr + RxFilterData);
+ }
+ /* restore RFCR */
+ outl (rfcr, np->ioaddr + RxFilterAddr);
+}
+
+/**
+ * Open NIC
+ *
+ * @v netdev Net device
+ * @ret rc Return status code
+ */
+static int natsemi_open (struct net_device *netdev)
+{
+ struct natsemi_private *np = netdev->priv;
+ uint32_t tx_config, rx_config;
+ int i;
+
+ /* Disable PME:
+ * The PME bit is initialized from the EEPROM contents.
+ * PCI cards probably have PME disabled, but motherboard
+ * implementations may have PME set to enable WakeOnLan.
+ * With PME set the chip will scan incoming packets but
+ * nothing will be written to memory.
+ */
+ outl (inl (np->ioaddr + ClkRun) & ~0x100, np->ioaddr + ClkRun);
+
+ /* Set MAC address in NIC
+ */
+ for (i = 0 ; i < ETH_ALEN ; i+=2) {
+ outl (i, np->ioaddr + RxFilterAddr);
+ outw (netdev->ll_addr[i] + (netdev->ll_addr[i + 1] << 8),
+ np->ioaddr + RxFilterData);
+ }
+
+ /* Setup Tx Ring
+ */
+ np->tx_cur = 0;
+ np->tx_dirty = 0;
+ for (i = 0 ; i < TX_RING_SIZE ; i++) {
+ np->tx[i].link = virt_to_bus ((i + 1 < TX_RING_SIZE) ? &np->tx[i + 1] : &np->tx[0]);
+ np->tx[i].cmdsts = 0;
+ np->tx[i].bufptr = 0;
+ }
+ outl (virt_to_bus (&np->tx[0]),np->ioaddr + TxRingPtr);
+
+ DBG ("Natsemi Tx descriptor loaded with: %#08x\n",
+ inl (np->ioaddr + TxRingPtr));
+
+ /* Setup RX ring
+ */
+ np->rx_cur = 0;
+ for (i = 0 ; i < NUM_RX_DESC ; i++) {
+ np->iobuf[i] = alloc_iob (RX_BUF_SIZE);
+ if (! np->iobuf[i])
+ goto memory_alloc_err;
+ np->rx[i].link = virt_to_bus ((i + 1 < NUM_RX_DESC)
+ ? &np->rx[i + 1] : &np->rx[0]);
+ np->rx[i].cmdsts = RX_BUF_SIZE;
+ np->rx[i].bufptr = virt_to_bus (np->iobuf[i]->data);
+ DBG (" Address of iobuf [%d] = %p and iobuf->data = %p \n", i,
+ &np->iobuf[i], &np->iobuf[i]->data);
+ }
+ outl (virt_to_bus (&np->rx[0]), np->ioaddr + RxRingPtr);
+
+ DBG ("Natsemi Rx descriptor loaded with: %#08x\n",
+ inl (np->ioaddr + RxRingPtr));
+
+ /* Setup RX Filter
+ */
+ outl (RxFilterEnable | AcceptBroadcast | AcceptAllMulticast | AcceptMyPhys,
+ np->ioaddr + RxFilterAddr);
+
+ /* Initialize other registers.
+ * Configure the PCI bus bursts and FIFO thresholds.
+ * Configure for standard, in-spec Ethernet.
+ */
+ if (inl (np->ioaddr + ChipConfig) & 0x20000000) { /* Full duplex */
+ DBG ("Full duplex\n");
+ tx_config = 0xD0801002 | 0xC0000000;
+ rx_config = 0x10000020 | 0x10000000;
+ } else {
+ DBG ("Half duplex\n");
+ tx_config = 0x10801002 & ~0xC0000000;
+ rx_config = 0x00000020 & ~0x10000000;
+ }
+ outl (tx_config, np->ioaddr + TxConfig);
+ outl (rx_config, np->ioaddr + RxConfig);
+
+ DBG ("Tx config register = %#08x Rx config register = %#08x\n",
+ inl (np->ioaddr + TxConfig),
+ inl (np->ioaddr + RxConfig));
+
+ /*Set the Interrupt Mask register
+ */
+ outl((RxOk|RxErr|TxOk|TxErr),np->ioaddr + IntrMask);
+ /*start the receiver
+ */
+ outl (RxOn, np->ioaddr + ChipCmd);
+
+ return 0;
+
+memory_alloc_err:
+
+ /* Frees any allocated buffers when memory
+ * for all buffers requested is not available
+ */
+ i = 0;
+ while (np->rx[i].cmdsts == RX_BUF_SIZE) {
+ free_iob (np->iobuf[i]);
+ i++;
+ }
+ return -ENOMEM;
+}
+
+/**
+ * Close NIC
+ *
+ * @v netdev Net device
+ */
+static void natsemi_close (struct net_device *netdev)
+{
+ struct natsemi_private *np = netdev->priv;
+ int i;
+
+ natsemi_reset (netdev);
+
+ for (i = 0; i < NUM_RX_DESC ; i++) {
+ free_iob (np->iobuf[i]);
+ }
+}
+
+/**
+ * Transmit packet
+ *
+ * @v netdev Network device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int natsemi_transmit (struct net_device *netdev, struct io_buffer *iobuf)
+{
+ struct natsemi_private *np = netdev->priv;
+
+ if (np->tx[np->tx_cur].cmdsts != 0) {
+ DBG ("TX overflow\n");
+ return -ENOBUFS;
+ }
+
+ /* Used by netdev_tx_complete ()
+ */
+ np->tx_iobuf[np->tx_cur] = iobuf;
+
+ /* Pad and align packet has not been used because its not required
+ * by the hardware.
+ * iob_pad (iobuf, ETH_ZLEN);
+ * can be used to achieve it, if required
+ */
+
+ /* Add the packet to TX ring
+ */
+ np->tx[np->tx_cur].bufptr = virt_to_bus (iobuf->data);
+ np->tx[np->tx_cur].cmdsts = iob_len (iobuf) | OWN;
+
+ DBG ("TX id %d at %#08lx + %#08zx\n", np->tx_cur,
+ virt_to_bus (&iobuf->data), iob_len (iobuf));
+
+ /* increment the circular buffer pointer to the next buffer location
+ */
+ np->tx_cur = (np->tx_cur + 1) % TX_RING_SIZE;
+
+ /*start the transmitter
+ */
+ outl (TxOn, np->ioaddr + ChipCmd);
+
+ return 0;
+}
+
+/**
+ * Poll for received packets
+ *
+ * @v netdev Network device
+ */
+static void natsemi_poll (struct net_device *netdev)
+{
+ struct natsemi_private *np = netdev->priv;
+ unsigned int tx_status;
+ unsigned int rx_status;
+ unsigned int intr_status;
+ unsigned int rx_len;
+ struct io_buffer *rx_iob;
+ int i;
+
+ /* read the interrupt register
+ */
+ intr_status = inl (np->ioaddr + IntrStatus);
+
+ if (!intr_status)
+ goto end;
+
+ DBG ("natsemi_poll: intr_status = %#08x\n", intr_status);
+
+ /* Check status of transmitted packets
+ */
+ i = np->tx_dirty;
+ while (i != np->tx_cur) {
+ tx_status = np->tx[np->tx_dirty].cmdsts;
+
+ DBG ("tx_dirty = %d tx_cur=%d tx_status=%#08x\n",
+ np->tx_dirty, np->tx_cur, tx_status);
+
+ if (tx_status & OWN)
+ break;
+
+ if (! (tx_status & DescPktOK)) {
+ netdev_tx_complete_err (netdev,np->tx_iobuf[np->tx_dirty],-EINVAL);
+ DBG ("Error transmitting packet, tx_status: %#08x\n",
+ tx_status);
+ } else {
+ netdev_tx_complete (netdev, np->tx_iobuf[np->tx_dirty]);
+ DBG ("Success transmitting packet\n");
+ }
+
+ np->tx[np->tx_dirty].cmdsts = 0;
+ np->tx_dirty = (np->tx_dirty + 1) % TX_RING_SIZE;
+ i = (i + 1) % TX_RING_SIZE;
+ }
+
+ /* Process received packets
+ */
+ rx_status = (unsigned int) np->rx[np->rx_cur].cmdsts;
+ while ((rx_status & OWN)) {
+ rx_len = (rx_status & DSIZE) - CRC_SIZE;
+
+ DBG ("Received packet, rx_curr = %d, rx_status = %#08x, rx_len = %d\n",
+ np->rx_cur, rx_status, rx_len);
+
+ if ((rx_status & (DescMore | DescPktOK | RxTooLong)) != DescPktOK) {
+ netdev_rx_err (netdev, NULL, -EINVAL);
+
+ DBG ("natsemi_poll: Corrupted packet received!"
+ " Status = %#08x\n",
+ np->rx[np->rx_cur].cmdsts);
+
+ } else {
+
+
+ /* If unable allocate space for this packet,
+ * try again next poll
+ */
+ rx_iob = alloc_iob (rx_len);
+ if (! rx_iob)
+ goto end;
+ memcpy (iob_put (rx_iob, rx_len),
+ np->iobuf[np->rx_cur]->data, rx_len);
+ /* Add this packet to the receive queue.
+ */
+ netdev_rx (netdev, rx_iob);
+ }
+ np->rx[np->rx_cur].cmdsts = RX_BUF_SIZE;
+ np->rx_cur = (np->rx_cur + 1) % NUM_RX_DESC;
+ rx_status = np->rx[np->rx_cur].cmdsts;
+ }
+end:
+ /* re-enable the potentially idle receive state machine
+ */
+ outl (RxOn, np->ioaddr + ChipCmd);
+}
+
+/**
+ * Enable/disable interrupts
+ *
+ * @v netdev Network device
+ * @v enable Non-zero for enable, zero for disable
+ */
+static void natsemi_irq (struct net_device *netdev, int enable)
+{
+ struct natsemi_private *np = netdev->priv;
+
+ outl ((enable ? (RxOk | RxErr | TxOk|TxErr) : 0),
+ np->ioaddr + IntrMask);
+ outl ((enable ? 1 : 0), np->ioaddr + IntrEnable);
+}
+
+static struct pci_device_id natsemi_nics[] = {
+ PCI_ROM(0x100b, 0x0020, "dp83815", "DP83815", 0),
+};
+
+struct pci_driver natsemi_driver __pci_driver = {
+ .ids = natsemi_nics,
+ .id_count = (sizeof (natsemi_nics) / sizeof (natsemi_nics[0])),
+ .probe = natsemi_probe,
+ .remove = natsemi_remove,
+};