| /************************************************************************** |
| * |
| * pcnet32.c -- Etherboot device driver for the AMD PCnet32 |
| * Written 2003-2003 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: |
| * pcnet32.c: An AMD PCnet32 ethernet driver for linux: |
| * |
| * (C) 1996-1999 Thomas Bogendoerfer |
| * See Linux Driver for full information |
| * |
| * The transmit and poll functions were written with reference to: |
| * lance.c - LANCE NIC driver for Etherboot written by Ken Yap |
| * |
| * Linux Driver Version 1.27a, 10.02.2002 |
| * |
| * |
| * REVISION HISTORY: |
| * ================ |
| * v1.0 08-06-2003 timlegge Initial port of Linux driver |
| * v1.1 08-23-2003 timlegge Add multicast support |
| * v1.2 01-17-2004 timlegge Initial driver output cleanup |
| * v1.3 03-29-2004 timlegge More driver cleanup |
| * |
| * Indent Options: indent -kr -i8 |
| ***************************************************************************/ |
| |
| /* 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 "pci.h" |
| /* Include the time functions */ |
| #include "timer.h" |
| #include "mii.h" |
| /* void hex_dump(const char *data, const unsigned int len); */ |
| |
| /* Etherboot Specific definations */ |
| #define drv_version "v1.3" |
| #define drv_date "03-29-2004" |
| |
| typedef unsigned char u8; |
| typedef signed char s8; |
| typedef unsigned short u16; |
| typedef signed short s16; |
| typedef unsigned int u32; |
| typedef signed int s32; |
| |
| static u32 ioaddr; /* Globally used for the card's io address */ |
| |
| #ifdef EDEBUG |
| #define dprintf(x) printf x |
| #else |
| #define dprintf(x) |
| #endif |
| |
| /* 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)) |
| |
| /* End Etherboot Specific */ |
| |
| int cards_found /* __initdata */ ; |
| |
| #ifdef REMOVE |
| /* FIXME: Remove these they are probably pointless */ |
| |
| /* |
| * VLB I/O addresses |
| */ |
| static unsigned int pcnet32_portlist[] /*__initdata */ = |
| { 0x300, 0x320, 0x340, 0x360, 0 }; |
| |
| static int pcnet32_debug = 1; |
| static int tx_start = 1; /* Mapping -- 0:20, 1:64, 2:128, 3:~220 (depends on chip vers) */ |
| static int pcnet32vlb; /* check for VLB cards ? */ |
| |
| static struct net_device *pcnet32_dev; |
| |
| static int max_interrupt_work = 80; |
| static int rx_copybreak = 200; |
| #endif |
| #define PCNET32_PORT_AUI 0x00 |
| #define PCNET32_PORT_10BT 0x01 |
| #define PCNET32_PORT_GPSI 0x02 |
| #define PCNET32_PORT_MII 0x03 |
| |
| #define PCNET32_PORT_PORTSEL 0x03 |
| #define PCNET32_PORT_ASEL 0x04 |
| #define PCNET32_PORT_100 0x40 |
| #define PCNET32_PORT_FD 0x80 |
| |
| #define PCNET32_DMA_MASK 0xffffffff |
| |
| /* |
| * table to translate option values from tulip |
| * to internal options |
| */ |
| static unsigned char options_mapping[] = { |
| PCNET32_PORT_ASEL, /* 0 Auto-select */ |
| PCNET32_PORT_AUI, /* 1 BNC/AUI */ |
| PCNET32_PORT_AUI, /* 2 AUI/BNC */ |
| PCNET32_PORT_ASEL, /* 3 not supported */ |
| PCNET32_PORT_10BT | PCNET32_PORT_FD, /* 4 10baseT-FD */ |
| PCNET32_PORT_ASEL, /* 5 not supported */ |
| PCNET32_PORT_ASEL, /* 6 not supported */ |
| PCNET32_PORT_ASEL, /* 7 not supported */ |
| PCNET32_PORT_ASEL, /* 8 not supported */ |
| PCNET32_PORT_MII, /* 9 MII 10baseT */ |
| PCNET32_PORT_MII | PCNET32_PORT_FD, /* 10 MII 10baseT-FD */ |
| PCNET32_PORT_MII, /* 11 MII (autosel) */ |
| PCNET32_PORT_10BT, /* 12 10BaseT */ |
| PCNET32_PORT_MII | PCNET32_PORT_100, /* 13 MII 100BaseTx */ |
| PCNET32_PORT_MII | PCNET32_PORT_100 | PCNET32_PORT_FD, /* 14 MII 100BaseTx-FD */ |
| PCNET32_PORT_ASEL /* 15 not supported */ |
| }; |
| |
| #define MAX_UNITS 8 /* More are supported, limit only on options */ |
| static int options[MAX_UNITS]; |
| static int full_duplex[MAX_UNITS]; |
| |
| /* |
| * Theory of Operation |
| * |
| * This driver uses the same software structure as the normal lance |
| * driver. So look for a verbose description in lance.c. The differences |
| * to the normal lance driver is the use of the 32bit mode of PCnet32 |
| * and PCnetPCI chips. Because these chips are 32bit chips, there is no |
| * 16MB limitation and we don't need bounce buffers. |
| */ |
| |
| |
| |
| /* |
| * Set the number of Tx and Rx buffers, using Log_2(# buffers). |
| * Reasonable default values are 4 Tx buffers, and 16 Rx buffers. |
| * That translates to 2 (4 == 2^^2) and 4 (16 == 2^^4). |
| */ |
| #ifndef PCNET32_LOG_TX_BUFFERS |
| #define PCNET32_LOG_TX_BUFFERS 1 |
| #define PCNET32_LOG_RX_BUFFERS 2 |
| #endif |
| |
| #define TX_RING_SIZE (1 << (PCNET32_LOG_TX_BUFFERS)) |
| #define TX_RING_MOD_MASK (TX_RING_SIZE - 1) |
| /* FIXME: Fix this to allow multiple tx_ring descriptors */ |
| #define TX_RING_LEN_BITS 0x0000 /*PCNET32_LOG_TX_BUFFERS) << 12) */ |
| |
| #define RX_RING_SIZE (1 << (PCNET32_LOG_RX_BUFFERS)) |
| #define RX_RING_MOD_MASK (RX_RING_SIZE - 1) |
| #define RX_RING_LEN_BITS ((PCNET32_LOG_RX_BUFFERS) << 4) |
| |
| #define PKT_BUF_SZ 1544 |
| |
| /* Offsets from base I/O address. */ |
| #define PCNET32_WIO_RDP 0x10 |
| #define PCNET32_WIO_RAP 0x12 |
| #define PCNET32_WIO_RESET 0x14 |
| #define PCNET32_WIO_BDP 0x16 |
| |
| #define PCNET32_DWIO_RDP 0x10 |
| #define PCNET32_DWIO_RAP 0x14 |
| #define PCNET32_DWIO_RESET 0x18 |
| #define PCNET32_DWIO_BDP 0x1C |
| |
| #define PCNET32_TOTAL_SIZE 0x20 |
| |
| /* Buffers for the tx and Rx */ |
| |
| /* Create a static buffer of size PKT_BUF_SZ for each |
| TX Descriptor. All descriptors point to a |
| part of this buffer */ |
| static unsigned char txb[PKT_BUF_SZ * TX_RING_SIZE]; |
| // __attribute__ ((aligned(16))); |
| |
| /* Create a static buffer of size PKT_BUF_SZ for each |
| RX Descriptor All descriptors point to a |
| part of this buffer */ |
| static unsigned char rxb[RX_RING_SIZE * PKT_BUF_SZ]; |
| // __attribute__ ((aligned(16))); |
| |
| /* The PCNET32 Rx and Tx ring descriptors. */ |
| struct pcnet32_rx_head { |
| u32 base; |
| s16 buf_length; |
| s16 status; |
| u32 msg_length; |
| u32 reserved; |
| }; |
| |
| struct pcnet32_tx_head { |
| u32 base; |
| s16 length; |
| s16 status; |
| u32 misc; |
| u32 reserved; |
| }; |
| |
| /* The PCNET32 32-Bit initialization block, described in databook. */ |
| struct pcnet32_init_block { |
| u16 mode; |
| u16 tlen_rlen; |
| u8 phys_addr[6]; |
| u16 reserved; |
| u32 filter[2]; |
| /* Receive and transmit ring base, along with extra bits. */ |
| u32 rx_ring; |
| u32 tx_ring; |
| }; |
| /* PCnet32 access functions */ |
| struct pcnet32_access { |
| u16(*read_csr) (unsigned long, int); |
| void (*write_csr) (unsigned long, int, u16); |
| u16(*read_bcr) (unsigned long, int); |
| void (*write_bcr) (unsigned long, int, u16); |
| u16(*read_rap) (unsigned long); |
| void (*write_rap) (unsigned long, u16); |
| void (*reset) (unsigned long); |
| }; |
| |
| /* Define the TX Descriptor */ |
| static struct pcnet32_tx_head tx_ring[TX_RING_SIZE] |
| __attribute__ ((aligned(16))); |
| |
| |
| /* Define the RX Descriptor */ |
| static struct pcnet32_rx_head rx_ring[RX_RING_SIZE] |
| __attribute__ ((aligned(16))); |
| |
| /* 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? */ |
| }; |
| |
| /* |
| * The first three fields of pcnet32_private are read by the ethernet device |
| * so we allocate the structure should be allocated by pci_alloc_consistent(). |
| */ |
| #define MII_CNT 4 |
| struct pcnet32_private { |
| struct pcnet32_init_block init_block; |
| struct pci_dev *pci_dev; /* Pointer to the associated pci device structure */ |
| const char *name; |
| /* The saved address of a sent-in-place packet/buffer, for skfree(). */ |
| struct sk_buff *tx_skbuff[TX_RING_SIZE]; |
| struct sk_buff *rx_skbuff[RX_RING_SIZE]; |
| struct pcnet32_access a; |
| unsigned int cur_rx, cur_tx; /* The next free ring entry */ |
| char tx_full; |
| int options; |
| int shared_irq:1, /* shared irq possible */ |
| ltint:1, /* enable TxDone-intr inhibitor */ |
| dxsuflo:1, /* disable transmit stop on uflo */ |
| mii:1; /* mii port available */ |
| struct mii_if_info mii_if; |
| unsigned char phys[MII_CNT]; |
| struct net_device *next; |
| int full_duplex:1; |
| } lpx; |
| |
| static struct pcnet32_private *lp; |
| |
| static int mdio_read(struct nic *nic __unused, int phy_id, int reg_num); |
| #if 0 |
| static void mdio_write(struct nic *nic __unused, int phy_id, int reg_num, |
| int val); |
| #endif |
| enum pci_flags_bit { |
| PCI_USES_IO = 1, PCI_USES_MEM = 2, PCI_USES_MASTER = 4, |
| PCI_ADDR0 = 0x10 << 0, PCI_ADDR1 = 0x10 << 1, PCI_ADDR2 = |
| 0x10 << 2, PCI_ADDR3 = 0x10 << 3, |
| }; |
| |
| |
| static u16 pcnet32_wio_read_csr(unsigned long addr, int index) |
| { |
| outw(index, addr + PCNET32_WIO_RAP); |
| return inw(addr + PCNET32_WIO_RDP); |
| } |
| |
| static void pcnet32_wio_write_csr(unsigned long addr, int index, u16 val) |
| { |
| outw(index, addr + PCNET32_WIO_RAP); |
| outw(val, addr + PCNET32_WIO_RDP); |
| } |
| |
| static u16 pcnet32_wio_read_bcr(unsigned long addr, int index) |
| { |
| outw(index, addr + PCNET32_WIO_RAP); |
| return inw(addr + PCNET32_WIO_BDP); |
| } |
| |
| static void pcnet32_wio_write_bcr(unsigned long addr, int index, u16 val) |
| { |
| outw(index, addr + PCNET32_WIO_RAP); |
| outw(val, addr + PCNET32_WIO_BDP); |
| } |
| |
| static u16 pcnet32_wio_read_rap(unsigned long addr) |
| { |
| return inw(addr + PCNET32_WIO_RAP); |
| } |
| |
| static void pcnet32_wio_write_rap(unsigned long addr, u16 val) |
| { |
| outw(val, addr + PCNET32_WIO_RAP); |
| } |
| |
| static void pcnet32_wio_reset(unsigned long addr) |
| { |
| inw(addr + PCNET32_WIO_RESET); |
| } |
| |
| static int pcnet32_wio_check(unsigned long addr) |
| { |
| outw(88, addr + PCNET32_WIO_RAP); |
| return (inw(addr + PCNET32_WIO_RAP) == 88); |
| } |
| |
| static struct pcnet32_access pcnet32_wio = { |
| read_csr:pcnet32_wio_read_csr, |
| write_csr:pcnet32_wio_write_csr, |
| read_bcr:pcnet32_wio_read_bcr, |
| write_bcr:pcnet32_wio_write_bcr, |
| read_rap:pcnet32_wio_read_rap, |
| write_rap:pcnet32_wio_write_rap, |
| reset:pcnet32_wio_reset |
| }; |
| |
| static u16 pcnet32_dwio_read_csr(unsigned long addr, int index) |
| { |
| outl(index, addr + PCNET32_DWIO_RAP); |
| return (inl(addr + PCNET32_DWIO_RDP) & 0xffff); |
| } |
| |
| static void pcnet32_dwio_write_csr(unsigned long addr, int index, u16 val) |
| { |
| outl(index, addr + PCNET32_DWIO_RAP); |
| outl(val, addr + PCNET32_DWIO_RDP); |
| } |
| |
| static u16 pcnet32_dwio_read_bcr(unsigned long addr, int index) |
| { |
| outl(index, addr + PCNET32_DWIO_RAP); |
| return (inl(addr + PCNET32_DWIO_BDP) & 0xffff); |
| } |
| |
| static void pcnet32_dwio_write_bcr(unsigned long addr, int index, u16 val) |
| { |
| outl(index, addr + PCNET32_DWIO_RAP); |
| outl(val, addr + PCNET32_DWIO_BDP); |
| } |
| |
| static u16 pcnet32_dwio_read_rap(unsigned long addr) |
| { |
| return (inl(addr + PCNET32_DWIO_RAP) & 0xffff); |
| } |
| |
| static void pcnet32_dwio_write_rap(unsigned long addr, u16 val) |
| { |
| outl(val, addr + PCNET32_DWIO_RAP); |
| } |
| |
| static void pcnet32_dwio_reset(unsigned long addr) |
| { |
| inl(addr + PCNET32_DWIO_RESET); |
| } |
| |
| static int pcnet32_dwio_check(unsigned long addr) |
| { |
| outl(88, addr + PCNET32_DWIO_RAP); |
| return ((inl(addr + PCNET32_DWIO_RAP) & 0xffff) == 88); |
| } |
| |
| static struct pcnet32_access pcnet32_dwio = { |
| read_csr:pcnet32_dwio_read_csr, |
| write_csr:pcnet32_dwio_write_csr, |
| read_bcr:pcnet32_dwio_read_bcr, |
| write_bcr:pcnet32_dwio_write_bcr, |
| read_rap:pcnet32_dwio_read_rap, |
| write_rap:pcnet32_dwio_write_rap, |
| reset:pcnet32_dwio_reset |
| }; |
| |
| |
| /* Initialize the PCNET32 Rx and Tx rings. */ |
| static int pcnet32_init_ring(struct nic *nic) |
| { |
| int i; |
| |
| lp->tx_full = 0; |
| lp->cur_rx = lp->cur_tx = 0; |
| |
| for (i = 0; i < RX_RING_SIZE; i++) { |
| rx_ring[i].base = (u32) virt_to_le32desc(&rxb[i]); |
| rx_ring[i].buf_length = le16_to_cpu(-PKT_BUF_SZ); |
| rx_ring[i].status = le16_to_cpu(0x8000); |
| } |
| |
| /* The Tx buffer address is filled in as needed, but we do need to clear |
| the upper ownership bit. */ |
| for (i = 0; i < TX_RING_SIZE; i++) { |
| tx_ring[i].base = 0; |
| tx_ring[i].status = 0; |
| } |
| |
| |
| lp->init_block.tlen_rlen = |
| le16_to_cpu(TX_RING_LEN_BITS | RX_RING_LEN_BITS); |
| for (i = 0; i < 6; i++) |
| lp->init_block.phys_addr[i] = nic->node_addr[i]; |
| lp->init_block.rx_ring = (u32) virt_to_le32desc(&rx_ring[0]); |
| lp->init_block.tx_ring = (u32) virt_to_le32desc(&tx_ring[0]); |
| return 0; |
| } |
| |
| /************************************************************************** |
| RESET - Reset adapter |
| ***************************************************************************/ |
| static void pcnet32_reset(struct nic *nic) |
| { |
| /* put the card in its initial state */ |
| u16 val; |
| int i; |
| |
| /* Reset the PCNET32 */ |
| lp->a.reset(ioaddr); |
| |
| /* switch pcnet32 to 32bit mode */ |
| lp->a.write_bcr(ioaddr, 20, 2); |
| |
| /* set/reset autoselect bit */ |
| val = lp->a.read_bcr(ioaddr, 2) & ~2; |
| if (lp->options & PCNET32_PORT_ASEL) |
| val |= 2; |
| lp->a.write_bcr(ioaddr, 2, val); |
| /* handle full duplex setting */ |
| if (lp->full_duplex) { |
| val = lp->a.read_bcr(ioaddr, 9) & ~3; |
| if (lp->options & PCNET32_PORT_FD) { |
| val |= 1; |
| if (lp->options == |
| (PCNET32_PORT_FD | PCNET32_PORT_AUI)) |
| val |= 2; |
| } else if (lp->options & PCNET32_PORT_ASEL) { |
| /* workaround of xSeries250, turn on for 79C975 only */ |
| i = ((lp->a. |
| read_csr(ioaddr, |
| 88) | (lp->a.read_csr(ioaddr, |
| 89) << 16)) >> |
| 12) & 0xffff; |
| if (i == 0x2627) |
| val |= 3; |
| } |
| lp->a.write_bcr(ioaddr, 9, val); |
| } |
| |
| /* set/reset GPSI bit in test register */ |
| val = lp->a.read_csr(ioaddr, 124) & ~0x10; |
| if ((lp->options & PCNET32_PORT_PORTSEL) == PCNET32_PORT_GPSI) |
| val |= 0x10; |
| lp->a.write_csr(ioaddr, 124, val); |
| |
| if (lp->mii && !(lp->options & PCNET32_PORT_ASEL)) { |
| val = lp->a.read_bcr(ioaddr, 32) & ~0x38; /* disable Auto Negotiation, set 10Mpbs, HD */ |
| if (lp->options & PCNET32_PORT_FD) |
| val |= 0x10; |
| if (lp->options & PCNET32_PORT_100) |
| val |= 0x08; |
| lp->a.write_bcr(ioaddr, 32, val); |
| } else { |
| if (lp->options & PCNET32_PORT_ASEL) { /* enable auto negotiate, setup, disable fd */ |
| val = lp->a.read_bcr(ioaddr, 32) & ~0x98; |
| val |= 0x20; |
| lp->a.write_bcr(ioaddr, 32, val); |
| } |
| } |
| |
| #ifdef DO_DXSUFLO |
| if (lp->dxsuflo) { /* Disable transmit stop on underflow */ |
| val = lp->a.read_csr(ioaddr, 3); |
| val |= 0x40; |
| lp->a.write_csr(ioaddr, 3, val); |
| } |
| #endif |
| |
| if (lp->ltint) { /* Enable TxDone-intr inhibitor */ |
| val = lp->a.read_csr(ioaddr, 5); |
| val |= (1 << 14); |
| lp->a.write_csr(ioaddr, 5, val); |
| } |
| lp->init_block.mode = |
| le16_to_cpu((lp->options & PCNET32_PORT_PORTSEL) << 7); |
| lp->init_block.filter[0] = 0xffffffff; |
| lp->init_block.filter[1] = 0xffffffff; |
| |
| pcnet32_init_ring(nic); |
| |
| |
| /* Re-initialize the PCNET32, and start it when done. */ |
| lp->a.write_csr(ioaddr, 1, |
| (virt_to_bus(&lp->init_block)) & 0xffff); |
| lp->a.write_csr(ioaddr, 2, (virt_to_bus(&lp->init_block)) >> 16); |
| lp->a.write_csr(ioaddr, 4, 0x0915); |
| lp->a.write_csr(ioaddr, 0, 0x0001); |
| |
| |
| i = 0; |
| while (i++ < 100) |
| if (lp->a.read_csr(ioaddr, 0) & 0x0100) |
| break; |
| /* |
| * We used to clear the InitDone bit, 0x0100, here but Mark Stockton |
| * reports that doing so triggers a bug in the '974. |
| */ |
| lp->a.write_csr(ioaddr, 0, 0x0042); |
| |
| dprintf(("pcnet32 open, csr0 %hX.\n", lp->a.read_csr(ioaddr, 0))); |
| |
| } |
| |
| /************************************************************************** |
| POLL - Wait for a frame |
| ***************************************************************************/ |
| static int pcnet32_poll(struct nic *nic __unused, 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 status; |
| int entry; |
| |
| entry = lp->cur_rx & RX_RING_MOD_MASK; |
| status = ((short) le16_to_cpu(rx_ring[entry].status) >> 8); |
| |
| if (status < 0) |
| return 0; |
| |
| if ( ! retrieve ) return 1; |
| |
| if (status == 0x03) { |
| nic->packetlen = |
| (le32_to_cpu(rx_ring[entry].msg_length) & 0xfff) - 4; |
| memcpy(nic->packet, &rxb[entry], nic->packetlen); |
| |
| /* Andrew Boyd of QNX reports that some revs of the 79C765 |
| * clear the buffer length */ |
| rx_ring[entry].buf_length = le16_to_cpu(-PKT_BUF_SZ); |
| rx_ring[entry].status |= le16_to_cpu(0x8000); /* prime for next receive */ |
| /* Switch to the next Rx ring buffer */ |
| lp->cur_rx++; |
| |
| } else { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /************************************************************************** |
| TRANSMIT - Transmit a frame |
| ***************************************************************************/ |
| static void pcnet32_transmit(struct nic *nic __unused, const char *d, /* Destination */ |
| unsigned int t, /* Type */ |
| unsigned int s, /* size */ |
| const char *p) |
| { /* Packet */ |
| /* send the packet to destination */ |
| unsigned long time; |
| u8 *ptxb; |
| u16 nstype; |
| u16 status; |
| int entry = 0; /*lp->cur_tx & TX_RING_MOD_MASK; */ |
| |
| status = 0x8300; |
| /* point to the current txb incase multiple tx_rings are used */ |
| ptxb = txb + (lp->cur_tx * PKT_BUF_SZ); |
| |
| /* copy the packet to ring buffer */ |
| memcpy(ptxb, d, ETH_ALEN); /* dst */ |
| memcpy(ptxb + ETH_ALEN, nic->node_addr, ETH_ALEN); /* src */ |
| nstype = htons((u16) t); /* type */ |
| memcpy(ptxb + 2 * ETH_ALEN, (u8 *) & nstype, 2); /* type */ |
| memcpy(ptxb + ETH_HLEN, p, s); |
| |
| s += ETH_HLEN; |
| while (s < ETH_ZLEN) /* pad to min length */ |
| ptxb[s++] = '\0'; |
| |
| tx_ring[entry].length = le16_to_cpu(-s); |
| tx_ring[entry].misc = 0x00000000; |
| tx_ring[entry].base = (u32) virt_to_le32desc(ptxb); |
| |
| /* we set the top byte as the very last thing */ |
| tx_ring[entry].status = le16_to_cpu(status); |
| |
| |
| /* Trigger an immediate send poll */ |
| lp->a.write_csr(ioaddr, 0, 0x0048); |
| |
| /* wait for transmit complete */ |
| lp->cur_tx = 0; /* (lp->cur_tx + 1); */ |
| time = currticks() + TICKS_PER_SEC; /* wait one second */ |
| while (currticks() < time && |
| ((short) le16_to_cpu(tx_ring[entry].status) < 0)); |
| |
| if ((short) le16_to_cpu(tx_ring[entry].status) < 0) |
| printf("PCNET32 timed out on transmit\n"); |
| |
| /* Stop pointing at the current txb |
| * otherwise the card continues to send the packet */ |
| tx_ring[entry].base = 0; |
| |
| } |
| |
| /************************************************************************** |
| DISABLE - Turn off ethernet interface |
| ***************************************************************************/ |
| static void pcnet32_disable(struct dev *dev __unused) |
| { |
| /* Stop the PCNET32 here -- it ocassionally polls memory if we don't */ |
| lp->a.write_csr(ioaddr, 0, 0x0004); |
| |
| /* |
| * Switch back to 16-bit mode to avoid problesm with dumb |
| * DOS packet driver after a warm reboot |
| */ |
| lp->a.write_bcr(ioaddr, 20, 4); |
| } |
| |
| /************************************************************************** |
| IRQ - Enable, Disable, or Force interrupts |
| ***************************************************************************/ |
| static void pcnet32_irq(struct nic *nic __unused, irq_action_t action __unused) |
| { |
| switch ( action ) { |
| case DISABLE : |
| break; |
| case ENABLE : |
| break; |
| case FORCE : |
| break; |
| } |
| } |
| |
| /************************************************************************** |
| PROBE - Look for an adapter, this routine's visible to the outside |
| You should omit the last argument struct pci_device * for a non-PCI NIC |
| ***************************************************************************/ |
| static int pcnet32_probe(struct dev *dev, struct pci_device *pci) |
| { |
| struct nic *nic = (struct nic *) dev; |
| int i, media; |
| int fdx, mii, fset, dxsuflo, ltint; |
| int chip_version; |
| char *chipname; |
| struct pcnet32_access *a = NULL; |
| u8 promaddr[6]; |
| |
| int shared = 1; |
| if (pci->ioaddr == 0) |
| return 0; |
| |
| /* BASE is used throughout to address the card */ |
| ioaddr = pci->ioaddr; |
| printf("pcnet32.c: Found %s, Vendor=0x%hX Device=0x%hX\n", |
| pci->name, pci->vendor, pci->dev_id); |
| |
| nic->irqno = 0; |
| nic->ioaddr = pci->ioaddr & ~3; |
| |
| /* reset the chip */ |
| pcnet32_wio_reset(ioaddr); |
| |
| /* NOTE: 16-bit check is first, otherwise some older PCnet chips fail */ |
| if (pcnet32_wio_read_csr(ioaddr, 0) == 4 |
| && pcnet32_wio_check(ioaddr)) { |
| a = &pcnet32_wio; |
| } else { |
| pcnet32_dwio_reset(ioaddr); |
| if (pcnet32_dwio_read_csr(ioaddr, 0) == 4 |
| && pcnet32_dwio_check(ioaddr)) { |
| a = &pcnet32_dwio; |
| } else |
| return 0; |
| } |
| |
| chip_version = |
| a->read_csr(ioaddr, 88) | (a->read_csr(ioaddr, 89) << 16); |
| |
| dprintf(("PCnet chip version is %0xhX\n", chip_version)); |
| if ((chip_version & 0xfff) != 0x003) |
| return 0; |
| |
| /* initialize variables */ |
| fdx = mii = fset = dxsuflo = ltint = 0; |
| chip_version = (chip_version >> 12) & 0xffff; |
| |
| switch (chip_version) { |
| case 0x2420: |
| chipname = "PCnet/PCI 79C970"; /* PCI */ |
| break; |
| case 0x2430: |
| if (shared) |
| chipname = "PCnet/PCI 79C970"; /* 970 gives the wrong chip id back */ |
| else |
| chipname = "PCnet/32 79C965"; /* 486/VL bus */ |
| break; |
| case 0x2621: |
| chipname = "PCnet/PCI II 79C970A"; /* PCI */ |
| fdx = 1; |
| break; |
| case 0x2623: |
| chipname = "PCnet/FAST 79C971"; /* PCI */ |
| fdx = 1; |
| mii = 1; |
| fset = 1; |
| ltint = 1; |
| break; |
| case 0x2624: |
| chipname = "PCnet/FAST+ 79C972"; /* PCI */ |
| fdx = 1; |
| mii = 1; |
| fset = 1; |
| break; |
| case 0x2625: |
| chipname = "PCnet/FAST III 79C973"; /* PCI */ |
| fdx = 1; |
| mii = 1; |
| break; |
| case 0x2626: |
| chipname = "PCnet/Home 79C978"; /* PCI */ |
| fdx = 1; |
| /* |
| * This is based on specs published at www.amd.com. This section |
| * assumes that a card with a 79C978 wants to go into 1Mb HomePNA |
| * mode. The 79C978 can also go into standard ethernet, and there |
| * probably should be some sort of module option to select the |
| * mode by which the card should operate |
| */ |
| /* switch to home wiring mode */ |
| media = a->read_bcr(ioaddr, 49); |
| |
| printf("media reset to %#x.\n", media); |
| a->write_bcr(ioaddr, 49, media); |
| break; |
| case 0x2627: |
| chipname = "PCnet/FAST III 79C975"; /* PCI */ |
| fdx = 1; |
| mii = 1; |
| break; |
| default: |
| printf("PCnet version %#x, no PCnet32 chip.\n", |
| chip_version); |
| return 0; |
| } |
| |
| /* |
| * On selected chips turn on the BCR18:NOUFLO bit. This stops transmit |
| * starting until the packet is loaded. Strike one for reliability, lose |
| * one for latency - although on PCI this isnt a big loss. Older chips |
| * have FIFO's smaller than a packet, so you can't do this. |
| */ |
| |
| if (fset) { |
| a->write_bcr(ioaddr, 18, |
| (a->read_bcr(ioaddr, 18) | 0x0800)); |
| a->write_csr(ioaddr, 80, |
| (a->read_csr(ioaddr, 80) & 0x0C00) | 0x0c00); |
| dxsuflo = 1; |
| ltint = 1; |
| } |
| |
| dprintf(("%s at %hX,", chipname, ioaddr)); |
| |
| /* read PROM address */ |
| for (i = 0; i < 6; i++) |
| promaddr[i] = inb(ioaddr + i); |
| |
| /* Update the nic structure with the MAC Address */ |
| for (i = 0; i < ETH_ALEN; i++) { |
| nic->node_addr[i] = promaddr[i]; |
| } |
| /* Print out some hardware info */ |
| printf("%s: %! at ioaddr %hX, ", pci->name, nic->node_addr, |
| ioaddr); |
| |
| /* Set to pci bus master */ |
| adjust_pci_device(pci); |
| |
| /* point to private storage */ |
| lp = &lpx; |
| |
| #if EBDEBUG |
| if (((chip_version + 1) & 0xfffe) == 0x2624) { /* Version 0x2623 or 0x2624 */ |
| i = a->read_csr(ioaddr, 80) & 0x0C00; /* Check tx_start_pt */ |
| dprintf((" tx_start_pt(0x%hX):", i)); |
| switch (i >> 10) { |
| case 0: |
| dprintf((" 20 bytes,")); |
| break; |
| case 1: |
| dprintf((" 64 bytes,")); |
| break; |
| case 2: |
| dprintf((" 128 bytes,")); |
| break; |
| case 3: |
| dprintf(("~220 bytes,")); |
| break; |
| } |
| i = a->read_bcr(ioaddr, 18); /* Check Burst/Bus control */ |
| dprintf((" BCR18(%hX):", i & 0xffff)); |
| if (i & (1 << 5)) |
| dprintf(("BurstWrEn ")); |
| if (i & (1 << 6)) |
| dprintf(("BurstRdEn ")); |
| if (i & (1 << 7)) |
| dprintf(("DWordIO ")); |
| if (i & (1 << 11)) |
| dprintf(("NoUFlow ")); |
| i = a->read_bcr(ioaddr, 25); |
| dprintf((" SRAMSIZE=0x%hX,", i << 8)); |
| i = a->read_bcr(ioaddr, 26); |
| dprintf((" SRAM_BND=0x%hX,", i << 8)); |
| i = a->read_bcr(ioaddr, 27); |
| if (i & (1 << 14)) |
| dprintf(("LowLatRx")); |
| } |
| #endif |
| lp->name = chipname; |
| lp->shared_irq = shared; |
| lp->full_duplex = fdx; |
| lp->dxsuflo = dxsuflo; |
| lp->ltint = ltint; |
| lp->mii = mii; |
| /* FIXME: Fix Options for only one card */ |
| if ((cards_found >= MAX_UNITS) |
| || ((unsigned int) options[cards_found] > sizeof(options_mapping))) |
| lp->options = PCNET32_PORT_ASEL; |
| else |
| lp->options = options_mapping[options[cards_found]]; |
| |
| if (fdx && !(lp->options & PCNET32_PORT_ASEL) && |
| ((cards_found >= MAX_UNITS) || full_duplex[cards_found])) |
| lp->options |= PCNET32_PORT_FD; |
| |
| if (!a) { |
| printf("No access methods\n"); |
| return 0; |
| } |
| lp->a = *a; |
| |
| /* detect special T1/E1 WAN card by checking for MAC address */ |
| if (nic->node_addr[0] == 0x00 && nic->node_addr[1] == 0xe0 |
| && nic->node_addr[2] == 0x75) |
| lp->options = PCNET32_PORT_FD | PCNET32_PORT_GPSI; |
| |
| lp->init_block.mode = le16_to_cpu(0x0003); /* Disable Rx and Tx. */ |
| lp->init_block.tlen_rlen = |
| le16_to_cpu(TX_RING_LEN_BITS | RX_RING_LEN_BITS); |
| for (i = 0; i < 6; i++) |
| lp->init_block.phys_addr[i] = nic->node_addr[i]; |
| lp->init_block.filter[0] = 0xffffffff; |
| lp->init_block.filter[1] = 0xffffffff; |
| lp->init_block.rx_ring = virt_to_bus(&rx_ring); |
| lp->init_block.tx_ring = virt_to_bus(&tx_ring); |
| |
| /* switch pcnet32 to 32bit mode */ |
| a->write_bcr(ioaddr, 20, 2); |
| |
| |
| a->write_csr(ioaddr, 1, (virt_to_bus(&lp->init_block)) & 0xffff); |
| a->write_csr(ioaddr, 2, (virt_to_bus(&lp->init_block)) >> 16); |
| |
| /* |
| * To auto-IRQ we enable the initialization-done and DMA error |
| * interrupts. For ISA boards we get a DMA error, but VLB and PCI |
| * boards will work. |
| */ |
| /* Trigger an initialization just for the interrupt. */ |
| |
| a->write_csr(ioaddr, 0, 0x41); |
| mdelay(1); |
| |
| cards_found++; |
| |
| /* point to NIC specific routines */ |
| pcnet32_reset(nic); |
| if (1) { |
| int tmp; |
| int phy, phy_idx = 0; |
| u16 mii_lpa; |
| lp->phys[0] = 1; /* Default Setting */ |
| 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) { |
| lp->phys[phy_idx++] = phy; |
| lp->mii_if.advertising = |
| mdio_read(nic, phy, MII_ADVERTISE); |
| if ((mii_status & 0x0040) == 0) { |
| tmp = phy; |
| dprintf (("MII PHY found at address %d, status " |
| "%hX advertising %hX\n", phy, mii_status, |
| lp->mii_if.advertising)); |
| } |
| } |
| } |
| if (phy_idx == 0) |
| printf("No MII transceiver found!\n"); |
| lp->mii_if.phy_id = lp->phys[0]; |
| |
| lp->mii_if.advertising = |
| mdio_read(nic, lp->phys[0], MII_ADVERTISE); |
| |
| mii_lpa = mdio_read(nic, lp->phys[0], MII_LPA); |
| lp->mii_if.advertising &= mii_lpa; |
| if (lp->mii_if.advertising & ADVERTISE_100FULL) |
| printf("100Mbps Full-Duplex\n"); |
| else if (lp->mii_if.advertising & ADVERTISE_100HALF) |
| printf("100Mbps Half-Duplex\n"); |
| else if (lp->mii_if.advertising & ADVERTISE_10FULL) |
| printf("10Mbps Full-Duplex\n"); |
| else if (lp->mii_if.advertising & ADVERTISE_10HALF) |
| printf("10Mbps Half-Duplex\n"); |
| else |
| printf("\n"); |
| } |
| |
| nic->poll = pcnet32_poll; |
| nic->transmit = pcnet32_transmit; |
| dev->disable = pcnet32_disable; |
| nic->irq = pcnet32_irq; |
| |
| return 1; |
| } |
| static int mdio_read(struct nic *nic __unused, int phy_id, int reg_num) |
| { |
| u16 val_out; |
| int phyaddr; |
| |
| if (!lp->mii) |
| return 0; |
| |
| phyaddr = lp->a.read_bcr(ioaddr, 33); |
| |
| lp->a.write_bcr(ioaddr, 33, |
| ((phy_id & 0x1f) << 5) | (reg_num & 0x1f)); |
| val_out = lp->a.read_bcr(ioaddr, 34); |
| lp->a.write_bcr(ioaddr, 33, phyaddr); |
| |
| return val_out; |
| } |
| |
| #if 0 |
| static void mdio_write(struct nic *nic __unused, int phy_id, int reg_num, |
| int val) |
| { |
| int phyaddr; |
| |
| if (!lp->mii) |
| return; |
| |
| phyaddr = lp->a.read_bcr(ioaddr, 33); |
| |
| lp->a.write_bcr(ioaddr, 33, |
| ((phy_id & 0x1f) << 5) | (reg_num & 0x1f)); |
| lp->a.write_bcr(ioaddr, 34, val); |
| lp->a.write_bcr(ioaddr, 33, phyaddr); |
| } |
| #endif |
| |
| static struct pci_id pcnet32_nics[] = { |
| PCI_ROM(0x1022, 0x2000, "lancepci", "AMD Lance/PCI"), |
| PCI_ROM(0x1022, 0x2625, "pcnetfastiii", "AMD Lance/PCI PCNet/32"), |
| PCI_ROM(0x1022, 0x2001, "amdhomepna", "AMD Lance/HomePNA"), |
| }; |
| |
| struct pci_driver pcnet32_driver = { |
| .type = NIC_DRIVER, |
| .name = "PCNET32/PCI", |
| .probe = pcnet32_probe, |
| .ids = pcnet32_nics, |
| .id_count = sizeof(pcnet32_nics) / sizeof(pcnet32_nics[0]), |
| .class = 0, |
| }; |