Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / drivers / net / sundance.c
diff --git a/qemu/roms/ipxe/src/drivers/net/sundance.c b/qemu/roms/ipxe/src/drivers/net/sundance.c
new file mode 100644 (file)
index 0000000..eef7c9c
--- /dev/null
@@ -0,0 +1,899 @@
+/**************************************************************************
+*
+*    sundance.c -- Etherboot device driver for the Sundance ST201 "Alta".
+*    Written 2002-2002 by Timothy Legge <tlegge@rogers.com>
+*
+*    This program is free software; you can redistribute it and/or modify
+*    it under the terms of the GNU General Public License as published by
+*    the Free Software Foundation; either version 2 of the License, or
+*    (at your option) any later version.
+*
+*    This program is distributed in the hope that it will be useful,
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*    GNU General Public License for more details.
+*
+*    You should have received a copy of the GNU General Public License
+*    along with this program; if not, write to the Free Software
+*    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+*    02110-1301, USA.
+*
+*    Portions of this code based on:
+*               sundance.c: A Linux device driver for the Sundance ST201 "Alta"
+*               Written 1999-2002 by Donald Becker
+*
+*               tulip.c: Tulip and Clone Etherboot Driver
+*               By Marty Conner
+*               Copyright (C) 2001 Entity Cyber, Inc.
+*
+*    Linux Driver Version LK1.09a, 10-Jul-2003 (2.4.25)
+*
+*    REVISION HISTORY:
+*    ================
+*    v1.1      01-01-2003      timlegge        Initial implementation
+*    v1.7      04-10-2003      timlegge        Transfers Linux Kernel (30 sec)
+*    v1.8      04-13-2003      timlegge        Fix multiple transmission bug
+*    v1.9      08-19-2003      timlegge        Support Multicast
+*    v1.10     01-17-2004      timlegge        Initial driver output cleanup
+*    v1.11     03-21-2004      timlegge        Remove unused variables
+*    v1.12     03-21-2004      timlegge        Remove excess MII defines
+*    v1.13     03-24-2004      timlegge        Update to Linux 2.4.25 driver
+*
+****************************************************************************/
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/* to get some global routines like printf */
+#include "etherboot.h"
+/* to get the interface to the body of the program */
+#include "nic.h"
+/* to get the PCI support functions, if this is a PCI NIC */
+#include <ipxe/pci.h>
+#include "mii.h"
+
+#define drv_version "v1.12"
+#define drv_date "2004-03-21"
+
+#define HZ 100
+
+/* Condensed operations for readability. */
+#define virt_to_le32desc(addr)  cpu_to_le32(virt_to_bus(addr))
+#define le32desc_to_virt(addr)  bus_to_virt(le32_to_cpu(addr))
+
+/* Set the mtu */
+static int mtu = 1514;
+
+/* Maximum number of multicast addresses to filter (vs. rx-all-multicast).
+   The sundance uses a 64 element hash table based on the Ethernet CRC.  */
+// static int multicast_filter_limit = 32;
+
+/* Set the copy breakpoint for the copy-only-tiny-frames scheme.
+   Setting to > 1518 effectively disables this feature.
+   This chip can receive into any byte alignment buffers, so word-oriented
+   archs do not need a copy-align of the IP header. */
+static int rx_copybreak = 0;
+static int flowctrl = 1;
+
+/* Allow forcing the media type */
+/* media[] specifies the media type the NIC operates at.
+                autosense      Autosensing active media.
+                10mbps_hd      10Mbps half duplex.
+                10mbps_fd      10Mbps full duplex.
+                100mbps_hd     100Mbps half duplex.
+                100mbps_fd     100Mbps full duplex.
+*/
+static char media[] = "autosense";
+
+/* Operational parameters that are set at compile time. */
+
+/* As Etherboot uses a Polling driver  we can keep the number of rings
+to the minimum number required.  In general that is 1 transmit and 4 receive receive rings.  However some cards require that
+there be a minimum of 2 rings  */
+#define TX_RING_SIZE   2
+#define TX_QUEUE_LEN   10      /* Limit ring entries actually used.  */
+#define RX_RING_SIZE   4
+
+
+/* Operational parameters that usually are not changed. */
+/* Time in jiffies before concluding the transmitter is hung. */
+#define TX_TIME_OUT      (4*HZ)
+#define PKT_BUF_SZ     1536
+
+/* Offsets to the device registers.
+   Unlike software-only systems, device drivers interact with complex hardware.
+   It's not useful to define symbolic names for every register bit in the
+   device.  The name can only partially document the semantics and make
+   the driver longer and more difficult to read.
+   In general, only the important configuration values or bits changed
+   multiple times should be defined symbolically.
+*/
+enum alta_offsets {
+       DMACtrl = 0x00,
+       TxListPtr = 0x04,
+       TxDMABurstThresh = 0x08,
+       TxDMAUrgentThresh = 0x09,
+       TxDMAPollPeriod = 0x0a,
+       RxDMAStatus = 0x0c,
+       RxListPtr = 0x10,
+       DebugCtrl0 = 0x1a,
+       DebugCtrl1 = 0x1c,
+       RxDMABurstThresh = 0x14,
+       RxDMAUrgentThresh = 0x15,
+       RxDMAPollPeriod = 0x16,
+       LEDCtrl = 0x1a,
+       ASICCtrl = 0x30,
+       EEData = 0x34,
+       EECtrl = 0x36,
+       TxStartThresh = 0x3c,
+       RxEarlyThresh = 0x3e,
+       FlashAddr = 0x40,
+       FlashData = 0x44,
+       TxStatus = 0x46,
+       TxFrameId = 0x47,
+       DownCounter = 0x18,
+       IntrClear = 0x4a,
+       IntrEnable = 0x4c,
+       IntrStatus = 0x4e,
+       MACCtrl0 = 0x50,
+       MACCtrl1 = 0x52,
+       StationAddr = 0x54,
+       MaxFrameSize = 0x5A,
+       RxMode = 0x5c,
+       MIICtrl = 0x5e,
+       MulticastFilter0 = 0x60,
+       MulticastFilter1 = 0x64,
+       RxOctetsLow = 0x68,
+       RxOctetsHigh = 0x6a,
+       TxOctetsLow = 0x6c,
+       TxOctetsHigh = 0x6e,
+       TxFramesOK = 0x70,
+       RxFramesOK = 0x72,
+       StatsCarrierError = 0x74,
+       StatsLateColl = 0x75,
+       StatsMultiColl = 0x76,
+       StatsOneColl = 0x77,
+       StatsTxDefer = 0x78,
+       RxMissed = 0x79,
+       StatsTxXSDefer = 0x7a,
+       StatsTxAbort = 0x7b,
+       StatsBcastTx = 0x7c,
+       StatsBcastRx = 0x7d,
+       StatsMcastTx = 0x7e,
+       StatsMcastRx = 0x7f,
+       /* Aliased and bogus values! */
+       RxStatus = 0x0c,
+};
+enum ASICCtrl_HiWord_bit {
+       GlobalReset = 0x0001,
+       RxReset = 0x0002,
+       TxReset = 0x0004,
+       DMAReset = 0x0008,
+       FIFOReset = 0x0010,
+       NetworkReset = 0x0020,
+       HostReset = 0x0040,
+       ResetBusy = 0x0400,
+};
+
+/* Bits in the interrupt status/mask registers. */
+enum intr_status_bits {
+       IntrSummary = 0x0001, IntrPCIErr = 0x0002, IntrMACCtrl = 0x0008,
+       IntrTxDone = 0x0004, IntrRxDone = 0x0010, IntrRxStart = 0x0020,
+       IntrDrvRqst = 0x0040,
+       StatsMax = 0x0080, LinkChange = 0x0100,
+       IntrTxDMADone = 0x0200, IntrRxDMADone = 0x0400,
+};
+
+/* Bits in the RxMode register. */
+enum rx_mode_bits {
+       AcceptAllIPMulti = 0x20, AcceptMultiHash = 0x10, AcceptAll = 0x08,
+       AcceptBroadcast = 0x04, AcceptMulticast = 0x02, AcceptMyPhys =
+           0x01,
+};
+/* Bits in MACCtrl. */
+enum mac_ctrl0_bits {
+       EnbFullDuplex = 0x20, EnbRcvLargeFrame = 0x40,
+       EnbFlowCtrl = 0x100, EnbPassRxCRC = 0x200,
+};
+enum mac_ctrl1_bits {
+       StatsEnable = 0x0020, StatsDisable = 0x0040, StatsEnabled = 0x0080,
+       TxEnable = 0x0100, TxDisable = 0x0200, TxEnabled = 0x0400,
+       RxEnable = 0x0800, RxDisable = 0x1000, RxEnabled = 0x2000,
+};
+
+/* The Rx and Tx buffer descriptors.
+   Using only 32 bit fields simplifies software endian correction.
+   This structure must be aligned, and should avoid spanning cache lines.
+*/
+struct netdev_desc {
+       u32 next_desc;
+       u32 status;
+       u32 addr;
+       u32 length;
+};
+
+/* Bits in netdev_desc.status */
+enum desc_status_bits {
+       DescOwn = 0x8000,
+       DescEndPacket = 0x4000,
+       DescEndRing = 0x2000,
+       LastFrag = 0x80000000,
+       DescIntrOnTx = 0x8000,
+       DescIntrOnDMADone = 0x80000000,
+       DisableAlign = 0x00000001,
+};
+
+/**********************************************
+* Descriptor Ring and Buffer defination
+***********************************************/
+/* Define the TX Descriptor */
+static struct netdev_desc tx_ring[TX_RING_SIZE];
+
+/* Define the RX Descriptor */
+static struct netdev_desc rx_ring[RX_RING_SIZE];
+
+/* Create a static buffer of size PKT_BUF_SZ for each RX and TX descriptor.
+   All descriptors point to a part of this buffer */
+struct {
+       unsigned char txb[PKT_BUF_SZ * TX_RING_SIZE];
+       unsigned char rxb[RX_RING_SIZE * PKT_BUF_SZ];
+} rx_tx_buf __shared;
+#define rxb rx_tx_buf.rxb
+#define txb rx_tx_buf.txb
+
+/* FIXME: Move BASE to the private structure */
+static u32 BASE;
+#define EEPROM_SIZE    128
+
+enum pci_id_flags_bits {
+       PCI_USES_IO = 1, PCI_USES_MEM = 2, PCI_USES_MASTER = 4,
+       PCI_ADDR0 = 0 << 4, PCI_ADDR1 = 1 << 4, PCI_ADDR2 =
+           2 << 4, PCI_ADDR3 = 3 << 4,
+};
+
+enum chip_capability_flags { CanHaveMII = 1, KendinPktDropBug = 2, };
+#define PCI_IOTYPE (PCI_USES_MASTER | PCI_USES_IO  | PCI_ADDR0)
+
+#define MII_CNT                4
+static struct sundance_private {
+       const char *nic_name;
+       /* Frequently used values */
+
+       unsigned int cur_rx;    /* Producer/consumer ring indices */
+       unsigned int mtu;
+
+       /* These values keep track of the tranceiver/media in use */
+       unsigned int flowctrl:1;
+       unsigned int an_enable:1;
+
+       unsigned int speed;
+
+       /* MII tranceiver section */
+       struct mii_if_info mii_if;
+       int mii_preamble_required;
+       unsigned char phys[MII_CNT];
+       unsigned char pci_rev_id;
+} sdx;
+
+static struct sundance_private *sdc;
+
+/* Station Address location within the EEPROM */
+#define EEPROM_SA_OFFSET       0x10
+#define DEFAULT_INTR (IntrRxDMADone | IntrPCIErr | \
+                        IntrDrvRqst | IntrTxDone | StatsMax | \
+                        LinkChange)
+
+static int eeprom_read(long ioaddr, int location);
+static int mdio_read(struct nic *nic, int phy_id, unsigned int location);
+static void mdio_write(struct nic *nic, int phy_id, unsigned int location,
+                      int value);
+static void set_rx_mode(struct nic *nic);
+
+static void check_duplex(struct nic *nic)
+{
+       int mii_lpa = mdio_read(nic, sdc->phys[0], MII_LPA);
+       int negotiated = mii_lpa & sdc->mii_if.advertising;
+       int duplex;
+
+       /* Force media */
+       if (!sdc->an_enable || mii_lpa == 0xffff) {
+               if (sdc->mii_if.full_duplex)
+                       outw(inw(BASE + MACCtrl0) | EnbFullDuplex,
+                            BASE + MACCtrl0);
+               return;
+       }
+
+       /* Autonegotiation */
+       duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040;
+       if (sdc->mii_if.full_duplex != duplex) {
+               sdc->mii_if.full_duplex = duplex;
+               DBG ("%s: Setting %s-duplex based on MII #%d "
+                        "negotiated capability %4.4x.\n", sdc->nic_name,
+                        duplex ? "full" : "half", sdc->phys[0],
+                        negotiated );
+               outw(inw(BASE + MACCtrl0) | duplex ? 0x20 : 0,
+                    BASE + MACCtrl0);
+       }
+}
+
+
+/**************************************************************************
+ *  init_ring - setup the tx and rx descriptors
+ *************************************************************************/
+static void init_ring(struct nic *nic __unused)
+{
+       int i;
+
+       sdc->cur_rx = 0;
+
+       /* Initialize all the Rx descriptors */
+       for (i = 0; i < RX_RING_SIZE; i++) {
+               rx_ring[i].next_desc = virt_to_le32desc(&rx_ring[i + 1]);
+               rx_ring[i].status = 0;
+               rx_ring[i].length = 0;
+               rx_ring[i].addr = 0;
+       }
+
+       /* Mark the last entry as wrapping the ring */
+       rx_ring[i - 1].next_desc = virt_to_le32desc(&rx_ring[0]);
+
+       for (i = 0; i < RX_RING_SIZE; i++) {
+               rx_ring[i].addr = virt_to_le32desc(&rxb[i * PKT_BUF_SZ]);
+               rx_ring[i].length = cpu_to_le32(PKT_BUF_SZ | LastFrag);
+       }
+
+       /* We only use one transmit buffer, but two
+        * descriptors so transmit engines have somewhere
+        * to point should they feel the need */
+       tx_ring[0].status = 0x00000000;
+       tx_ring[0].addr = virt_to_bus(&txb[0]);
+       tx_ring[0].next_desc = 0;       /* virt_to_bus(&tx_ring[1]); */
+
+       /* This descriptor is never used */
+       tx_ring[1].status = 0x00000000;
+       tx_ring[1].addr = 0;    /*virt_to_bus(&txb[0]); */
+       tx_ring[1].next_desc = 0;
+
+       /* Mark the last entry as wrapping the ring,
+        * though this should never happen */
+       tx_ring[1].length = cpu_to_le32(LastFrag | PKT_BUF_SZ);
+}
+
+/**************************************************************************
+ *  RESET - Reset Adapter
+ * ***********************************************************************/
+static void sundance_reset(struct nic *nic)
+{
+       int i;
+
+       init_ring(nic);
+
+       outl(virt_to_le32desc(&rx_ring[0]), BASE + RxListPtr);
+       /* The Tx List Pointer is written as packets are queued */
+
+       /* Initialize other registers. */
+       /* __set_mac_addr(dev); */
+       {
+               u16 addr16;
+
+               addr16 = (nic->node_addr[0] | (nic->node_addr[1] << 8));
+               outw(addr16, BASE + StationAddr);
+               addr16 = (nic->node_addr[2] | (nic->node_addr[3] << 8));
+               outw(addr16, BASE + StationAddr + 2);
+               addr16 = (nic->node_addr[4] | (nic->node_addr[5] << 8));
+               outw(addr16, BASE + StationAddr + 4);
+       }
+
+       outw(sdc->mtu + 14, BASE + MaxFrameSize);
+       if (sdc->mtu > 2047)    /* this will never happen with default options */
+               outl(inl(BASE + ASICCtrl) | 0x0c, BASE + ASICCtrl);
+
+       set_rx_mode(nic);
+
+       outw(0, BASE + DownCounter);
+       /* Set the chip to poll every N*30nsec */
+       outb(100, BASE + RxDMAPollPeriod);
+
+       /* Fix DFE-580TX packet drop issue */
+       if (sdc->pci_rev_id >= 0x14)
+               writeb(0x01, BASE + DebugCtrl1);
+
+       outw(RxEnable | TxEnable, BASE + MACCtrl1);
+
+       /* Construct a perfect filter frame with the mac address as first match
+        * and broadcast for all others */
+       for (i = 0; i < 192; i++)
+               txb[i] = 0xFF;
+
+       txb[0] = nic->node_addr[0];
+       txb[1] = nic->node_addr[1];
+       txb[2] = nic->node_addr[2];
+       txb[3] = nic->node_addr[3];
+       txb[4] = nic->node_addr[4];
+       txb[5] = nic->node_addr[5];
+
+       DBG ( "%s: Done sundance_reset, status: Rx %hX Tx %hX "
+             "MAC Control %hX, %hX %hX\n",
+             sdc->nic_name, (int) inl(BASE + RxStatus),
+             (int) inw(BASE + TxStatus), (int) inl(BASE + MACCtrl0),
+             (int) inw(BASE + MACCtrl1), (int) inw(BASE + MACCtrl0) );
+}
+
+/**************************************************************************
+IRQ - Wait for a frame
+***************************************************************************/
+static void sundance_irq ( struct nic *nic, irq_action_t action ) {
+        unsigned int intr_status;
+
+       switch ( action ) {
+       case DISABLE :
+       case ENABLE :
+               intr_status = inw(nic->ioaddr + IntrStatus);
+               intr_status = intr_status & ~DEFAULT_INTR;
+               if ( action == ENABLE ) 
+                       intr_status = intr_status | DEFAULT_INTR;
+               outw(intr_status, nic->ioaddr + IntrEnable);
+               break;
+        case FORCE :
+               outw(0x0200, BASE + ASICCtrl);
+               break;
+        }
+}
+/**************************************************************************
+POLL - Wait for a frame
+***************************************************************************/
+static int sundance_poll(struct nic *nic, int retrieve)
+{
+       /* return true if there's an ethernet packet ready to read */
+       /* nic->packet should contain data on return */
+       /* nic->packetlen should contain length of data */
+       int entry = sdc->cur_rx % RX_RING_SIZE;
+       u32 frame_status = le32_to_cpu(rx_ring[entry].status);
+       int intr_status;
+       int pkt_len = 0;
+
+       if (!(frame_status & DescOwn))
+               return 0;
+
+       /* There is a packet ready */
+       if(!retrieve)
+               return 1;
+
+       intr_status = inw(nic->ioaddr + IntrStatus);
+       outw(intr_status, nic->ioaddr + IntrStatus);
+
+       pkt_len = frame_status & 0x1fff;
+
+       if (frame_status & 0x001f4000) {
+               DBG ( "Polling frame_status error\n" ); /* Do we really care about this */
+       } else {
+               if (pkt_len < rx_copybreak) {
+                       /* FIXME: What should happen Will this ever occur */
+                       printf("Poll Error: pkt_len < rx_copybreak");
+               } else {
+                       nic->packetlen = pkt_len;
+                       memcpy(nic->packet, rxb +
+                              (sdc->cur_rx * PKT_BUF_SZ), nic->packetlen);
+
+               }
+       }
+       rx_ring[entry].length = cpu_to_le32(PKT_BUF_SZ | LastFrag);
+       rx_ring[entry].status = 0;
+       entry++;
+       sdc->cur_rx = entry % RX_RING_SIZE;
+       outw(DEFAULT_INTR & ~(IntrRxDone|IntrRxDMADone), 
+               nic->ioaddr + IntrStatus);
+       return 1;
+}
+
+/**************************************************************************
+TRANSMIT - Transmit a frame
+***************************************************************************/
+static void sundance_transmit(struct nic *nic, const char *d,  /* Destination */
+                             unsigned int t,   /* Type */
+                             unsigned int s,   /* size */
+                             const char *p)
+{                              /* Packet */
+       u16 nstype;
+       u32 to;
+
+       /* Disable the Tx */
+       outw(TxDisable, BASE + MACCtrl1);
+
+       memcpy(txb, d, ETH_ALEN);
+       memcpy(txb + ETH_ALEN, nic->node_addr, ETH_ALEN);
+       nstype = htons((u16) t);
+       memcpy(txb + 2 * ETH_ALEN, (u8 *) & nstype, 2);
+       memcpy(txb + ETH_HLEN, p, s);
+
+       s += ETH_HLEN;
+       s &= 0x0FFF;
+       while (s < ETH_ZLEN)
+               txb[s++] = '\0';
+
+       /* Setup the transmit descriptor */
+       tx_ring[0].length = cpu_to_le32(s | LastFrag);
+       tx_ring[0].status = cpu_to_le32(0x00000001);
+
+       /* Point to transmit descriptor */
+       outl(virt_to_le32desc(&tx_ring[0]), BASE + TxListPtr);
+
+       /* Enable Tx */
+       outw(TxEnable, BASE + MACCtrl1);
+       /* Trigger an immediate send */
+       outw(0, BASE + TxStatus);
+
+       to = currticks() + TX_TIME_OUT;
+       while (!(tx_ring[0].status & 0x00010000) && (currticks() < to));        /* wait */
+
+       if (currticks() >= to) {
+               printf("TX Time Out");
+       }
+       /* Disable Tx */
+       outw(TxDisable, BASE + MACCtrl1);
+
+}
+
+/**************************************************************************
+DISABLE - Turn off ethernet interface
+***************************************************************************/
+static void sundance_disable ( struct nic *nic __unused ) {
+       /* put the card in its initial state */
+       /* This function serves 3 purposes.
+        * This disables DMA and interrupts so we don't receive
+        *  unexpected packets or interrupts from the card after
+        *  etherboot has finished.
+        * This frees resources so etherboot may use
+        *  this driver on another interface
+        * This allows etherboot to reinitialize the interface
+        *  if something is something goes wrong.
+        */
+       outw(0x0000, BASE + IntrEnable);
+       /* Stop the Chipchips Tx and Rx Status */
+       outw(TxDisable | RxDisable | StatsDisable, BASE + MACCtrl1);
+}
+
+static struct nic_operations sundance_operations = {
+       .connect        = dummy_connect,
+       .poll           = sundance_poll,
+       .transmit       = sundance_transmit,
+       .irq            = sundance_irq,
+
+};
+
+/**************************************************************************
+PROBE - Look for an adapter, this routine's visible to the outside
+***************************************************************************/
+static int sundance_probe ( struct nic *nic, struct pci_device *pci ) {
+
+       u8 ee_data[EEPROM_SIZE];
+       u16 mii_ctl;
+       int i;
+       int speed;
+
+       if (pci->ioaddr == 0)
+               return 0;
+
+       /* BASE is used throughout to address the card */
+       BASE = pci->ioaddr;
+       printf(" sundance.c: Found %s Vendor=0x%hX Device=0x%hX\n",
+              pci->id->name, pci->vendor, pci->device);
+
+       /* Get the MAC Address by reading the EEPROM */
+       for (i = 0; i < 3; i++) {
+               ((u16 *) ee_data)[i] =
+                   le16_to_cpu(eeprom_read(BASE, i + EEPROM_SA_OFFSET));
+       }
+       /* Update the nic structure with the MAC Address */
+       for (i = 0; i < ETH_ALEN; i++) {
+               nic->node_addr[i] = ee_data[i];
+       }
+
+       /* Set the card as PCI Bus Master */
+       adjust_pci_device(pci);
+
+//      sdc->mii_if.dev = pci;
+//      sdc->mii_if.phy_id_mask = 0x1f;
+//      sdc->mii_if.reg_num_mask = 0x1f;
+
+       /* point to private storage */
+       sdc = &sdx;
+
+       sdc->nic_name = pci->id->name;
+       sdc->mtu = mtu;
+
+       pci_read_config_byte(pci, PCI_REVISION_ID, &sdc->pci_rev_id);
+
+       DBG ( "Device revision id: %hx\n", sdc->pci_rev_id );
+
+       /* Print out some hardware info */
+       DBG ( "%s: %s at ioaddr %hX, ",
+             pci->id->name, nic->node_addr, (unsigned int) BASE);
+
+       sdc->mii_preamble_required = 0;
+       if (1) {
+               int phy, phy_idx = 0;
+               sdc->phys[0] = 1;       /* Default Setting */
+               sdc->mii_preamble_required++;
+               for (phy = 1; phy < 32 && phy_idx < MII_CNT; phy++) {
+                       int mii_status = mdio_read(nic, phy, MII_BMSR);
+                       if (mii_status != 0xffff && mii_status != 0x0000) {
+                               sdc->phys[phy_idx++] = phy;
+                               sdc->mii_if.advertising =
+                                   mdio_read(nic, phy, MII_ADVERTISE);
+                               if ((mii_status & 0x0040) == 0)
+                                       sdc->mii_preamble_required++;
+                               DBG 
+                                   ( "%s: MII PHY found at address %d, status " "%hX advertising %hX\n", sdc->nic_name, phy, mii_status, sdc->mii_if.advertising );
+                       }
+               }
+               sdc->mii_preamble_required--;
+               if (phy_idx == 0)
+                       printf("%s: No MII transceiver found!\n",
+                              sdc->nic_name);
+               sdc->mii_if.phy_id = sdc->phys[0];
+       }
+
+       /* Parse override configuration */
+       sdc->an_enable = 1;
+       if (strcasecmp(media, "autosense") != 0) {
+               sdc->an_enable = 0;
+               if (strcasecmp(media, "100mbps_fd") == 0 ||
+                   strcasecmp(media, "4") == 0) {
+                       sdc->speed = 100;
+                       sdc->mii_if.full_duplex = 1;
+               } else if (strcasecmp(media, "100mbps_hd") == 0
+                          || strcasecmp(media, "3") == 0) {
+                       sdc->speed = 100;
+                       sdc->mii_if.full_duplex = 0;
+               } else if (strcasecmp(media, "10mbps_fd") == 0 ||
+                          strcasecmp(media, "2") == 0) {
+                       sdc->speed = 10;
+                       sdc->mii_if.full_duplex = 1;
+               } else if (strcasecmp(media, "10mbps_hd") == 0 ||
+                          strcasecmp(media, "1") == 0) {
+                       sdc->speed = 10;
+                       sdc->mii_if.full_duplex = 0;
+               } else {
+                       sdc->an_enable = 1;
+               }
+       }
+       if (flowctrl == 1)
+               sdc->flowctrl = 1;
+
+       /* Fibre PHY? */
+       if (inl(BASE + ASICCtrl) & 0x80) {
+               /* Default 100Mbps Full */
+               if (sdc->an_enable) {
+                       sdc->speed = 100;
+                       sdc->mii_if.full_duplex = 1;
+                       sdc->an_enable = 0;
+               }
+       }
+
+       /* The Linux driver uses flow control and resets the link here.  This means the
+          mii section from above would need to be re done I believe.  Since it serves
+          no real purpose leave it out. */
+
+       /* Force media type */
+       if (!sdc->an_enable) {
+               mii_ctl = 0;
+               mii_ctl |= (sdc->speed == 100) ? BMCR_SPEED100 : 0;
+               mii_ctl |= (sdc->mii_if.full_duplex) ? BMCR_FULLDPLX : 0;
+               mdio_write(nic, sdc->phys[0], MII_BMCR, mii_ctl);
+               printf("Override speed=%d, %s duplex\n",
+                      sdc->speed,
+                      sdc->mii_if.full_duplex ? "Full" : "Half");
+       }
+
+       /* Reset the chip to erase previous misconfiguration */
+       DBG ( "ASIC Control is %#x\n", inl(BASE + ASICCtrl) );
+       outw(0x007f, BASE + ASICCtrl + 2);
+
+       /*
+       * wait for reset to complete
+       * this is heavily inspired by the linux sundance driver
+       * according to the linux driver it can take up to 1ms for the reset
+       * to complete
+       */
+       i = 0;
+       while(inl(BASE + ASICCtrl) & (ResetBusy << 16)) {
+               if(i++ >= 10) {
+                       DBG("sundance: NIC reset did not complete.\n");
+                       break;
+               }
+               udelay(100);
+       }
+
+       DBG ( "ASIC Control is now %#x.\n", inl(BASE + ASICCtrl) );
+
+       sundance_reset(nic);
+       if (sdc->an_enable) {
+               u16 mii_advertise, mii_lpa;
+               mii_advertise =
+                   mdio_read(nic, sdc->phys[0], MII_ADVERTISE);
+               mii_lpa = mdio_read(nic, sdc->phys[0], MII_LPA);
+               mii_advertise &= mii_lpa;
+               if (mii_advertise & ADVERTISE_100FULL)
+                       sdc->speed = 100;
+               else if (mii_advertise & ADVERTISE_100HALF)
+                       sdc->speed = 100;
+               else if (mii_advertise & ADVERTISE_10FULL)
+                       sdc->speed = 10;
+               else if (mii_advertise & ADVERTISE_10HALF)
+                       sdc->speed = 10;
+       } else {
+               mii_ctl = mdio_read(nic, sdc->phys[0], MII_BMCR);
+               speed = (mii_ctl & BMCR_SPEED100) ? 100 : 10;
+               sdc->speed = speed;
+               printf("%s: Link changed: %dMbps ,", sdc->nic_name, speed);
+               printf("%s duplex.\n", (mii_ctl & BMCR_FULLDPLX) ?
+                      "full" : "half");
+       }
+       check_duplex(nic);
+       if (sdc->flowctrl && sdc->mii_if.full_duplex) {
+               outw(inw(BASE + MulticastFilter1 + 2) | 0x0200,
+                    BASE + MulticastFilter1 + 2);
+               outw(inw(BASE + MACCtrl0) | EnbFlowCtrl, BASE + MACCtrl0);
+       }
+       printf("%dMbps, %s-Duplex\n", sdc->speed,
+              sdc->mii_if.full_duplex ? "Full" : "Half");
+
+       /* point to NIC specific routines */
+       nic->nic_op     = &sundance_operations;
+
+       nic->irqno  = pci->irq;
+       nic->ioaddr = BASE;
+
+       return 1;
+}
+
+
+/* Read the EEPROM and MII Management Data I/O (MDIO) interfaces. */
+static int eeprom_read(long ioaddr, int location)
+{
+       int boguscnt = 10000;   /* Typical 1900 ticks */
+       outw(0x0200 | (location & 0xff), ioaddr + EECtrl);
+       do {
+               if (!(inw(ioaddr + EECtrl) & 0x8000)) {
+                       return inw(ioaddr + EEData);
+               }
+       }
+       while (--boguscnt > 0);
+       return 0;
+}
+
+/*  MII transceiver control section.
+       Read and write the MII registers using software-generated serial
+       MDIO protocol.  See the MII specifications or DP83840A data sheet
+       for details.
+
+       The maximum data clock rate is 2.5 Mhz.
+       The timing is decoupled from the processor clock by flushing the write
+       from the CPU write buffer with a following read, and using PCI
+       transaction time. */
+
+#define mdio_in(mdio_addr) inb(mdio_addr)
+#define mdio_out(value, mdio_addr) outb(value, mdio_addr)
+#define mdio_delay(mdio_addr) inb(mdio_addr)
+
+enum mii_reg_bits {
+       MDIO_ShiftClk = 0x0001, MDIO_Data = 0x0002, MDIO_EnbOutput =
+           0x0004,
+};
+#define MDIO_EnbIn  (0)
+#define MDIO_WRITE0 (MDIO_EnbOutput)
+#define MDIO_WRITE1 (MDIO_Data | MDIO_EnbOutput)
+
+/* Generate the preamble required for initial synchronization and
+   a few older transceivers. */
+static void mdio_sync(long mdio_addr)
+{
+       int bits = 32;
+
+       /* Establish sync by sending at least 32 logic ones. */
+       while (--bits >= 0) {
+               mdio_out(MDIO_WRITE1, mdio_addr);
+               mdio_delay(mdio_addr);
+               mdio_out(MDIO_WRITE1 | MDIO_ShiftClk, mdio_addr);
+               mdio_delay(mdio_addr);
+       }
+}
+
+static int
+mdio_read(struct nic *nic __unused, int phy_id, unsigned int location)
+{
+       long mdio_addr = BASE + MIICtrl;
+       int mii_cmd = (0xf6 << 10) | (phy_id << 5) | location;
+       int i, retval = 0;
+
+       if (sdc->mii_preamble_required)
+               mdio_sync(mdio_addr);
+
+       /* Shift the read command bits out. */
+       for (i = 15; i >= 0; i--) {
+               int dataval =
+                   (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
+
+               mdio_out(dataval, mdio_addr);
+               mdio_delay(mdio_addr);
+               mdio_out(dataval | MDIO_ShiftClk, mdio_addr);
+               mdio_delay(mdio_addr);
+       }
+       /* Read the two transition, 16 data, and wire-idle bits. */
+       for (i = 19; i > 0; i--) {
+               mdio_out(MDIO_EnbIn, mdio_addr);
+               mdio_delay(mdio_addr);
+               retval = (retval << 1) | ((mdio_in(mdio_addr) & MDIO_Data)
+                                         ? 1 : 0);
+               mdio_out(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr);
+               mdio_delay(mdio_addr);
+       }
+       return (retval >> 1) & 0xffff;
+}
+
+static void
+mdio_write(struct nic *nic __unused, int phy_id,
+          unsigned int location, int value)
+{
+       long mdio_addr = BASE + MIICtrl;
+       int mii_cmd =
+           (0x5002 << 16) | (phy_id << 23) | (location << 18) | value;
+       int i;
+
+       if (sdc->mii_preamble_required)
+               mdio_sync(mdio_addr);
+
+       /* Shift the command bits out. */
+       for (i = 31; i >= 0; i--) {
+               int dataval =
+                   (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
+               mdio_out(dataval, mdio_addr);
+               mdio_delay(mdio_addr);
+               mdio_out(dataval | MDIO_ShiftClk, mdio_addr);
+               mdio_delay(mdio_addr);
+       }
+       /* Clear out extra bits. */
+       for (i = 2; i > 0; i--) {
+               mdio_out(MDIO_EnbIn, mdio_addr);
+               mdio_delay(mdio_addr);
+               mdio_out(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr);
+               mdio_delay(mdio_addr);
+       }
+       return;
+}
+
+static void set_rx_mode(struct nic *nic __unused)
+{
+       int i;
+       u16 mc_filter[4];       /* Multicast hash filter */
+       u32 rx_mode;
+
+       memset(mc_filter, 0xff, sizeof(mc_filter));
+       rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
+
+       if (sdc->mii_if.full_duplex && sdc->flowctrl)
+               mc_filter[3] |= 0x0200;
+       for (i = 0; i < 4; i++)
+               outw(mc_filter[i], BASE + MulticastFilter0 + i * 2);
+       outb(rx_mode, BASE + RxMode);
+       return;
+}
+
+static struct pci_device_id sundance_nics[] = {
+       PCI_ROM(0x13f0, 0x0201, "sundance", "ST201 Sundance 'Alta' based Adaptor", 0),
+       PCI_ROM(0x1186, 0x1002, "dfe530txs", "D-Link DFE530TXS (Sundance ST201 Alta)", 0),
+       PCI_ROM(0x13f0, 0x0200, "ip100a", "IC+ IP100A", 0),
+};
+
+PCI_DRIVER ( sundance_driver, sundance_nics, PCI_NO_CLASS );
+
+DRIVER ( "SUNDANCE/PCI", nic_driver, pci_driver, sundance_driver,
+        sundance_probe, sundance_disable );
+
+/*
+ * Local variables:
+ *  c-basic-offset: 8
+ *  c-indent-level: 8
+ *  tab-width: 8
+ * End:
+ */