| /************************************************************************** |
| * |
| * 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., 675 Mass Ave, Cambridge, MA 02139, 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 |
| * |
| ****************************************************************************/ |
| |
| /* 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 <gpxe/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)) |
| |
| /* May need to be moved to mii.h */ |
| struct mii_if_info { |
| int phy_id; |
| int advertising; |
| unsigned int full_duplex:1; /* is full duplex? */ |
| }; |
| |
| /* 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 indicies */ |
| 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 retreive) |
| { |
| /* 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(!retreive) |
| 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->driver_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->driver_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->driver_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: |
| */ |