Goto sanos source index
//
// tulip.c
//
// Digital "Tulip" Ethernet network driver for DEC 21*4*-based chips/ethercards
//
// Written/copyright 1994-2002 by Donald Becker
// Ported to sanos 2002 by Michael Ringgaard.
//
// This software may be used and distributed according to the terms of
// the GNU General Public License (GPL), incorporated herein by reference.
// Drivers based on or derived from this code fall under the GPL and must
// retain the authorship, copyright and license notice. This file is not
// a complete program and may only be used when the entire operating
// system is licensed under the GPL.
//
// This driver is for the Digital "Tulip" Ethernet adapter interface.
// It should work with most DEC 21*4*-based chips/ethercards, as well as
// with work-alike chips from Lite-On (PNIC) and Macronix (MXIC) and ASIX.
//
// The author may be reached as becker@scyld.com, or C/O
// Scyld Computing Corporation
// 410 Severn Ave., Suite 210
// Annapolis MD 21403
//
#include <os/krnl.h>
// Maximum events (Rx packets, etc.) to handle at each interrupt
static int max_interrupt_work = 25;
// The possible media types that can be set in options[] are:
#define MEDIA_MASK 31
static char *medianame[32] = {
"10baseT", "10base2", "AUI", "100baseTx",
"10baseT-FDX", "100baseTx-FDX", "100baseT4", "100baseFx",
"100baseFx-FDX", "MII 10baseT", "MII 10baseT-FDX", "MII",
"10baseT(forced)", "MII 100baseTx", "MII 100baseTx-FDX", "MII 100baseT4",
"MII 100baseFx-HDX", "MII 100baseFx-FDX", "Home-PNA 1Mbps", "Invalid-19",
"","","","", "","","","", "","","","Transceiver reset",
};
// Set if the PCI BIOS detects the chips on a multiport board backwards.
#ifdef REVERSE_PROBE_ORDER
static int reverse_probe = 1;
#else
static int reverse_probe = 0;
#endif
// Set the copy breakpoint for the copy-only-tiny-buffer Rx structure.
static int rx_copybreak = 100;
//
// Set the bus performance register.
// Typical: Set 16 longword cache alignment, no burst limit.
// Cache alignment bits 15:14 Burst length 13:8
// 0000 No alignment 0x00000000 unlimited 0800 8 longwords
// 4000 8 longwords 0100 1 longword 1000 16 longwords
// 8000 16 longwords 0200 2 longwords 2000 32 longwords
// C000 32 longwords 0400 4 longwords
// Warning: many older 486 systems are broken and require setting 0x00A04800
// 8 longword cache alignment, 8 longword burst.
//
static int csr0 = 0x01A00000 | 0x8000;
//
// Maximum number of multicast addresses to filter (vs. rx-all-multicast).
// Typical is a 64 element hash table based on the Ethernet CRC.
// This value does not apply to the 512 bit table chips.
//
static int multicast_filter_limit = 32;
//
// Keep the descriptor ring sizes a power of two for efficiency.
// The Tx queue length limits transmit packets to a portion of the available
// ring entries. It should be at least one element less to allow multicast
// filter setup frames to be queued. It must be at least four for hysteresis.
// Making the Tx queue too long decreases the effectiveness of channel
// bonding and packet priority.
// Large receive rings merely consume memory.
//
#define TX_RING_SIZE 16
#define TX_QUEUE_LEN 10
#define RX_RING_SIZE 32
#define TX_TIMEOUT (4*HZ) // Time in ticks before concluding the transmitter is hung
#define PKT_BUF_SZ 1536 // Size of each temporary Rx buffer
//
// This is a mysterious value that can be written to CSR11 in the 21040 (only)
// to support a pre-NWay full-duplex signaling mechanism using short frames.
// No one knows what it should be, but if left at its default value some
// 10base2(!) packets trigger a full-duplex-request interrupt.
//
#define FULL_DUPLEX_MAGIC 0x6969
//
// This driver was originally written to use I/O space access, but now
// uses memory space by default. Override this this with -DUSE_IO_OPS.
//
#ifdef USE_IO_OPS
#define readb(port) inp(port)
#define readw(port) inpw(port)
#define readl(port) inpd(port)
#define writeb(data, port) outp((port), (data))
#define writew(data, port) outpw((port), (data))
#define writel(data, port) outpd((port), (data))
#else
#define readb(addr) (*(volatile unsigned char *) (addr))
#define readw(addr) (*(volatile unsigned short *) (addr))
#define readl(addr) (*(volatile unsigned int *) (addr))
#define writeb(data ,addr) (*(volatile unsigned char *) (addr) = (data))
#define writew(data ,addr) (*(volatile unsigned short *) (addr) = (data))
#define writel(data ,addr) (*(volatile unsigned int *) (addr) = (data))
#endif
//
// Theory of Operation
//
// I. Board Compatibility
//
// This device driver is designed for the DECchip "Tulip", Digital's
// single-chip ethernet controllers for PCI. Supported members of the family
// are the 21040, 21041, 21140, 21140A, 21142, and 21143. Similar work-alike
// chips from Lite-On, Macronics, ASIX, Compex and other listed below are also
// supported.
//
// These chips are used on at least 140 unique PCI board designs. The great
// number of chips and board designs supported is the reason for the
// driver size and complexity. Almost of the increasing complexity is in the
// board configuration and media selection code. There is very little
// increasing in the operational critical path length.
//
// II. Board-specific settings
//
// PCI bus devices are configured by the system at boot time, so no jumpers
// need to be set on the board. The system BIOS preferably should assign the
// PCI INTA signal to an otherwise unused system IRQ line.
//
// Some boards have EEPROMs tables with default media entry. The factory default
// is usually "autoselect". This should only be overridden when using
// transceiver connections without link beat e.g. 10base2 or AUI, or (rarely!)
// for forcing full-duplex when used with old link partners that do not do
// autonegotiation.
//
// III. Driver operation
//
// IIIa. Ring buffers
//
// The Tulip can use either ring buffers or lists of Tx and Rx descriptors.
// This driver uses statically allocated rings of Rx and Tx descriptors, set at
// compile time by RX/TX_RING_SIZE. This version of the driver allocates pbufs
// for the Rx ring buffers at open() time and passes the p->payload field to the
// Tulip as receive data buffers. When an incoming frame is less than
// RX_COPYBREAK bytes long, a fresh pbuf is allocated and the frame is
// copied to the new pbuf. When the incoming frame is larger, the pbuf is
// passed directly up the protocol stack and replaced by a newly allocated
// pbuf.
//
// The RX_COPYBREAK value is chosen to trade-off the memory wasted by
// using a full-sized pbuf for small frames vs. the copying costs of larger
// frames. For small frames the copying cost is negligible (esp. considering
// that we are pre-loading the cache with immediately useful header
// information). For large frames the copying cost is non-trivial, and the
// larger copy might flush the cache of useful data. A subtle aspect of this
// choice is that the Tulip only receives into longword aligned buffers, thus
// the IP header at offset 14 is not longword aligned for further processing.
//
// IIIC. Synchronization
// The driver runs as two independent, single-threaded flows of control. One
// is the send-packet routine, which enforces single-threaded use by the
// semaphore. The other thread is the interrupt handler, which is single
// threaded by the hardware and other software.
//
// The interrupt handler has exclusive control over the Rx ring and records stats
// from the Tx ring. (The Tx-done interrupt can not be selectively turned off, so
// we cannot avoid the interrupt overhead by having the Tx routine reap the Tx
// stats.) After reaping the stats, it marks the queue entry as empty by setting
// the 'base' to zero.
//
// IV. Notes
//
// Thanks to Duke Kamstra of SMC for long ago providing an EtherPower board.
// Greg LaPolla at Linksys provided PNIC and other Linksys boards.
// Znyx provided a four-port card for testing.
//
// IVb. References
//
// http://scyld.com/expert/NWay.html
// http://www.digital.com (search for current 21*4* datasheets and "21X4 SROM")
// http://www.national.com/pf/DP/DP83840A.html
// http://www.asix.com.tw/pmac.htm
// http://www.admtek.com.tw/
//
// IVc. Errata
//
// The old DEC databooks were light on details.
// The 21040 databook claims that CSR13, CSR14, and CSR15 should each be the last
// register of the set CSR12-15 written. Hmmm, now how is that possible?
//
// The DEC SROM format is very badly designed not precisely defined, leading to
// part of the media selection junkheap below. Some boards do not have EEPROM
// media tables and need to be patched up. Worse, other boards use the DEC
// design kit media table when it is not correct for their design.
//
// We cannot use MII interrupts because there is no defined GPIO pin to attach
// them. The MII transceiver status is polled using an kernel timer.
//
static int tulip_pwr_event(void *dev_instance, int event);
#ifdef USE_IO_OPS
#define TULIP_IOTYPE PCI_USES_MASTER | PCI_USES_IO | PCI_ADDR0
#define TULIP_SIZE 0x80
#define TULIP_SIZE1 0x100
#else
#define TULIP_IOTYPE PCI_USES_MASTER | PCI_USES_MEM | PCI_ADDR1
#define TULIP_SIZE 0x400 // New PCI v2.1 recommends 4K min mem size
#define TULIP_SIZE1 0x400 // New PCI v2.1 recommends 4K min mem size
#endif
// This much match tulip_tbl[]! Note 21142 == 21143.
enum tulip_chips {
DC21040=0,
DC21041=1,
DC21140=2,
DC21142=3,
DC21143=3,
LC82C168,
MX98713,
MX98715,
MX98725,
AX88141,
AX88140,
PNIC2,
COMET,
COMPEX9881,
I21145,
XIRCOM,
CONEXANT,
// These flags may be added to the chip type
HAS_VLAN = 0x100,
};
static struct board board_tbl[] = {
{"Digital", "Digital DC21040 Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0002), 0xffffffff, 0, 0, 0, 0, DC21040},
{"Digital", "Digital DC21041 Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0014), 0xffffffff, 0, 0, 0, 0, DC21041},
{"Digital", "Digital DS21140A Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0009), 0xffffffff, 0, 0, 0x20, 0xf0, DC21140},
{"Digital", "Digital DS21140 Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0009), 0xffffffff, 0, 0, 0, 0, DC21140},
{"Digital", "Digital DS21143-xD Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0019), 0xffffffff, 0, 0, 0x40, 0xf0, DC21142 | HAS_VLAN},
{"Digital", "Digital DS21143-xC Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0019), 0xffffffff, 0, 0, 0x30, 0xf0, DC21142},
{"Digital", "Digital DS21142 Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x1011, 0x0019), 0xffffffff, 0, 0, 0, 0, DC21142},
{"Kingston", "Kingston KNE110tx (PNIC)", BUSTYPE_PCI, PCI_UNITCODE(0x11AD, 0x0002), 0xffffffff, PCI_UNITCODE(0x2646, 0xf002), 0xffffffff, 0, 0, LC82C168},
{"Linksys", "Linksys LNE100TX (82c168 PNIC)", BUSTYPE_PCI, PCI_UNITCODE(0x11AD, 0x0002), 0xffffffff, PCI_UNITCODE(0x11ad, 0xffff), 0xffffffff, 17, 0xff, LC82C168},
{"Linksys", "Linksys LNE100TX (82c169 PNIC)", BUSTYPE_PCI, PCI_UNITCODE(0x11AD, 0x0002), 0xffffffff, PCI_UNITCODE(0x11ad, 0xf003), 0xffffffff, 32, 0xff, LC82C168},
{"Lite-On", "Lite-On 82c168 PNIC", BUSTYPE_PCI, PCI_UNITCODE(0x11AD, 0x0002), 0xffffffff, 0, 0, 0, 0, LC82C168},
{"Macronix", "Macronix 98713 PMAC", BUSTYPE_PCI, PCI_UNITCODE(0x10d9, 0x0512), 0xffffffff, 0, 0, 0, 0, MX98713},
{"Macronix", "Macronix 98715 PMAC", BUSTYPE_PCI, PCI_UNITCODE(0x10d9, 0x0531), 0xffffffff, 0, 0, 0, 0, MX98715},
{"Macronix", "Macronix 98725 PMAC", BUSTYPE_PCI, PCI_UNITCODE(0x10d9, 0x0531), 0xffffffff, 0, 0, 0, 0, MX98725},
{"ASIX", "ASIX AX88141", BUSTYPE_PCI, PCI_UNITCODE(0x125B, 0x1400), 0xffffffff, 0, 0, 0x10, 0xf0, AX88141},
{"ASIX", "ASIX AX88140", BUSTYPE_PCI, PCI_UNITCODE(0x125B, 0x1400), 0xffffffff, 0, 0, 0, 0, AX88140},
{"Lite-On", "Lite-On LC82C115 PNIC-II", BUSTYPE_PCI, PCI_UNITCODE(0x11AD, 0xc115), 0xffffffff, 0, 0, 0, 0, PNIC2},
{"ADMtek", "ADMtek AN981 Comet", BUSTYPE_PCI, PCI_UNITCODE(0x1317, 0x0981), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Centaur-P", BUSTYPE_PCI, PCI_UNITCODE(0x1317, 0x0985), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Centaur-C", BUSTYPE_PCI, PCI_UNITCODE(0x1317, 0x1985), 0xffffffff, 0, 0, 0, 0, COMET},
{"D-Link", "D-Link DFE-680TXD v1.0 (ADMtek Centaur-C)", BUSTYPE_PCI, PCI_UNITCODE(0x1186, 0x1541), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Centaur-C (Linksys v2)", BUSTYPE_PCI, PCI_UNITCODE(0x13d1, 0xab02), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Centaur-C (Linksys)", BUSTYPE_PCI, PCI_UNITCODE(0x13d1, 0xab03), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Centaur-C (Linksys)", BUSTYPE_PCI, PCI_UNITCODE(0x13d1, 0xab08), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Centaur-C (Linksys PCM200 v3)", BUSTYPE_PCI, PCI_UNITCODE(0x1737, 0xab09), 0xffffffff, 0, 0, 0, 0, COMET},
{"STMicro", "STMicro STE10/100 Comet", BUSTYPE_PCI, PCI_UNITCODE(0x104a, 0x0981), 0xffffffff, 0, 0, 0, 0, COMET},
{"STMicro", "STMicro STE10/100A Comet", BUSTYPE_PCI, PCI_UNITCODE(0x104a, 0x2774), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Comet-II", BUSTYPE_PCI, PCI_UNITCODE(0x1317, 0x9511), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Comet-II (9513)", BUSTYPE_PCI, PCI_UNITCODE(0x1317, 0x9513), 0xffffffff, 0, 0, 0, 0, COMET},
{"SMC", "SMC1255TX (ADMtek Comet)", BUSTYPE_PCI, PCI_UNITCODE(0x1113, 0x1216), 0xffffffff, PCI_UNITCODE(0x10b8, 0x1255), 0xffffffff, 0, 0, COMET},
{"Accton", "Accton EN1217/EN2242 (ADMtek Comet)", BUSTYPE_PCI, PCI_UNITCODE(0x1113, 0x1216), 0xffffffff, 0, 0, 0, 0, COMET},
{"SMC", "SMC1255TX (ADMtek Comet-II)", BUSTYPE_PCI, PCI_UNITCODE(0x10b8, 0x1255), 0xffffffff, 0, 0, 0, 0, COMET},
{"ADMtek", "ADMtek Comet-II (model 1020)", BUSTYPE_PCI, PCI_UNITCODE(0x111a, 0x1020), 0xffffffff, 0, 0, 0, 0, COMET},
{"Allied Telesyn", "Allied Telesyn A120 (ADMtek Comet)", BUSTYPE_PCI, PCI_UNITCODE(0x1259, 0xa120), 0xffffffff, 0, 0, 0, 0, COMET},
{"Compex", "Compex RL100-TX", BUSTYPE_PCI, PCI_UNITCODE(0x11F6, 0x9881), 0xffffffff, 0, 0, 0, 0, COMPEX9881},
{"Intel", "Intel 21145 Tulip", BUSTYPE_PCI, PCI_UNITCODE(0x8086, 0x0039), 0xffffffff, 0, 0, 0, 0, I21145},
{"Xircom", "Xircom Tulip clone", BUSTYPE_PCI, PCI_UNITCODE(0x115d, 0x0003), 0xffffffff, 0, 0, 0, 0, XIRCOM},
{"Davicom", "Davicom DM9102", BUSTYPE_PCI, PCI_UNITCODE(0x1282, 0x9102), 0xffffffff, 0, 0, 0, 0, DC21140},
{"Davicom", "Davicom DM9100", BUSTYPE_PCI, PCI_UNITCODE(0x1282, 0x9100), 0xffffffff, 0, 0, 0, 0, DC21140},
{"Macronix", "Macronix mxic-98715 (EN1217)", BUSTYPE_PCI, PCI_UNITCODE(0x1113, 0x1217), 0xffffffff, 0, 0, 0, 0, MX98715},
{"Conexant", "Conexant LANfinity", BUSTYPE_PCI, PCI_UNITCODE(0x14f1, 0x1803), 0xffffffff, 0, 0, 0, 0, CONEXANT},
{"3Com", "3Com 3cSOHO100B-TX (ADMtek Centaur)", BUSTYPE_PCI, PCI_UNITCODE(0x10b7, 0x9300), 0xffffffff, 0, 0, 0, 0, COMET},
{NULL,},
};
// This table is used during operation for capabilities and media timer
static void tulip_timer(void *data);
static void nway_timer(void *data);
static void mxic_timer(void *data);
static void pnic_timer(void *data);
static void comet_timer(void *data);
enum tbl_flag {
HAS_MII = 0x001,
HAS_MEDIA_TABLE = 0x002,
CSR12_IN_SROM = 0x004,
ALWAYS_CHECK_MII = 0x008,
HAS_PWRDWN = 0x010,
MC_HASH_ONLY = 0x020, // Hash-only multicast filter
HAS_PNICNWAY = 0x080,
HAS_NWAY = 0x040, // Uses internal NWay xcvr
HAS_INTR_MITIGATION = 0x100,
IS_ASIX = 0x200,
HAS_8023X = 0x400,
COMET_MAC_ADDR = 0x800,
};
//
// Note: this table must match enum tulip_chips above
//
struct tulip_chip_table {
char *chip_name;
int io_size; // Unused
int valid_intrs; // CSR7 interrupt enable settings
int flags;
void (*media_timer)(void *data);
};
static struct tulip_chip_table tulip_tbl[] = {
{"Digital DC21040 Tulip", 128, 0x0001ebef, 0, tulip_timer},
{"Digital DC21041 Tulip", 128, 0x0001ebff, HAS_MEDIA_TABLE | HAS_NWAY, tulip_timer},
{"Digital DS21140 Tulip", 128, 0x0001ebef, HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM, tulip_timer},
{"Digital DS21143 Tulip", 128, 0x0801fbff, HAS_MII | HAS_MEDIA_TABLE | ALWAYS_CHECK_MII | HAS_PWRDWN | HAS_NWAY | HAS_INTR_MITIGATION, nway_timer},
{"Lite-On 82c168 PNIC", 256, 0x0001ebef, HAS_MII | HAS_PNICNWAY, pnic_timer},
{"Macronix 98713 PMAC", 128, 0x0001ebef, HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM, mxic_timer},
{"Macronix 98715 PMAC", 256, 0x0001ebef, HAS_MEDIA_TABLE, mxic_timer},
{"Macronix 98725 PMAC", 256, 0x0001ebef, HAS_MEDIA_TABLE, mxic_timer},
{"ASIX AX88140", 128, 0x0001fbff, HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM | MC_HASH_ONLY | IS_ASIX, tulip_timer},
{"ASIX AX88141", 128, 0x0001fbff, HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM | MC_HASH_ONLY | IS_ASIX, tulip_timer},
{"Lite-On PNIC-II", 256, 0x0801fbff, HAS_MII | HAS_NWAY | HAS_8023X, nway_timer},
{"ADMtek Comet", 256, 0x0001abef, HAS_MII | MC_HASH_ONLY | COMET_MAC_ADDR, comet_timer},
{"Compex 9881 PMAC", 128, 0x0001ebef, HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM, mxic_timer},
{"Intel DS21145 Tulip", 128, 0x0801fbff, HAS_MII | HAS_MEDIA_TABLE | ALWAYS_CHECK_MII | HAS_PWRDWN | HAS_NWAY, nway_timer},
{"Xircom tulip work-alike", 128, 0x0801fbff, HAS_MII | HAS_MEDIA_TABLE | ALWAYS_CHECK_MII | HAS_PWRDWN | HAS_NWAY, nway_timer},
{"Conexant LANfinity", 256, 0x0001ebef, HAS_MII | HAS_PWRDWN, tulip_timer},
{0},
};
//
// A full-duplex map for media types
//
enum MediaIs {
MediaIsFD = 1,
MediaAlwaysFD = 2,
MediaIsMII = 4,
MediaIsFx = 8,
MediaIs100 = 16
};
static const char media_cap[32] = {0,0,0,16, 3,19,16,24, 27,4,7,5, 0,20,23,20, 28,31,0,0, };
static unsigned char t21040_csr13[] = {2,0x0C,8,4, 4,0,0,0, 0,0,0,0, 4,0,0,0};
// 21041 transceiver register settings: 10-T, 10-2, AUI, 10-T, 10T-FD
static unsigned short t21041_csr13[] = { 0xEF01, 0xEF09, 0xEF09, 0xEF01, 0xEF09, };
static unsigned short t21041_csr14[] = { 0xFFFF, 0xF7FD, 0xF7FD, 0x6F3F, 0x6F3D, };
static unsigned short t21041_csr15[] = { 0x0008, 0x0006, 0x000E, 0x0008, 0x0008, };
static unsigned short t21142_csr13[] = { 0x0001, 0x0009, 0x0009, 0x0000, 0x0001, };
static unsigned short t21142_csr14[] = { 0xFFFF, 0x0705, 0x0705, 0x0000, 0x7F3D, };
static unsigned short t21142_csr15[] = { 0x0008, 0x0006, 0x000E, 0x0008, 0x0008, };
//
// Offsets to the Command and Status Registers, "CSRs". All accesses
// must be longword instructions and quadword aligned.
//
enum tulip_offsets {
CSR0 = 0,
CSR1 = 0x08,
CSR2 = 0x10,
CSR3 = 0x18,
CSR4 = 0x20,
CSR5 = 0x28,
CSR6 = 0x30,
CSR7 = 0x38,
CSR8 = 0x40,
CSR9 = 0x48,
CSR10 = 0x50,
CSR11 = 0x58,
CSR12 = 0x60,
CSR13 = 0x68,
CSR14 = 0x70,
CSR15 = 0x78
};
// The bits in the CSR5 status registers, mostly interrupt sources
enum status_bits {
TimerInt = 0x800,
TPLnkFail = 0x1000,
TPLnkPass = 0x10,
NormalIntr = 0x10000,
AbnormalIntr = 0x8000,
PCIBusError = 0x2000,
RxJabber = 0x200,
RxStopped = 0x100,
RxNoBuf = 0x80,
RxIntr = 0x40,
TxFIFOUnderflow = 0x20,
TxJabber = 0x08,
TxNoBuf = 0x04,
TxDied = 0x02,
TxIntr = 0x01,
};
// The configuration bits in CSR6
enum csr6_mode_bits {
TxOn = 0x2000,
RxOn = 0x0002,
FullDuplex = 0x0200,
AcceptBroadcast = 0x0100,
AcceptAllMulticast = 0x0080,
AcceptAllPhys = 0x0040,
AcceptRunt = 0x0008,
};
// The Tulip Rx and Tx buffer descriptors
struct tulip_rx_desc {
long status;
long length;
unsigned long buffer1, buffer2;
};
struct tulip_tx_desc {
long status;
long length;
unsigned long buffer1, buffer2; // We use only buffer 1
};
enum desc_status_bits {
DescOwned = 0x80000000,
RxDescFatalErr = 0x8000,
RxWholePkt = 0x0300,
};
//
// Ring-wrap flag in length field, use for last ring entry.
// 0x01000000 means chain on buffer2 address,
// 0x02000000 means use the ring start address in CSR2/3.
// Note: Some work-alike chips do not function correctly in chained mode.
// The ASIX chip works only in chained mode.
// Thus we indicates ring mode, but always write the 'next' field for
// chained mode as well.
//
#define DESC_RING_WRAP 0x02000000
#define EEPROM_SIZE 512 // support 256*16 EEPROMs
struct medialeaf {
unsigned char type;
unsigned char media;
unsigned char *leafdata;
};
struct mediatable {
unsigned short defaultmedia;
unsigned char leafcount, csr12dir; // General purpose pin directions
unsigned has_mii:1, has_nonmii:1, has_reset:6;
unsigned long csr15dir, csr15val; // 21143 NWay setting
struct medialeaf mleaf[0];
};
struct mediainfo {
struct mediainfo *next;
int info_type;
int index;
unsigned char *info;
};
struct tulip_private {
struct tulip_rx_desc rx_ring[RX_RING_SIZE];
struct tulip_tx_desc tx_ring[TX_RING_SIZE];
// The saved addresses of Rx/Tx-in-place packet buffers
struct pbuf *tx_pbuf[TX_RING_SIZE];
struct pbuf *rx_pbuf[RX_RING_SIZE];
struct dev *next_module;
unsigned short setup_frame[96]; // Pseudo-Tx frame to init address table
unsigned long mc_filter[2]; // Multicast hash filter
struct eth_addr hwaddr; // MAC address for NIC
dev_t devno;
struct unit *pci_dev;
int chip_id, revision;
int flags;
int iobase; // Configured I/O base
int irq; // Configured IRQ
struct timer timer; // Media selection timer
struct interrupt intr; // Interrupt object for driver
struct dpc dpc; // DPC for driver
unsigned int csr0, csr6; // Current CSR0, CSR6 settings
// Note: cache line pairing and isolation of Rx vs. Tx indicies
struct sem tx_sem; // Semaphore for Tx ring not full
unsigned int cur_rx, dirty_rx; // Producer/consumer ring indices
struct stats_nic stats;
unsigned int cur_tx, dirty_tx;
unsigned int tx_full:1; // The Tx queue is full
unsigned int rx_dead:1; // We have no Rx buffers
unsigned int full_duplex:1; // Full-duplex operation requested
unsigned int full_duplex_lock:1;
unsigned int fake_addr:1; // Multiport board faked address
unsigned int media2:4; // Secondary monitored media port
unsigned int medialock:1; // Do not sense media type
unsigned int mediasense:1; // Media sensing in progress
unsigned int nway:1, nwayset:1; // 21143 internal NWay
unsigned int default_port:8; // Last if_port value
unsigned char eeprom[EEPROM_SIZE]; // Serial EEPROM contents
void (*link_change)(struct dev *dev, int csr5);
unsigned short lpar; // 21143 Link partner ability
unsigned short sym_advertise, mii_advertise; // NWay to-advertise
unsigned short advertising[4]; // MII advertise, from SROM table
signed char phys[4], mii_cnt; // MII device addresses
//spinlock_t mii_lock;
struct mediatable *mtable;
int cur_index; // Current media index
int if_port;
int saved_if_port;
int trans_start;
int last_rx;
};
static void start_link(struct dev *dev);
static void parse_eeprom(struct dev *dev);
static int read_eeprom(long ioaddr, int location, int addr_len);
static int mdio_read(struct dev *dev, int phy_id, int location);
static void mdio_write(struct dev *dev, int phy_id, int location, int value);
static int tulip_open(struct dev *dev);
// Chip-specific media selection (timer functions prototyped above)
static int check_duplex(struct dev *dev);
static void select_media(struct dev *dev, int startup);
static void init_media(struct dev *dev);
static void nway_lnk_change(struct dev *dev, int csr5);
static void nway_start(struct dev *dev);
static void pnic_lnk_change(struct dev *dev, int csr5);
static void pnic_do_nway(struct dev *dev);
static void tulip_tx_timeout(struct dev *dev);
static void tulip_init_ring(struct dev *dev);
static int tulip_rx(struct dev *dev);
static void tulip_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
static int tulip_close(struct dev *dev);
static struct stats_nic *tulip_get_stats(struct dev *dev);
static int tulip_set_rx_mode(struct dev *dev);
// A list of all installed Tulip devices
static struct dev *root_tulip_dev = NULL;
//
// Start the link, typically called at probe1() time but sometimes later with
// multiport cards
//
static void start_link(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int i;
if ((tp->flags & ALWAYS_CHECK_MII) || (tp->mtable && tp->mtable->has_mii) || (!tp->mtable && (tp->flags & HAS_MII))) {
int phyn, phy_idx = 0;
if (tp->mtable && tp->mtable->has_mii) {
for (i = 0; i < tp->mtable->leafcount; i++) {
if (tp->mtable->mleaf[i].media == 11) {
tp->cur_index = i;
tp->saved_if_port = tp->if_port;
select_media(dev, 2);
tp->if_port = tp->saved_if_port;
break;
}
}
}
// Find the connected MII xcvrs
// Doing this in open() would allow detecting external xcvrs later,
// but takes much time
for (phyn = 1; phyn <= 32 && phy_idx < sizeof(tp->phys); phyn++) {
int phy = phyn & 0x1f;
int mii_status = mdio_read(dev, phy, 1);
if ((mii_status & 0x8301) == 0x8001 || ((mii_status & 0x8000) == 0 && (mii_status & 0x7800) != 0)) {
int mii_reg0 = mdio_read(dev, phy, 0);
int mii_advert = mdio_read(dev, phy, 4);
int to_advert;
if (tp->mii_advertise) {
to_advert = tp->mii_advertise;
} else if (tp->advertising[phy_idx]) {
to_advert = tp->advertising[phy_idx];
} else {
// Leave unchanged
tp->mii_advertise = to_advert = mii_advert;
}
tp->phys[phy_idx++] = phy;
kprintf(KERN_INFO "%s: MII transceiver #%d config %4.4x status %4.4x advertising %4.4x.\n", dev->name, phy, mii_reg0, mii_status, mii_advert);
// Fixup for DLink with miswired PHY
if (mii_advert != to_advert) {
kprintf(KERN_DEBUG "%s: Advertising %4.4x on PHY %d, previously advertising %4.4x.\n", dev->name, to_advert, phy, mii_advert);
mdio_write(dev, phy, 4, to_advert);
}
// Enable autonegotiation: some boards default to off
mdio_write(dev, phy, 0, (mii_reg0 & ~0x3000) | (tp->full_duplex ? 0x0100 : 0x0000) | ((media_cap[tp->default_port] & MediaIs100) ? 0x2000 : 0x1000));
}
}
tp->mii_cnt = phy_idx;
if (tp->mtable && tp->mtable->has_mii && phy_idx == 0) {
kprintf(KERN_INFO "%s: ***WARNING***: No MII transceiver found!\n", dev->name);
tp->phys[0] = 1;
}
}
// Reset the xcvr interface and turn on heartbeat
switch (tp->chip_id) {
case DC21040:
writel(0x00000000, ioaddr + CSR13);
writel(0x00000004, ioaddr + CSR13);
break;
case DC21041:
// This is nway_start()
if (tp->sym_advertise == 0) tp->sym_advertise = 0x0061;
writel(0x00000000, ioaddr + CSR13);
writel(0xFFFFFFFF, ioaddr + CSR14);
writel(0x00000008, ioaddr + CSR15); // Listen on AUI also
writel(readl(ioaddr + CSR6) | FullDuplex, ioaddr + CSR6);
writel(0x0000EF01, ioaddr + CSR13);
break;
case DC21140:
default:
if (tp->mtable) writel(tp->mtable->csr12dir | 0x100, ioaddr + CSR12);
break;
case DC21142:
case PNIC2:
if (tp->mii_cnt || media_cap[tp->if_port] & MediaIsMII) {
writel(0x82020000, ioaddr + CSR6);
writel(0x0000, ioaddr + CSR13);
writel(0x0000, ioaddr + CSR14);
writel(0x820E0000, ioaddr + CSR6);
} else {
nway_start(dev);
}
break;
case LC82C168:
if (!tp->mii_cnt) {
tp->nway = 1;
tp->nwayset = 0;
writel(0x00420000, ioaddr + CSR6);
writel(0x30, ioaddr + CSR12);
writel(0x0001F078, ioaddr + 0xB8);
writel(0x0201F078, ioaddr + 0xB8); // Turn on autonegotiation
}
break;
case MX98713:
case COMPEX9881:
writel(0x00000000, ioaddr + CSR6);
writel(0x000711C0, ioaddr + CSR14); // Turn on NWay
writel(0x00000001, ioaddr + CSR13);
break;
case MX98715:
case MX98725:
writel(0x01a80000, ioaddr + CSR6);
writel(0xFFFFFFFF, ioaddr + CSR14);
writel(0x00001000, ioaddr + CSR12);
break;
case COMET:
break;
}
if (tp->flags & HAS_PWRDWN) pci_write_config_dword(tp->pci_dev, 0x40, 0x40000000);
}
//
// Serial EEPROM section
//
// The main routine to parse the very complicated SROM structure.
// Search www.digital.com for "21X4 SROM" to get details.
// This code is very complex, and will require changes to support
// additional cards, so I will be verbose about what is going on.
//
// Known cards that have old-style EEPROMs.
// Writing this table is described at http://www.scyld.com/network/tulip-media.html
//
struct fixups {
char *name;
unsigned char addr0, addr1, addr2;
unsigned short newtable[32]; /* Max length below. */
};
static struct fixups eeprom_fixups[] = {
{"Asante", 0, 0, 0x94, {0x1e00, 0x0000, 0x0800, 0x0100, 0x018c,
0x0000, 0x0000, 0xe078, 0x0001, 0x0050, 0x0018}},
{"SMC9332DST", 0, 0, 0xC0, { 0x1e00, 0x0000, 0x0800, 0x041f,
0x0000, 0x009E, // 10baseT
0x0004, 0x009E, // 10baseT-FD
0x0903, 0x006D, // 100baseTx
0x0905, 0x006D, // 100baseTx-FD
}},
{"Cogent EM100", 0, 0, 0x92, { 0x1e00, 0x0000, 0x0800, 0x063f,
0x0107, 0x8021, // 100baseFx
0x0108, 0x8021, // 100baseFx-FD
0x0100, 0x009E, // 10baseT
0x0104, 0x009E, // 10baseT-FD
0x0103, 0x006D, // 100baseTx
0x0105, 0x006D, // 100baseTx-FD
}},
{"Maxtech NX-110", 0, 0, 0xE8, { 0x1e00, 0x0000, 0x0800, 0x0513,
0x1001, 0x009E, // 10base2, CSR12 0x10
0x0000, 0x009E, // 10baseT
0x0004, 0x009E, // 10baseT-FD
0x0303, 0x006D, // 100baseTx, CSR12 0x03
0x0305, 0x006D, // 100baseTx-FD CSR12 0x03
}},
{"Accton EN1207", 0, 0, 0xE8, { 0x1e00, 0x0000, 0x0800, 0x051F,
0x1B01, 0x0000, // 10base2, CSR12 0x1B
0x0B00, 0x009E, // 10baseT, CSR12 0x0B
0x0B04, 0x009E, // 10baseT-FD,CSR12 0x0B
0x1B03, 0x006D, // 100baseTx, CSR12 0x1B
0x1B05, 0x006D, // 100baseTx-FD CSR12 0x1B
}},
{NULL,}
};
static char *block_name[] = {
"21140 non-MII", "21140 MII PHY", "21142 Serial PHY", "21142 MII PHY", "21143 SYM PHY", "21143 reset method"
};
#define get_u16(ptr) (*(unsigned short *)(ptr))
static void parse_eeprom(struct dev *dev) {
// The last media info list parsed, for multiport boards
static struct mediatable *last_mediatable = NULL;
static unsigned char *last_ee_data = NULL;
static int controller_index = 0;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
unsigned char *p, *ee_data = tp->eeprom;
int new_advertise = 0;
int i;
tp->mtable = 0;
// Detect an old-style (SA only) EEPROM layout: memcmp(eedata, eedata + 16, 8)
for (i = 0; i < 8; i ++) {
if (ee_data[i] != ee_data[16 + i]) break;
}
if (i >= 8) {
if (ee_data[0] == 0xff) {
if (last_mediatable) {
controller_index++;
kprintf(KERN_INFO "%s: Controller %d of multiport board.\n", dev->name, controller_index);
tp->mtable = last_mediatable;
ee_data = last_ee_data;
goto subsequent_board;
} else {
kprintf(KERN_INFO "%s: Missing EEPROM, this interface may not work correctly!\n", dev->name);
}
return;
}
// Do a fix-up based on the vendor half of the station address
for (i = 0; eeprom_fixups[i].name; i++) {
if (tp->hwaddr.addr[0] == eeprom_fixups[i].addr0 &&
tp->hwaddr.addr[1] == eeprom_fixups[i].addr1 &&
tp->hwaddr.addr[2] == eeprom_fixups[i].addr2) {
if (tp->hwaddr.addr[2] == 0xE8 && ee_data[0x1a] == 0x55) i++; // An Accton EN1207, not an outlaw Maxtech
memcpy(ee_data + 26, eeprom_fixups[i].newtable, sizeof(eeprom_fixups[i].newtable));
kprintf(KERN_INFO "%s: Old format EEPROM on '%s' board. Using substitute media control info.\n", dev->name, eeprom_fixups[i].name);
break;
}
}
if (eeprom_fixups[i].name == NULL) {
// No fixup found
kprintf(KERN_INFO "%s: Old style EEPROM with no media selection information.\n", dev->name);
return;
}
}
controller_index = 0;
if (ee_data[19] > 1) {
struct dev *prev_dev;
struct tulip_private *otp;
// This is a multiport board. The probe order may be "backwards", so we patch up already found devices.
last_ee_data = ee_data;
for (prev_dev = tp->next_module; prev_dev; prev_dev = otp->next_module) {
otp = (struct tulip_private *) prev_dev->privdata;
if (otp->eeprom[0] == 0xff && otp->mtable == 0) {
parse_eeprom(prev_dev);
start_link(prev_dev);
} else {
break;
}
}
controller_index = 0;
}
subsequent_board:
p = (void *) (ee_data + ee_data[27 + controller_index * 3]);
if (ee_data[27] == 0) {
// No valid media table
} else if (tp->chip_id == DC21041) {
int media = get_u16(p);
int count = p[2];
p += 3;
kprintf(KERN_INFO "%s: 21041 Media table, default media %4.4x (%s).\n", dev->name, media, media & 0x0800 ? "Autosense" : medianame[media & MEDIA_MASK]);
for (i = 0; i < count; i++) {
unsigned char media_block = *p++;
int media_code = media_block & MEDIA_MASK;
if (media_block & 0x40) p += 6;
switch(media_code) {
case 0: new_advertise |= 0x0020; break;
case 4: new_advertise |= 0x0040; break;
}
kprintf(KERN_INFO "%s: 21041 media #%d, %s.\n", dev->name, media_code, medianame[media_code]);
}
} else {
unsigned char csr12dir = 0;
int count;
struct mediatable *mtable;
unsigned short media = get_u16(p);
p += 2;
if (tp->flags & CSR12_IN_SROM) csr12dir = *p++;
count = *p++;
mtable = (struct mediatable *) kmalloc(sizeof(struct mediatable) + count * sizeof(struct medialeaf));
if (mtable == NULL) return;
last_mediatable = tp->mtable = mtable;
mtable->defaultmedia = media;
mtable->leafcount = count;
mtable->csr12dir = csr12dir;
mtable->has_nonmii = mtable->has_mii = mtable->has_reset = 0;
mtable->csr15dir = mtable->csr15val = 0;
kprintf(KERN_INFO "%s: EEPROM default media type %s.\n", dev->name, media & 0x0800 ? "Autosense" : medianame[media & MEDIA_MASK]);
for (i = 0; i < count; i++) {
struct medialeaf *leaf = &mtable->mleaf[i];
if ((p[0] & 0x80) == 0) {
// 21140 Compact block
leaf->type = 0;
leaf->media = p[0] & 0x3f;
leaf->leafdata = p;
if ((p[2] & 0x61) == 0x01) mtable->has_mii = 1; // Bogus, but Znyx boards do it
p += 4;
} else {
switch(leaf->type = p[1]) {
case 5:
mtable->has_reset = i + 1; // Assure non-zero
// Fall through
case 6:
leaf->media = 31;
break;
case 1:
case 3:
mtable->has_mii = 1;
leaf->media = 11;
break;
case 2:
if ((p[2] & 0x3f) == 0) {
unsigned long base15 = (p[2] & 0x40) ? get_u16(p + 7) : 0x0008;
unsigned short *p1 = (unsigned short *)(p + (p[2] & 0x40 ? 9 : 3));
mtable->csr15dir = (p1[0] << 16) + base15;
mtable->csr15val = (p1[1] << 16) + base15;
}
// Fall through
case 0:
case 4:
mtable->has_nonmii = 1;
leaf->media = p[2] & MEDIA_MASK;
switch (leaf->media) {
case 0: new_advertise |= 0x0020; break;
case 4: new_advertise |= 0x0040; break;
case 3: new_advertise |= 0x0080; break;
case 5: new_advertise |= 0x0100; break;
case 6: new_advertise |= 0x0200; break;
}
break;
default:
leaf->media = 19;
}
leaf->leafdata = p + 2;
p += (p[0] & 0x3f) + 1;
}
}
if (new_advertise) tp->sym_advertise = new_advertise;
}
}
// Reading a serial EEPROM is a "bit" grungy, but we work our way through:->
// EEPROM_Ctrl bits
#define EE_SHIFT_CLK 0x02 // EEPROM shift clock
#define EE_CS 0x01 // EEPROM chip select
#define EE_DATA_WRITE 0x04 // Data from the Tulip to EEPROM
#define EE_WRITE_0 0x01
#define EE_WRITE_1 0x05
#define EE_DATA_READ 0x08 // Data from the EEPROM chip
#define EE_ENB (0x4800 | EE_CS)
// Delay between EEPROM clock transitions.
// Even at 33Mhz current PCI implementations do not overrun the EEPROM clock.
// We add a bus turn-around to insure that this remains true
#define eeprom_delay() readl(ee_addr)
// The EEPROM commands include the alway-set leading bit
#define EE_READ_CMD (6)
// Note: this routine returns extra data bits for size detection
static int read_eeprom(long ioaddr, int location, int addr_len) {
int i;
unsigned retval = 0;
long ee_addr = ioaddr + CSR9;
int read_cmd = location | (EE_READ_CMD << addr_len);
writel(EE_ENB & ~EE_CS, ee_addr);
writel(EE_ENB, ee_addr);
// Shift the read command bits out
for (i = 4 + addr_len; i >= 0; i--) {
short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
writel(EE_ENB | dataval, ee_addr);
eeprom_delay();
writel(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
eeprom_delay();
retval = (retval << 1) | ((readl(ee_addr) & EE_DATA_READ) ? 1 : 0);
}
writel(EE_ENB, ee_addr);
eeprom_delay();
for (i = 16; i > 0; i--) {
writel(EE_ENB | EE_SHIFT_CLK, ee_addr);
eeprom_delay();
retval = (retval << 1) | ((readl(ee_addr) & EE_DATA_READ) ? 1 : 0);
writel(EE_ENB, ee_addr);
eeprom_delay();
}
// Terminate the EEPROM access
writel(EE_ENB & ~EE_CS, ee_addr);
return retval;
}
//
// 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 minimum timing is usually
// met by back-to-back PCI I/O cycles, but we insert a delay to avoid
// "overclocking" issues or future 66Mhz PCI
#define mdio_delay() readl(mdio_addr)
// Read and write the MII registers using software-generated serial
// MDIO protocol. It is just different enough from the EEPROM protocol
// to not share code. The maxium data clock rate is 2.5 Mhz
#define MDIO_SHIFT_CLK 0x10000
#define MDIO_DATA_WRITE0 0x00000
#define MDIO_DATA_WRITE1 0x20000
#define MDIO_ENB 0x00000 // Ignore the 0x02000 databook setting
#define MDIO_ENB_IN 0x40000
#define MDIO_DATA_READ 0x80000
static const unsigned char comet_miireg2offset[32] = {
0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, 0xCC, 0, 0,0,0,0, 0,0,0,0,
0,0xD0,0,0, 0,0,0,0, 0,0,0,0, 0, 0xD4, 0xD8, 0xDC,
};
static int mdio_read(struct dev *dev, int phy_id, int location) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int i;
int read_cmd = (0xf6 << 10) | ((phy_id & 0x1f) << 5) | location;
int retval = 0;
long ioaddr = tp->iobase;
long mdio_addr = ioaddr + CSR9;
//unsigned long flags;
if (location & ~0x1f) return 0xffff;
if (tp->chip_id == COMET && phy_id == 30) {
if (comet_miireg2offset[location]) {
return readl(ioaddr + comet_miireg2offset[location]);
}
return 0xffff;
}
//spin_lock_irqsave(&tp->mii_lock, flags);
if (tp->chip_id == LC82C168) {
int i = 1000;
writel(0x60020000 + (phy_id<<23) + (location<<18), ioaddr + 0xA0);
readl(ioaddr + 0xA0);
readl(ioaddr + 0xA0);
readl(ioaddr + 0xA0);
readl(ioaddr + 0xA0);
while (--i > 0) {
if (!((retval = readl(ioaddr + 0xA0)) & 0x80000000)) break;
}
//spin_unlock_irqrestore(&tp->mii_lock, flags);
return retval & 0xffff;
}
// Establish sync by sending at least 32 logic ones
for (i = 32; i >= 0; i--) {
writel(MDIO_ENB | MDIO_DATA_WRITE1, mdio_addr);
mdio_delay();
writel(MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr);
mdio_delay();
}
// Shift the read command bits out
for (i = 15; i >= 0; i--) {
int dataval = (read_cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0;
writel(MDIO_ENB | dataval, mdio_addr);
mdio_delay();
writel(MDIO_ENB | dataval | MDIO_SHIFT_CLK, mdio_addr);
mdio_delay();
}
// Read the two transition, 16 data, and wire-idle bits
for (i = 19; i > 0; i--) {
writel(MDIO_ENB_IN, mdio_addr);
mdio_delay();
retval = (retval << 1) | ((readl(mdio_addr) & MDIO_DATA_READ) ? 1 : 0);
writel(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr);
mdio_delay();
}
//spin_unlock_irqrestore(&tp->mii_lock, flags);
return (retval >> 1) & 0xffff;
}
static void mdio_write(struct dev *dev, int phy_id, int location, int val) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int i;
int cmd = (0x5002 << 16) | (phy_id << 23) | (location << 18) | (val & 0xffff);
long ioaddr = tp->iobase;
long mdio_addr = ioaddr + CSR9;
if (location & ~0x1f) return;
if (tp->chip_id == COMET && phy_id == 30) {
if (comet_miireg2offset[location]) writel(val, ioaddr + comet_miireg2offset[location]);
return;
}
if (tp->chip_id == LC82C168) {
int i = 1000;
writel(cmd, ioaddr + 0xA0);
do {
if (!(readl(ioaddr + 0xA0) & 0x80000000)) break;
} while (--i > 0);
return;
}
// Establish sync by sending 32 logic ones
for (i = 32; i >= 0; i--) {
writel(MDIO_ENB | MDIO_DATA_WRITE1, mdio_addr);
mdio_delay();
writel(MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr);
mdio_delay();
}
// Shift the command bits out
for (i = 31; i >= 0; i--) {
int dataval = (cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0;
writel(MDIO_ENB | dataval, mdio_addr);
mdio_delay();
writel(MDIO_ENB | dataval | MDIO_SHIFT_CLK, mdio_addr);
mdio_delay();
}
// Clear out extra bits
for (i = 2; i > 0; i--) {
writel(MDIO_ENB_IN, mdio_addr);
mdio_delay();
writel(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr);
mdio_delay();
}
}
//
// Set up the transceiver control registers for the selected media type.
// STARTUP indicates to reset the transceiver. It is set to '2' for
// the initial card detection, and '1' during resume or open().
//
static void select_media(struct dev *dev, int startup) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
struct mediatable *mtable = tp->mtable;
unsigned long new_csr6;
int i;
if (mtable) {
struct medialeaf *mleaf = &mtable->mleaf[tp->cur_index];
unsigned char *p = mleaf->leafdata;
//kprintf(KERN_DEBUG "%s: Media table type %d.\n", dev->name, mleaf->type);
switch (mleaf->type) {
case 0: // 21140 non-MII xcvr
//kprintf(KERN_DEBUG "%s: Using a 21140 non-MII transceiver with control setting %2.2x.\n", dev->name, p[1]);
tp->if_port = p[0];
if (startup) writel(mtable->csr12dir | 0x100, ioaddr + CSR12);
writel(p[1], ioaddr + CSR12);
new_csr6 = 0x02000000 | ((p[2] & 0x71) << 18);
break;
case 2:
case 4: {
unsigned short setup[5];
unsigned long csr13val, csr14val, csr15dir, csr15val;
for (i = 0; i < 5; i++) setup[i] = get_u16(&p[i * 2 + 1]);
tp->if_port = p[0] & MEDIA_MASK;
if (media_cap[tp->if_port] & MediaAlwaysFD) tp->full_duplex = 1;
if (startup && mtable->has_reset) {
struct medialeaf *rleaf = &mtable->mleaf[mtable->has_reset-1];
unsigned char *rst = rleaf->leafdata;
kprintf(KERN_DEBUG "%s: Resetting the transceiver.\n", dev->name);
for (i = 0; i < rst[0]; i++) writel(get_u16(rst + 1 + (i<<1)) << 16, ioaddr + CSR15);
}
kprintf(KERN_DEBUG "%s: 21143 non-MII %s transceiver control %4.4x/%4.4x.\n", dev->name, medianame[tp->if_port], setup[0], setup[1]);
if (p[0] & 0x40) {
// SIA (CSR13-15) setup values are provided
csr13val = setup[0];
csr14val = setup[1];
csr15dir = (setup[3]<<16) | setup[2];
csr15val = (setup[4]<<16) | setup[2];
writel(0, ioaddr + CSR13);
writel(csr14val, ioaddr + CSR14);
writel(csr15dir, ioaddr + CSR15); // Direction
writel(csr15val, ioaddr + CSR15); // Data
writel(csr13val, ioaddr + CSR13);
} else {
csr13val = 1;
csr14val = 0x0003FFFF;
csr15dir = (setup[0]<<16) | 0x0008;
csr15val = (setup[1]<<16) | 0x0008;
if (tp->if_port <= 4) csr14val = t21142_csr14[tp->if_port];
if (startup) {
writel(0, ioaddr + CSR13);
writel(csr14val, ioaddr + CSR14);
}
writel(csr15dir, ioaddr + CSR15); // Direction
writel(csr15val, ioaddr + CSR15); // Data
if (startup) writel(csr13val, ioaddr + CSR13);
}
kprintf(KERN_DEBUG "%s: Setting CSR15 to %8.8x/%8.8x.\n", dev->name, csr15dir, csr15val);
if (mleaf->type == 4) {
new_csr6 = 0x820A0000 | ((setup[2] & 0x71) << 18);
} else {
new_csr6 = 0x82420000;
}
break;
}
case 1:
case 3: {
int phy_num = p[0];
int init_length = p[1];
unsigned short *misc_info;
tp->if_port = 11;
new_csr6 = 0x020E0000;
if (mleaf->type == 3) {
// 21142
unsigned short *init_sequence = (unsigned short *)(p + 2);
unsigned short *reset_sequence = &((unsigned short *)(p + 3))[init_length];
int reset_length = p[2 + init_length * 2];
misc_info = reset_sequence + reset_length;
if (startup) {
for (i = 0; i < reset_length; i++) {
writel(get_u16(&reset_sequence[i]) << 16, ioaddr + CSR15);
}
}
for (i = 0; i < init_length; i++) {
writel(get_u16(&init_sequence[i]) << 16, ioaddr + CSR15);
}
} else {
unsigned char *init_sequence = p + 2;
unsigned char *reset_sequence = p + 3 + init_length;
int reset_length = p[2 + init_length];
misc_info = (unsigned short *)(reset_sequence + reset_length);
if (startup) {
writel(mtable->csr12dir | 0x100, ioaddr + CSR12);
for (i = 0; i < reset_length; i++) writel(reset_sequence[i], ioaddr + CSR12);
}
for (i = 0; i < init_length; i++) writel(init_sequence[i], ioaddr + CSR12);
}
tp->advertising[phy_num] = get_u16(&misc_info[1]) | 1;
if (startup < 2) {
if (tp->mii_advertise == 0) tp->mii_advertise = tp->advertising[phy_num];
kprintf(KERN_DEBUG "%s: Advertising %4.4x on MII %d.\n", dev->name, tp->mii_advertise, tp->phys[phy_num]);
mdio_write(dev, tp->phys[phy_num], 4, tp->mii_advertise);
}
break;
}
default:
kprintf(KERN_DEBUG "%s: Invalid media table selection %d.\n", dev->name, mleaf->type);
new_csr6 = 0x020E0000;
}
//kprintf(KERN_DEBUG "%s: Using media type %s, CSR12 is %2.2x.\n", dev->name, medianame[tp->if_port], readl(ioaddr + CSR12) & 0xff);
} else if (tp->chip_id == DC21041) {
int port = tp->if_port <= 4 ? tp->if_port : 0;
kprintf(KERN_DEBUG "%s: 21041 using media %s, CSR12 is %4.4x.\n", dev->name, medianame[port == 3 ? 12: port], readl(ioaddr + CSR12));
writel(0x00000000, ioaddr + CSR13); /* Reset the serial interface */
writel(t21041_csr14[port], ioaddr + CSR14);
writel(t21041_csr15[port], ioaddr + CSR15);
writel(t21041_csr13[port], ioaddr + CSR13);
new_csr6 = 0x80020000;
} else if (tp->chip_id == LC82C168) {
if (startup && ! tp->medialock) tp->if_port = tp->mii_cnt ? 11 : 0;
kprintf(KERN_DEBUG "%s: PNIC PHY status is %3.3x, media %s.\n", dev->name, readl(ioaddr + 0xB8), medianame[tp->if_port]);
if (tp->mii_cnt) {
new_csr6 = 0x810C0000;
writel(0x0001, ioaddr + CSR15);
writel(0x0201B07A, ioaddr + 0xB8);
} else if (startup) {
// Start with 10mbps to do autonegotiation
writel(0x32, ioaddr + CSR12);
new_csr6 = 0x00420000;
writel(0x0001B078, ioaddr + 0xB8);
writel(0x0201B078, ioaddr + 0xB8);
} else if (tp->if_port == 3 || tp->if_port == 5) {
writel(0x33, ioaddr + CSR12);
new_csr6 = 0x01860000;
// Trigger autonegotiation
writel(startup ? 0x0201F868 : 0x0001F868, ioaddr + 0xB8);
} else {
writel(0x32, ioaddr + CSR12);
new_csr6 = 0x00420000;
writel(0x1F078, ioaddr + 0xB8);
}
} else if (tp->chip_id == DC21040) {
// 21040
// Turn on the xcvr interface
int csr12 = readl(ioaddr + CSR12);
kprintf(KERN_DEBUG "%s: 21040 media type is %s, CSR12 is %2.2x.\n", dev->name, medianame[tp->if_port], csr12);
if (media_cap[tp->if_port] & MediaAlwaysFD) tp->full_duplex = 1;
new_csr6 = 0x20000;
// Set the full duplux match frame
writel(FULL_DUPLEX_MAGIC, ioaddr + CSR11);
writel(0x00000000, ioaddr + CSR13); // Reset the serial interface
if (t21040_csr13[tp->if_port] & 8) {
writel(0x0705, ioaddr + CSR14);
writel(0x0006, ioaddr + CSR15);
} else {
writel(0xffff, ioaddr + CSR14);
writel(0x0000, ioaddr + CSR15);
}
writel(0x8f01 | t21040_csr13[tp->if_port], ioaddr + CSR13);
} else {
// Unknown chip type with no media table
if (tp->default_port == 0) tp->if_port = tp->mii_cnt ? 11 : 3;
if (media_cap[tp->if_port] & MediaIsMII) {
new_csr6 = 0x020E0000;
} else if (media_cap[tp->if_port] & MediaIsFx) {
new_csr6 = 0x02860000;
} else {
new_csr6 = 0x038E0000;
}
kprintf(KERN_DEBUG "%s: No media description table, assuming %s transceiver, CSR12 %2.2x.\n", dev->name, medianame[tp->if_port], readl(ioaddr + CSR12));
}
tp->csr6 = new_csr6 | (tp->csr6 & 0xfdff) | (tp->full_duplex ? FullDuplex : 0);
}
static void init_media(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int i;
tp->saved_if_port = tp->if_port;
if (tp->if_port == 0) tp->if_port = tp->default_port;
// Allow selecting a default media
i = 0;
if (tp->mtable == NULL) goto media_picked;
if (tp->if_port) {
int looking_for = media_cap[tp->if_port] & MediaIsMII ? 11 : (tp->if_port == 12 ? 0 : tp->if_port);
for (i = 0; i < tp->mtable->leafcount; i++) {
if (tp->mtable->mleaf[i].media == looking_for) {
kprintf(KERN_INFO "%s: Using user-specified media %s.\n", dev->name, medianame[tp->if_port]);
goto media_picked;
}
}
}
if ((tp->mtable->defaultmedia & 0x0800) == 0) {
int looking_for = tp->mtable->defaultmedia & MEDIA_MASK;
for (i = 0; i < tp->mtable->leafcount; i++) {
if (tp->mtable->mleaf[i].media == looking_for) {
kprintf(KERN_INFO "%s: Using EEPROM-set media %s.\n", dev->name, medianame[looking_for]);
goto media_picked;
}
}
}
// Start sensing first non-full-duplex media
for (i = tp->mtable->leafcount - 1; (media_cap[tp->mtable->mleaf[i].media] & MediaAlwaysFD) && i > 0; i--);
media_picked:
tp->csr6 = 0;
tp->cur_index = i;
tp->nwayset = 0;
if (tp->if_port) {
if (tp->chip_id == DC21143 && (media_cap[tp->if_port] & MediaIsMII)) {
// We must reset the media CSRs when we force-select MII mode
writel(0x0000, ioaddr + CSR13);
writel(0x0000, ioaddr + CSR14);
writel(0x0008, ioaddr + CSR15);
}
select_media(dev, 1);
return;
}
switch(tp->chip_id) {
case DC21041:
// tp->nway = 1;
nway_start(dev);
break;
case DC21142:
if (tp->mii_cnt) {
select_media(dev, 1);
kprintf(KERN_INFO "%s: Using MII transceiver %d, status %4.4x.\n", dev->name, tp->phys[0], mdio_read(dev, tp->phys[0], 1));
writel(0x82020000, ioaddr + CSR6);
tp->csr6 = 0x820E0000;
tp->if_port = 11;
writel(0x0000, ioaddr + CSR13);
writel(0x0000, ioaddr + CSR14);
} else {
nway_start(dev);
}
break;
case PNIC2:
nway_start(dev);
break;
case LC82C168:
if (tp->mii_cnt) {
tp->if_port = 11;
tp->csr6 = 0x814C0000 | (tp->full_duplex ? FullDuplex : 0);
writel(0x0001, ioaddr + CSR15);
} else if (readl(ioaddr + CSR5) & TPLnkPass) {
pnic_do_nway(dev);
} else {
// Start with 10mbps to do autonegotiation
writel(0x32, ioaddr + CSR12);
tp->csr6 = 0x00420000;
writel(0x0001B078, ioaddr + 0xB8);
writel(0x0201B078, ioaddr + 0xB8);
}
break;
case MX98713:
case COMPEX9881:
tp->if_port = 0;
tp->csr6 = 0x01880000 | (tp->full_duplex ? FullDuplex : 0);
writel(0x0f370000 | readw(ioaddr + 0x80), ioaddr + 0x80);
break;
case MX98715:
case MX98725:
// Provided by BOLO, Macronix - 12/10/1998
tp->if_port = 0;
tp->csr6 = 0x01a80000 | FullDuplex;
writel(0x0f370000 | readw(ioaddr + 0x80), ioaddr + 0x80);
writel(0x11000 | readw(ioaddr + 0xa0), ioaddr + 0xa0);
break;
case COMET:
case CONEXANT:
// Enable automatic Tx underrun recovery
writel(readl(ioaddr + 0x88) | 1, ioaddr + 0x88);
tp->if_port = tp->mii_cnt ? 11 : 0;
tp->csr6 = 0x00040000;
break;
case AX88140:
case AX88141:
tp->csr6 = tp->mii_cnt ? 0x00040100 : 0x00000100;
break;
default:
select_media(dev, 1);
}
}
//
// Check the MII negotiated duplex, and change the CSR6 setting if
// required.
// Return 0 if everything is OK.
// Return < 0 if the transceiver is missing or has no link beat.
//
static int check_duplex(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int mii_reg1, mii_reg5, negotiated, duplex;
if (tp->full_duplex_lock) return 0;
mii_reg5 = mdio_read(dev, tp->phys[0], 5);
negotiated = mii_reg5 & tp->mii_advertise;
kprintf(KERN_INFO "%s: MII link partner %4.4x, negotiated %4.4x.\n", dev->name, mii_reg5, negotiated);
if (mii_reg5 == 0xffff) return -2;
if ((mii_reg5 & 0x4000) == 0 && ((mii_reg1 = mdio_read(dev, tp->phys[0], 1)) & 0x0004) == 0) {
int new_reg1 = mdio_read(dev, tp->phys[0], 1);
if ((new_reg1 & 0x0004) == 0) {
kprintf(KERN_INFO "%s: No link beat on the MII interface, status %4.4x.\n", dev->name, new_reg1);
return -1;
}
}
duplex = ((negotiated & 0x0300) == 0x0100 || (negotiated & 0x00C0) == 0x0040);
// 100baseTx-FD or 10T-FD, but not 100-HD
if (tp->full_duplex != duplex) {
tp->full_duplex = duplex;
if (negotiated & 0x0380) tp->csr6 &= ~0x00400000;
if (tp->full_duplex) {
tp->csr6 |= FullDuplex;
} else {
tp->csr6 &= ~FullDuplex;
}
writel(tp->csr6 | RxOn, ioaddr + CSR6);
writel(tp->csr6 | TxOn | RxOn, ioaddr + CSR6);
kprintf(KERN_INFO "%s: Setting %s-duplex based on MII #%d link partner capability of %4.4x.\n",
dev->name, tp->full_duplex ? "full" : "half",
tp->phys[0], mii_reg5);
return 1;
}
return 0;
}
static void tulip_timer(void *data) {
struct dev *dev = (struct dev *) data;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
unsigned long csr12 = readl(ioaddr + CSR12);
int next_tick = 2 * HZ;
//kprintf(KERN_DEBUG "%s: Media selection tick, %s, status %8.8x mode %8.8x SIA %8.8x %8.8x %8.8x %8.8x.\n",
// dev->name, medianame[tp->if_port], readl(ioaddr + CSR5),
// readl(ioaddr + CSR6), csr12, readl(ioaddr + CSR13),
// readl(ioaddr + CSR14), readl(ioaddr + CSR15));
switch (tp->chip_id) {
case DC21040:
if (!tp->medialock && (csr12 & 0x0002)) {
// Network error
kprintf(KERN_INFO "%s: No link beat found.\n", dev->name);
tp->if_port = (tp->if_port == 2 ? 0 : 2);
select_media(dev, 0);
tp->trans_start = get_ticks();
}
break;
case DC21041:
kprintf(KERN_DEBUG "%s: 21041 media tick CSR12 %8.8x.\n", dev->name, csr12);
if (tp->medialock) break;
switch (tp->if_port) {
case 0:
case 3:
case 4:
if (csr12 & 0x0004) {
// LnkFail. 10baseT is dead. Check for activity on alternate port.
tp->mediasense = 1;
if (csr12 & 0x0200) {
tp->if_port = 2;
} else {
tp->if_port = 1;
}
kprintf(KERN_INFO "%s: No 21041 10baseT link beat, Media switched to %s.\n", dev->name, medianame[tp->if_port]);
writel(0, ioaddr + CSR13); // Reset
writel(t21041_csr14[tp->if_port], ioaddr + CSR14);
writel(t21041_csr15[tp->if_port], ioaddr + CSR15);
writel(t21041_csr13[tp->if_port], ioaddr + CSR13);
next_tick = 10 * HZ; // 10 sec
} else {
next_tick = 30 * HZ;
}
break;
case 1: // 10base2
case 2: // AUI
if (csr12 & 0x0100) {
next_tick = (30 * HZ); // 30 sec
tp->mediasense = 0;
} else if ((csr12 & 0x0004) == 0) {
kprintf(KERN_INFO "%s: 21041 media switched to 10baseT.\n", dev->name);
tp->if_port = 0;
select_media(dev, 0);
next_tick = (24 * HZ) / 10; // 2.4 sec
} else if (tp->mediasense || (csr12 & 0x0002)) {
tp->if_port = 3 - tp->if_port; // Swap ports
select_media(dev, 0);
next_tick = 20 * HZ;
} else {
next_tick = 20 * HZ;
}
break;
}
break;
case DC21140:
case DC21142:
case MX98713:
case COMPEX9881:
default: {
struct medialeaf *mleaf;
unsigned char *p;
if (tp->mtable == NULL) {
// No EEPROM info, use generic code
// Not much that can be done. Assume this a generic MII or SYM transceiver
next_tick = 60 * HZ;
kprintf(KERN_DEBUG "%s: network media monitor CSR6 %8.8x CSR12 0x%2.2x.\n", dev->name, readl(ioaddr + CSR6), csr12 & 0xff);
break;
}
mleaf = &tp->mtable->mleaf[tp->cur_index];
p = mleaf->leafdata;
switch (mleaf->type) {
case 0: case 4: {
// Type 0 serial or 4 SYM transceiver. Check the link beat bit.
int offset = mleaf->type == 4 ? 5 : 2;
char bitnum = p[offset];
if (p[offset + 1] & 0x80) {
kprintf(KERN_DEBUG "%s: Transceiver monitor tick CSR12=%#2.2x, no media sense.\n", dev->name, csr12);
if (mleaf->type == 4) {
if (mleaf->media == 3 && (csr12 & 0x02)) goto select_next_media;
}
break;
}
//kprintf(KERN_DEBUG "%s: Transceiver monitor tick: CSR12=%#2.2x bit %d is %d, expecting %d.\n",
// dev->name, csr12, (bitnum >> 1) & 7,
// (csr12 & (1 << ((bitnum >> 1) & 7))) != 0,
// (bitnum >= 0));
// Check that the specified bit has the proper value.
if ((bitnum < 0) != ((csr12 & (1 << ((bitnum >> 1) & 7))) != 0)) {
//kprintf(KERN_DEBUG "%s: Link beat detected for %s.\n", dev->name, medianame[mleaf->media & MEDIA_MASK]);
if ((p[2] & 0x61) == 0x01) goto actually_mii; // Bogus Znyx board
break;
}
if (tp->medialock) break;
select_next_media:
if (--tp->cur_index < 0) {
// We start again, but should instead look for default
tp->cur_index = tp->mtable->leafcount - 1;
}
tp->if_port = tp->mtable->mleaf[tp->cur_index].media;
if (media_cap[tp->if_port] & MediaIsFD) goto select_next_media; // Skip FD entries
kprintf(KERN_DEBUG "%s: No link beat on media %s, trying transceiver type %s.\n",
dev->name, medianame[mleaf->media & MEDIA_MASK],
medianame[tp->mtable->mleaf[tp->cur_index].media]);
select_media(dev, 0);
// Restart the transmit process
writel(tp->csr6 | RxOn, ioaddr + CSR6);
writel(tp->csr6 | TxOn | RxOn, ioaddr + CSR6);
next_tick = (24 * HZ) / 10;
break;
}
case 1:
case 3: // 21140, 21142 MII
actually_mii:
check_duplex(dev);
next_tick = 60 * HZ;
break;
case 2: // 21142 serial block has no link beat. */
default:
break;
}
}
break;
}
mod_timer(&tp->timer, get_ticks() + next_tick);
}
//
// Handle internal NWay transceivers uniquely.
// These exist on the 21041, 21143 (in SYM mode) and the PNIC2.
//
static void nway_timer(void *data) {
struct dev *dev = (struct dev *) data;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int csr12 = readl(ioaddr + CSR12);
int next_tick = 60 * HZ;
int new_csr6 = 0;
kprintf(KERN_INFO "%s: N-Way autonegotiation status %8.8x, %s.\n", dev->name, csr12, medianame[tp->if_port]);
if (media_cap[tp->if_port] & MediaIsMII) {
check_duplex(dev);
} else if (tp->nwayset) {
// Do not screw up a negotiated session!
kprintf(KERN_INFO "%s: Using NWay-set %s media, csr12 %8.8x.\n", dev->name, medianame[tp->if_port], csr12);
} else if (tp->medialock) {
} else if (tp->if_port == 3) {
if (csr12 & 2) {
// No 100mbps link beat, revert to 10mbps.
kprintf(KERN_INFO "%s: No 21143 100baseTx link beat, %8.8x, trying NWay.\n", dev->name, csr12);
nway_start(dev);
next_tick = 3 * HZ;
}
} else if ((csr12 & 0x7000) != 0x5000) {
// Negotiation failed. Search media types.
kprintf(KERN_INFO "%s: 21143 negotiation failed, status %8.8x.\n", dev->name, csr12);
if (!(csr12 & 4)) {
// 10mbps link beat good
new_csr6 = 0x82420000;
tp->if_port = 0;
writel(0, ioaddr + CSR13);
writel(0x0003FFFF, ioaddr + CSR14);
writew(t21142_csr15[tp->if_port], ioaddr + CSR15);
writel(t21142_csr13[tp->if_port], ioaddr + CSR13);
} else {
// Select 100mbps port to check for link beat
new_csr6 = 0x83860000;
tp->if_port = 3;
writel(0, ioaddr + CSR13);
writel(0x0003FF7F, ioaddr + CSR14);
writew(8, ioaddr + CSR15);
writel(1, ioaddr + CSR13);
}
kprintf(KERN_INFO "%s: Testing new 21143 media %s.\n", dev->name, medianame[tp->if_port]);
if (new_csr6 != (tp->csr6 & ~0x20D7)) {
tp->csr6 &= 0x20D7;
tp->csr6 |= new_csr6;
writel(0x0301, ioaddr + CSR12);
writel(tp->csr6 | RxOn, ioaddr + CSR6);
writel(tp->csr6 | TxOn | RxOn, ioaddr + CSR6);
}
next_tick = 3 * HZ;
}
if (tp->cur_tx - tp->dirty_tx > 0 && get_ticks() - tp->trans_start > TX_TIMEOUT) {
kprintf(KERN_WARNING "%s: Tx hung, %d vs. %d.\n", dev->name, tp->cur_tx, tp->dirty_tx);
tulip_tx_timeout(dev);
}
mod_timer(&tp->timer, get_ticks() + next_tick);
}
static void nway_start(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int csr14 = ((tp->sym_advertise & 0x0780) << 9) | ((tp->sym_advertise & 0x0020) << 1) | 0xffbf;
tp->if_port = 0;
tp->nway = tp->mediasense = 1;
tp->nwayset = tp->lpar = 0;
if (tp->chip_id == PNIC2) {
tp->csr6 = 0x01000000 | (tp->sym_advertise & 0x0040 ? FullDuplex : 0);
return;
}
kprintf(KERN_DEBUG "%s: Restarting internal NWay autonegotiation, %8.8x.\n", dev->name, csr14);
writel(0x0001, ioaddr + CSR13);
writel(csr14, ioaddr + CSR14);
tp->csr6 = 0x82420000 | (tp->sym_advertise & 0x0040 ? FullDuplex : 0) | (tp->csr6 & 0x20ff);
writel(tp->csr6, ioaddr + CSR6);
if (tp->mtable && tp->mtable->csr15dir) {
writel(tp->mtable->csr15dir, ioaddr + CSR15);
writel(tp->mtable->csr15val, ioaddr + CSR15);
} else if (tp->chip_id != PNIC2) {
writew(0x0008, ioaddr + CSR15);
}
if (tp->chip_id == DC21041) {
writel(0xEF01, ioaddr + CSR12);
} else {
writel(0x1301, ioaddr + CSR12);
}
}
static void nway_lnk_change(struct dev *dev, int csr5) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int csr12 = readl(ioaddr + CSR12);
if (tp->chip_id == PNIC2) {
kprintf(KERN_INFO "%s: PNIC-2 link status changed, CSR5/12/14 %8.8x %8.8x, %8.8x.\n", dev->name, csr12, csr5, readl(ioaddr + CSR14));
tp->if_port = 5;
tp->lpar = csr12 >> 16;
tp->nwayset = 1;
tp->csr6 = 0x01000000 | (tp->csr6 & 0xffff);
writel(tp->csr6, ioaddr + CSR6);
return;
}
kprintf(KERN_INFO "%s: 21143 link status interrupt %8.8x, CSR5 %x, %8.8x.\n", dev->name, csr12, csr5, readl(ioaddr + CSR14));
// If NWay finished and we have a negotiated partner capability
if (tp->nway && !tp->nwayset && (csr12 & 0x7000) == 0x5000) {
int setup_done = 0;
int negotiated = tp->sym_advertise & (csr12 >> 16);
tp->lpar = csr12 >> 16;
tp->nwayset = 1;
if (negotiated & 0x0100) {
tp->if_port = 5;
} else if (negotiated & 0x0080) {
tp->if_port = 3;
} else if (negotiated & 0x0040) {
tp->if_port = 4;
} else if (negotiated & 0x0020) {
tp->if_port = 0;
} else {
tp->nwayset = 0;
if ((csr12 & 2) == 0 && (tp->sym_advertise & 0x0180)) tp->if_port = 3;
}
tp->full_duplex = (media_cap[tp->if_port] & MediaAlwaysFD) ? 1 : 0;
if (tp->nwayset) {
kprintf(KERN_INFO "%s: Switching to %s based on link negotiation %4.4x & %4.4x = %4.4x.\n", dev->name, medianame[tp->if_port], tp->sym_advertise, tp->lpar, negotiated);
} else {
kprintf(KERN_INFO "%s: Autonegotiation failed, using %s, link beat status %4.4x.\n", dev->name, medianame[tp->if_port], csr12);
}
if (tp->mtable) {
int i;
for (i = 0; i < tp->mtable->leafcount; i++) {
if (tp->mtable->mleaf[i].media == tp->if_port) {
tp->cur_index = i;
select_media(dev, 0);
setup_done = 1;
break;
}
}
}
if (! setup_done) {
tp->csr6 = (tp->if_port & 1 ? 0x838E0000 : 0x82420000) | (tp->csr6 & 0x20ff);
if (tp->full_duplex) tp->csr6 |= FullDuplex;
writel(1, ioaddr + CSR13);
}
writel(tp->csr6 | TxOn | RxOn, ioaddr + CSR6);
kprintf(KERN_DEBUG "%s: Setting CSR6 %8.8x/%x CSR12 %8.8x.\n", dev->name, tp->csr6, readl(ioaddr + CSR6), readl(ioaddr + CSR12));
} else if ((tp->nwayset && (csr5 & 0x08000000) &&
(tp->if_port == 3 || tp->if_port == 5) &&
(csr12 & 2) == 2) || (tp->nway && (csr5 & (TPLnkFail)))) {
// Link blew? Maybe restart NWay
del_timer(&tp->timer);
nway_start(dev);
mod_timer(&tp->timer, get_ticks() + 3 * HZ);
} else if (tp->if_port == 3 || tp->if_port == 5) {
kprintf(KERN_INFO "%s: 21143 %s link beat %s.\n", dev->name, medianame[tp->if_port], (csr12 & 2) ? "failed" : "good");
if ((csr12 & 2) && ! tp->medialock) {
del_timer(&tp->timer);
nway_start(dev);
mod_timer(&tp->timer, get_ticks() + 3 * HZ);
} else if (tp->if_port == 5) {
writel(readl(ioaddr + CSR14) & ~0x080, ioaddr + CSR14);
}
} else if (tp->if_port == 0 || tp->if_port == 4) {
if ((csr12 & 4) == 0) {
kprintf(KERN_INFO "%s: 21143 10baseT link beat good.\n", dev->name);
}
} else if (!(csr12 & 4)) {
// 10mbps link beat good
kprintf(KERN_INFO "%s: 21143 10mbps sensed media.\n", dev->name);
tp->if_port = 0;
} else if (tp->nwayset) {
kprintf(KERN_INFO "%s: 21143 using NWay-set %s, csr6 %8.8x.\n", dev->name, medianame[tp->if_port], tp->csr6);
} else {
// 100mbps link beat good
kprintf(KERN_INFO "%s: 21143 100baseTx sensed media.\n", dev->name);
tp->if_port = 3;
tp->csr6 = 0x838E0000 | (tp->csr6 & 0x20ff);
writel(0x0003FF7F, ioaddr + CSR14);
writel(0x0301, ioaddr + CSR12);
writel(tp->csr6 | RxOn, ioaddr + CSR6);
writel(tp->csr6 | RxOn | TxOn, ioaddr + CSR6);
}
}
static void mxic_timer(void *data) {
struct dev *dev = (struct dev *)data;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int next_tick = 60 * HZ;
kprintf(KERN_INFO "%s: MXIC negotiation status %8.8x.\n", dev->name, readl(ioaddr + CSR12));
mod_timer(&tp->timer, get_ticks() + next_tick);
}
static void pnic_do_nway(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
unsigned long phy_reg = readl(ioaddr + 0xB8);
unsigned long new_csr6 = tp->csr6 & ~0x40C40200;
if (phy_reg & 0x78000000) {
// Ignore baseT4
if (phy_reg & 0x20000000) {
tp->if_port = 5;
} else if (phy_reg & 0x40000000) {
tp->if_port = 3;
} else if (phy_reg & 0x10000000) {
tp->if_port = 4;
} else if (phy_reg & 0x08000000) {
tp->if_port = 0;
}
tp->nwayset = 1;
new_csr6 = (tp->if_port & 1) ? 0x01860000 : 0x00420000;
writel(0x32 | (tp->if_port & 1), ioaddr + CSR12);
if (tp->if_port & 1) writel(0x1F868, ioaddr + 0xB8);
if (phy_reg & 0x30000000) {
tp->full_duplex = 1;
new_csr6 |= FullDuplex;
}
kprintf(KERN_DEBUG "%s: PNIC autonegotiated status %8.8x, %s.\n", dev->name, phy_reg, medianame[tp->if_port]);
if (tp->csr6 != new_csr6) {
tp->csr6 = new_csr6;
writel(tp->csr6 | RxOn, ioaddr + CSR6); // Restart Tx
writel(tp->csr6 | TxOn | RxOn, ioaddr + CSR6);
tp->trans_start = get_ticks();
}
}
}
static void pnic_lnk_change(struct dev *dev, int csr5) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int phy_reg = readl(ioaddr + 0xB8);
kprintf(KERN_DEBUG "%s: PNIC link changed state %8.8x, CSR5 %8.8x.\n", dev->name, phy_reg, csr5);
if (readl(ioaddr + CSR5) & TPLnkFail) {
writel((readl(ioaddr + CSR7) & ~TPLnkFail) | TPLnkPass, ioaddr + CSR7);
if (!tp->nwayset || get_ticks() - tp->trans_start > 1 * HZ) {
tp->csr6 = 0x00420000 | (tp->csr6 & 0x0000fdff);
writel(tp->csr6, ioaddr + CSR6);
writel(0x30, ioaddr + CSR12);
writel(0x0201F078, ioaddr + 0xB8); // Turn on autonegotiation
tp->trans_start = get_ticks();
}
} else if (readl(ioaddr + CSR5) & TPLnkPass) {
pnic_do_nway(dev);
writel((readl(ioaddr + CSR7) & ~TPLnkPass) | TPLnkFail, ioaddr + CSR7);
}
}
static void pnic_timer(void *data) {
struct dev *dev = (struct dev *) data;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int next_tick = 60 * HZ;
if (media_cap[tp->if_port] & MediaIsMII) {
if (check_duplex(dev) > 0) next_tick = 3 * HZ;
} else {
int csr12 = readl(ioaddr + CSR12);
int new_csr6 = tp->csr6 & ~0x40C40200;
int phy_reg = readl(ioaddr + 0xB8);
int csr5 = readl(ioaddr + CSR5);
kprintf(KERN_DEBUG "%s: PNIC timer PHY status %8.8x, %s CSR5 %8.8x.\n", dev->name, phy_reg, medianame[tp->if_port], csr5);
if (phy_reg & 0x04000000) {
// Remote link fault
writel(0x0201F078, ioaddr + 0xB8);
next_tick = 1 * HZ;
tp->nwayset = 0;
} else if (phy_reg & 0x78000000) {
// Ignore baseT4
pnic_do_nway(dev);
next_tick = 60 * HZ;
} else if (csr5 & TPLnkFail) {
// 100baseTx link beat
kprintf(KERN_DEBUG "%s: %s link beat failed, CSR12 %4.4x, CSR5 %8.8x, PHY %3.3x.\n",
dev->name, medianame[tp->if_port], csr12,
readl(ioaddr + CSR5), readl(ioaddr + 0xB8));
next_tick = 3 * HZ;
if (tp->medialock) {
} else if (tp->nwayset && (tp->if_port & 1)) {
next_tick = 1 * HZ;
} else if (tp->if_port == 0) {
tp->if_port = 3;
writel(0x33, ioaddr + CSR12);
new_csr6 = 0x01860000;
writel(0x1F868, ioaddr + 0xB8);
} else {
tp->if_port = 0;
writel(0x32, ioaddr + CSR12);
new_csr6 = 0x00420000;
writel(0x1F078, ioaddr + 0xB8);
}
if (tp->csr6 != new_csr6) {
tp->csr6 = new_csr6;
writel(tp->csr6 | RxOn, ioaddr + CSR6); // Restart Tx
writel(tp->csr6 | RxOn | TxOn, ioaddr + CSR6);
tp->trans_start = get_ticks();
kprintf(KERN_INFO "%s: Changing PNIC configuration to %s %s-duplex, CSR6 %8.8x.\n",
dev->name, medianame[tp->if_port],
tp->full_duplex ? "full" : "half", new_csr6);
}
}
}
mod_timer(&tp->timer, get_ticks() + next_tick);
}
static void comet_timer(void *data) {
struct dev *dev = (struct dev *) data;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int next_tick = 60 * HZ;
kprintf(KERN_DEBUG "%s: Comet link status %4.4x partner capability %4.4x.\n",
dev->name, mdio_read(dev, tp->phys[0], 1),
mdio_read(dev, tp->phys[0], 5));
check_duplex(dev);
mod_timer(&tp->timer, get_ticks() + next_tick);
}
static void tulip_tx_timeout(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
if (media_cap[tp->if_port] & MediaIsMII) {
// Do nothing -- the media monitor should handle this
int mii_bmsr = mdio_read(dev, tp->phys[0], 1);
kprintf(KERN_WARNING "%s: Transmit timeout using MII device, status %4.4x.\n", dev->name, mii_bmsr);
if (!(mii_bmsr & 0x0004)) {
// No link beat present
tp->trans_start = get_ticks();
//netif_link_down(dev);
return;
}
} else {
switch (tp->chip_id) {
case DC21040:
if (!tp->medialock && readl(ioaddr + CSR12) & 0x0002) {
tp->if_port = (tp->if_port == 2 ? 0 : 2);
kprintf(KERN_INFO "%s: transmit timed out, switching to %s.\n", dev->name, medianame[tp->if_port]);
select_media(dev, 0);
}
tp->trans_start = get_ticks();
return; // Note: not break!
case DC21041: {
int csr12 = readl(ioaddr + CSR12);
kprintf(KERN_WARNING "%s: 21041 transmit timed out, status %8.8x, CSR12 %8.8x, CSR13 %8.8x, CSR14 %8.8x, resetting...\n",
dev->name, readl(ioaddr + CSR5), csr12,
readl(ioaddr + CSR13), readl(ioaddr + CSR14));
tp->mediasense = 1;
if (! tp->medialock) {
if (tp->if_port == 1 || tp->if_port == 2) {
tp->if_port = (csr12 & 0x0004) ? 2 - tp->if_port : 0;
} else {
tp->if_port = 1;
}
select_media(dev, 0);
}
break;
}
case DC21142:
if (tp->nwayset) {
kprintf(KERN_WARNING "%s: Transmit timed out, status %8.8x, SIA %8.8x %8.8x %8.8x %8.8x, restarting NWay .\n",
dev->name, (int)readl(ioaddr + CSR5),
readl(ioaddr + CSR12), readl(ioaddr + CSR13),
readl(ioaddr + CSR14), readl(ioaddr + CSR15));
nway_start(dev);
break;
}
// Fall through
case DC21140:
case MX98713:
case COMPEX9881:
kprintf(KERN_WARNING "%s: %s transmit timed out, status %8.8x, SIA %8.8x %8.8x %8.8x %8.8x, resetting...\n",
dev->name, tulip_tbl[tp->chip_id].chip_name,
readl(ioaddr + CSR5), readl(ioaddr + CSR12),
readl(ioaddr + CSR13), readl(ioaddr + CSR14),
readl(ioaddr + CSR15));
if (!tp->medialock && tp->mtable) {
do {
--tp->cur_index;
} while (tp->cur_index >= 0 && (media_cap[tp->mtable->mleaf[tp->cur_index].media] & MediaIsFD));
if (tp->cur_index < 0) {
// We start again, but should instead look for default
tp->cur_index = tp->mtable->leafcount - 1;
}
select_media(dev, 0);
kprintf(KERN_WARNING "%s: transmit timed out, switching to %s media.\n", dev->name, medianame[tp->if_port]);
}
break;
case PNIC2:
kprintf(KERN_WARNING "%s: PNIC2 transmit timed out, status %8.8x, CSR6/7 %8.8x / %8.8x CSR12 %8.8x, resetting...\n",
dev->name, readl(ioaddr + CSR5), readl(ioaddr + CSR6),
readl(ioaddr + CSR7), readl(ioaddr + CSR12));
break;
default:
kprintf(KERN_WARNING "%s: Transmit timed out, status %8.8x, CSR12 %8.8x, resetting...\n",
dev->name, readl(ioaddr + CSR5), readl(ioaddr + CSR12));
}
}
// Stop and restart the Tx process.
writel(tp->csr6 | RxOn, ioaddr + CSR6);
writel(tp->csr6 | RxOn | TxOn, ioaddr + CSR6);
// Trigger an immediate transmit demand
writel(0, ioaddr + CSR1);
writel(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7);
tp->trans_start = get_ticks();
tp->stats.tx_errors++;
}
// Initialize the Rx and Tx rings, along with various 'dev' bits
static void tulip_init_ring(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int i;
tp->rx_dead = tp->tx_full = 0;
tp->cur_rx = tp->cur_tx = 0;
tp->dirty_rx = tp->dirty_tx = 0;
for (i = 0; i < RX_RING_SIZE; i++) {
tp->rx_ring[i].status = 0x00000000;
tp->rx_ring[i].length = PKT_BUF_SZ;
tp->rx_ring[i].buffer2 = virt2phys(&tp->rx_ring[i + 1]);
tp->rx_pbuf[i] = NULL;
}
// Mark the last entry as wrapping the ring
tp->rx_ring[i - 1].length = PKT_BUF_SZ | DESC_RING_WRAP;
tp->rx_ring[i - 1].buffer2 = virt2phys(&tp->rx_ring[0]);
for (i = 0; i < RX_RING_SIZE; i++) {
// Note the receive buffer must be longword aligned.
// alloc_pbuf() provides the alignment.
struct pbuf *pbuf = pbuf_alloc(PBUF_RAW, PKT_BUF_SZ, PBUF_RW);
tp->rx_pbuf[i] = pbuf;
if (pbuf == NULL) break;
tp->rx_ring[i].status = DescOwned;
tp->rx_ring[i].buffer1 = virt2phys(pbuf->payload);
}
tp->dirty_rx = (unsigned int)(i - RX_RING_SIZE);
// The Tx buffer descriptor is filled in as needed, but we
// do need to clear the ownership bit
for (i = 0; i < TX_RING_SIZE; i++) {
tp->tx_pbuf[i] = 0;
tp->tx_ring[i].status = 0x00000000;
tp->tx_ring[i].buffer2 = virt2phys(&tp->tx_ring[i + 1]);
}
tp->tx_ring[i - 1].buffer2 = virt2phys(&tp->tx_ring[0]);
}
static int tulip_transmit(struct dev *dev, struct pbuf *p) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int entry, q_used_cnt;
unsigned long flag;
// Wait for free entry in transmit ring
if (wait_for_object(&tp->tx_sem, TX_TIMEOUT) < 0) {
kprintf(KERN_WARNING "%s: transmit timeout, drop packet\n", dev->name);
tp->stats.tx_dropped++;
return -ETIMEOUT;
}
// Make sure the packet buffer is not fragmented
p = pbuf_linearize(PBUF_RAW, p);
//kprintf(KERN_DEBUG "%s: Transmit packet, %d bytes\n", dev->name, p->len);
// Caution: the write order is important here, set the field with the ownership bits last
// Calculate the next Tx descriptor entry
entry = tp->cur_tx % TX_RING_SIZE;
q_used_cnt = tp->cur_tx - tp->dirty_tx;
tp->tx_pbuf[entry] = p;
tp->tx_ring[entry].buffer1 = virt2phys(p->payload);
if (q_used_cnt < TX_QUEUE_LEN / 2) {
// Typical path
flag = 0x60000000; // No interrupt
} else if (q_used_cnt == TX_QUEUE_LEN / 2) {
flag = 0xe0000000; // Tx-done intr
} else if (q_used_cnt < TX_QUEUE_LEN) {
flag = 0x60000000; // No Tx-done intr
} else {
// Leave room for set_rx_mode() to fill entries
tp->tx_full = 1;
flag = 0xe0000000; // Tx-done intr
}
if (entry == TX_RING_SIZE - 1) flag = 0xe0000000 | DESC_RING_WRAP;
tp->tx_ring[entry].length = p->len | flag;
tp->tx_ring[entry].status = DescOwned;
tp->cur_tx++;
tp->trans_start = get_ticks();
// Trigger an immediate transmit demand
writel(0, tp->iobase + CSR1);
return 0;
}
static int tulip_rx(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int entry = tp->cur_rx % RX_RING_SIZE;
int rx_work_limit = tp->dirty_rx + RX_RING_SIZE - tp->cur_rx;
int work_done = 0;
//kprintf(KERN_DEBUG " In tulip_rx(), entry %d %8.8x.\n", entry, tp->rx_ring[entry].status);
// If we own the next entry, it is a new packet. Send it up
while (!(tp->rx_ring[entry].status & DescOwned)) {
long status = tp->rx_ring[entry].status;
//kprintf(KERN_DEBUG "%s: In tulip_rx(), entry %d %8.8x.\n", dev->name, entry, status);
if (--rx_work_limit < 0) break;
if ((status & 0x38008300) != 0x0300) {
if ((status & 0x38000300) != 0x0300) {
// Ingore earlier buffers
if ((status & 0xffff) != 0x7fff) {
kprintf(KERN_WARNING "%s: Oversized Ethernet frame spanned multiple buffers, status %8.8x!\n", dev->name, status);
tp->stats.rx_length_errors++;
}
} else if (status & RxDescFatalErr) {
// There was a fatal error
kprintf(KERN_DEBUG "%s: Receive error, Rx status %8.8x.\n", dev->name, status);
tp->stats.rx_errors++; /* end of a packet.*/
if (status & 0x0890) tp->stats.rx_length_errors++;
if (status & 0x0004) tp->stats.rx_frame_errors++;
if (status & 0x0002) tp->stats.rx_crc_errors++;
if (status & 0x0001) tp->stats.rx_fifo_errors++;
}
} else {
// Omit the four octet CRC from the length
int pkt_len = ((status >> 16) & 0x7ff) - 4;
struct pbuf *p;
// Check if the packet is long enough to accept without copying
// to a minimally-sized packet buffer
if (pkt_len < rx_copybreak && (p = pbuf_alloc(PBUF_RAW, pkt_len, PBUF_RW)) != NULL) {
memcpy(p->payload, tp->rx_pbuf[entry]->payload, pkt_len);
work_done++;
} else {
// Pass up the packet buffer already on the Rx ring
p = tp->rx_pbuf[entry];
tp->rx_pbuf[entry] = NULL;
}
//kprintf(KERN_DEBUG "%s: Received packet, %d bytes\n", dev->name, pkt_len);
// Resize packet buffer
pbuf_realloc(p, pkt_len);
// Send packet to upper layer
if (dev_receive(tp->devno, p) < 0) pbuf_free(p);
tp->last_rx = get_ticks();
tp->stats.rx_packets++;
tp->stats.rx_bytes += pkt_len;
}
entry = (++tp->cur_rx) % RX_RING_SIZE;
}
// Refill the Rx ring buffers
for (; tp->cur_rx - tp->dirty_rx > 0; tp->dirty_rx++) {
entry = tp->dirty_rx % RX_RING_SIZE;
if (tp->rx_pbuf[entry] == NULL) {
struct pbuf *p;
p = tp->rx_pbuf[entry] = pbuf_alloc(PBUF_RAW, PKT_BUF_SZ, PBUF_RW);
if (p == NULL) {
if (tp->cur_rx - tp->dirty_rx == RX_RING_SIZE) kprintf(KERN_ERR "%s: No kernel memory to allocate receive buffers.\n", dev->name);
break;
}
tp->rx_ring[entry].buffer1 = virt2phys(p->payload);
work_done++;
}
tp->rx_ring[entry].status = DescOwned;
}
return work_done;
}
static void empty_rings(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
int i;
// Free all the buffers in the Rx queue
for (i = 0; i < RX_RING_SIZE; i++) {
struct pbuf *p = tp->rx_pbuf[i];
tp->rx_pbuf[i] = 0;
tp->rx_ring[i].status = 0; // Not owned by Tulip chip
tp->rx_ring[i].length = 0;
tp->rx_ring[i].buffer1 = 0xBADF00D0; // An invalid address
if (p) pbuf_free(p);
}
for (i = 0; i < TX_RING_SIZE; i++) {
if (tp->tx_pbuf[i]) pbuf_free(tp->tx_pbuf[i]);
tp->tx_pbuf[i] = 0;
}
}
static int tulip_close(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
//netif_stop_tx_queue(dev);
kprintf(KERN_DEBUG "%s: Shutting down ethercard, status was %2.2x.\n", dev->name, readl(ioaddr + CSR5));
// Disable interrupts by clearing the interrupt mask
writel(0x00000000, ioaddr + CSR7);
// Stop the Tx and Rx processes
writel(readl(ioaddr + CSR6) & ~TxOn & ~RxOn, ioaddr + CSR6);
// 21040 -- Leave the card in 10baseT state
if (tp->chip_id == DC21040) writel(0x00000004, ioaddr + CSR13);
if (readl(ioaddr + CSR6) != 0xffffffff) tp->stats.rx_missed_errors += readl(ioaddr + CSR8) & 0xffff;
del_timer(&tp->timer);
disable_irq(tp->irq);
tp->if_port = tp->saved_if_port;
empty_rings(dev);
// Leave the driver in snooze, not sleep, mode
if (tp->flags & HAS_PWRDWN) pci_write_config_dword(tp->pci_dev, 0x40, 0x40000000);
return 0;
}
static struct stats_nic *tulip_get_stats(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int csr8 = readl(ioaddr + CSR8);
if (csr8 != 0xffffffff) tp->stats.rx_missed_errors += (unsigned short) csr8;
return &tp->stats;
}
//
// Set or clear the multicast filter for this adaptor.
// Note that we only use exclusion around actually queueing the
// new frame, not around filling tp->setup_frame. This is non-deterministic
// when re-entered but still correct
//
// The little-endian AUTODIN32 ethernet CRC calculation.
// N.B. Do not use for bulk data, use a table-based routine instead.
static unsigned const ethernet_polynomial_le = 0xedb88320U;
static unsigned long ether_crc_le(int length, unsigned char *data) {
unsigned long crc = 0xffffffff;
while(--length >= 0) {
unsigned char current_octet = *data++;
int bit;
for (bit = 8; --bit >= 0; current_octet >>= 1) {
if ((crc ^ current_octet) & 1) {
crc >>= 1;
crc ^= ethernet_polynomial_le;
} else {
crc >>= 1;
}
}
}
return crc;
}
static int tulip_set_rx_mode(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int csr6 = readl(ioaddr + CSR6) & ~0x00D5;
tp->csr6 &= ~0x00D5;
if (!dev->netif) {
// Network interface not attached yet -- accept all multicasts
tp->csr6 |= AcceptAllMulticast;
csr6 |= AcceptAllMulticast;
} else if (dev->netif->flags & NETIF_PROMISC) {
// Set promiscuous
tp->csr6 |= AcceptAllMulticast | AcceptAllPhys;
csr6 |= AcceptAllMulticast | AcceptAllPhys;
// Unconditionally log net taps
kprintf(KERN_INFO "%s: Promiscuous mode enabled.\n", dev->name);
} else if (dev->netif->mccount > 1000 || (dev->netif->flags & NETIF_ALLMULTI)) {
// Too many to filter well -- accept all multicasts
tp->csr6 |= AcceptAllMulticast;
csr6 |= AcceptAllMulticast;
} else if (tp->flags & MC_HASH_ONLY) {
// Some work-alikes have only a 64-entry hash filter table
struct mclist *mclist;
int i;
if (dev->netif->mccount > multicast_filter_limit) {
tp->csr6 |= AcceptAllMulticast;
csr6 |= AcceptAllMulticast;
} else {
unsigned long mc_filter[2] = {0, 0}; // Multicast hash filter
int filterbit;
for (i = 0, mclist = dev->netif->mclist; mclist && i < dev->netif->mccount; i++, mclist = mclist->next) {
if (tp->flags & COMET_MAC_ADDR) {
filterbit = ether_crc_le(ETHER_ADDR_LEN, mclist->hwaddr.addr);
} else {
filterbit = ether_crc(ETHER_ADDR_LEN, mclist->hwaddr.addr) >> 26;
}
filterbit &= 0x3f;
set_bit(mc_filter, filterbit);
}
if (mc_filter[0] == tp->mc_filter[0] && mc_filter[1] == tp->mc_filter[1]) {
// No change
} else if (tp->flags & IS_ASIX) {
writel(2, ioaddr + CSR13);
writel(mc_filter[0], ioaddr + CSR14);
writel(3, ioaddr + CSR13);
writel(mc_filter[1], ioaddr + CSR14);
} else if (tp->flags & COMET_MAC_ADDR) {
writel(mc_filter[0], ioaddr + 0xAC);
writel(mc_filter[1], ioaddr + 0xB0);
}
tp->mc_filter[0] = mc_filter[0];
tp->mc_filter[1] = mc_filter[1];
}
} else {
unsigned short *eaddrs, *setup_frm = tp->setup_frame;
struct mclist *mclist;
unsigned long tx_flags = 0x08000000 | 192;
int i;
// Note that only the low-address shortword of setup_frame is valid!
// The values are doubled for big-endian architectures
if (dev->netif->mccount > 14) {
// Must use a multicast hash table
unsigned short hash_table[32];
tx_flags = 0x08400000 | 192; // Use hash filter
memset(hash_table, 0, sizeof(hash_table));
set_bit(hash_table, 255); // Broadcast entry
for (i = 0, mclist = dev->netif->mclist; mclist && i < dev->netif->mccount; i++, mclist = mclist->next) {
set_bit(hash_table, ether_crc_le(ETHER_ADDR_LEN, mclist->hwaddr.addr) & 0x1ff);
}
for (i = 0; i < 32; i++) {
*setup_frm++ = hash_table[i];
*setup_frm++ = hash_table[i];
}
setup_frm = &tp->setup_frame[13 * 6];
} else {
// We have <= 14 addresses so we can use the wonderful
// 16 address perfect filtering of the Tulip
for (i = 0, mclist = dev->netif->mclist; i < dev->netif->mccount; i++, mclist = mclist->next) {
eaddrs = (unsigned short *) mclist->hwaddr.addr;
*setup_frm++ = *eaddrs; *setup_frm++ = *eaddrs++;
*setup_frm++ = *eaddrs; *setup_frm++ = *eaddrs++;
*setup_frm++ = *eaddrs; *setup_frm++ = *eaddrs++;
}
// Fill the unused entries with the broadcast address
memset(setup_frm, 0xff, (15 - i) * 12);
setup_frm = &tp->setup_frame[15 * 6];
}
// Fill the final entry with our physical address
eaddrs = (unsigned short *) tp->hwaddr.addr;
*setup_frm++ = eaddrs[0]; *setup_frm++ = eaddrs[0];
*setup_frm++ = eaddrs[1]; *setup_frm++ = eaddrs[1];
*setup_frm++ = eaddrs[2]; *setup_frm++ = eaddrs[2];
// Now add this frame to the Tx list
if (tp->cur_tx - tp->dirty_tx > TX_RING_SIZE - 2) {
// Same setup recently queued, we need not add it
} else {
unsigned int entry;
entry = tp->cur_tx++ % TX_RING_SIZE;
if (entry != 0) {
// Avoid a chip errata by prefixing a dummy entry
tp->tx_pbuf[entry] = 0;
tp->tx_ring[entry].length = (entry == TX_RING_SIZE-1) ? DESC_RING_WRAP : 0;
tp->tx_ring[entry].buffer1 = 0;
tp->tx_ring[entry].status = DescOwned;
entry = tp->cur_tx++ % TX_RING_SIZE;
}
tp->tx_pbuf[entry] = 0;
// Put the setup frame on the Tx list
if (entry == TX_RING_SIZE - 1) tx_flags |= DESC_RING_WRAP; // Wrap ring
tp->tx_ring[entry].length = tx_flags;
tp->tx_ring[entry].buffer1 = virt2phys(tp->setup_frame);
tp->tx_ring[entry].status = DescOwned;
if (tp->cur_tx - tp->dirty_tx >= TX_RING_SIZE - 2) tp->tx_full = 1;
// Trigger an immediate transmit demand
writel(0, ioaddr + CSR1);
}
}
writel(csr6, ioaddr + CSR6);
return 0;
}
// The interrupt handler does all of the Rx thread work and cleans up
// after the Tx thread
static void tulip_dpc(void *arg) {
struct dev *dev = (struct dev *) arg;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int csr5, work_budget = max_interrupt_work;
while (1) {
csr5 = readl(ioaddr + CSR5);
if ((csr5 & (NormalIntr | AbnormalIntr)) == 0) break;
//kprintf(KERN_DEBUG "%s: interrupt csr5=%#8.8x new csr5=%#8.8x.\n", dev->name, csr5, readl(tp->iobase + CSR5));
// Acknowledge all of the current interrupt sources ASAP
writel(csr5 & 0x0001ffff, ioaddr + CSR5);
if (csr5 & (RxIntr | RxNoBuf)) work_budget -= tulip_rx(dev);
if (csr5 & (TxNoBuf | TxDied | TxIntr)) {
unsigned int dirty_tx;
for (dirty_tx = tp->dirty_tx; tp->cur_tx - dirty_tx > 0; dirty_tx++) {
int entry = dirty_tx % TX_RING_SIZE;
int status = tp->tx_ring[entry].status;
if (status < 0) break; // It still has not been Txed
// Check for Rx filter setup frames
if (tp->tx_pbuf[entry] == NULL) continue;
if (status & 0x8000) {
// There was an major error, log it
kprintf(KERN_DEBUG "%s: Transmit error, Tx status %8.8x.\n", dev->name, status);
tp->stats.tx_errors++;
if (status & 0x4104) tp->stats.tx_aborted_errors++;
if (status & 0x0C00) tp->stats.tx_carrier_errors++;
if (status & 0x0200) tp->stats.tx_window_errors++;
if (status & 0x0002) tp->stats.tx_fifo_errors++;
if ((status & 0x0080) && tp->full_duplex == 0) tp->stats.tx_heartbeat_errors++;
if (status & 0x0100) tp->stats.collisions++;
} else {
//kprintf(KERN_DEBUG "%s: Transmit complete, status %8.8x.\n", dev->name, status);
//if (status & 0x0001) tp->stats.tx_deferred++;
tp->stats.tx_bytes += tp->tx_pbuf[entry]->len;
tp->stats.collisions += (status >> 3) & 15;
tp->stats.tx_packets++;
}
// Free the original packet buffer
pbuf_free(tp->tx_pbuf[entry]);
tp->tx_pbuf[entry] = 0;
release_sem(&tp->tx_sem, 1);
}
if (tp->tx_full && tp->cur_tx - dirty_tx < TX_QUEUE_LEN - 4) {
// The ring is no longer full, clear tbusy
tp->tx_full = 0;
}
tp->dirty_tx = dirty_tx;
}
if (tp->rx_dead) {
tulip_rx(dev);
if (tp->cur_rx - tp->dirty_rx < RX_RING_SIZE - 3) {
kprintf(KERN_ERR "%s: Restarted Rx at %d / %d.\n", dev->name, tp->cur_rx, tp->dirty_rx);
writel(0, ioaddr + CSR2); // Rx poll demand
tp->rx_dead = 0;
}
}
// Log errors
if (csr5 & AbnormalIntr) {
// Abnormal error summary bit
if (csr5 == 0xffffffff) break;
if (csr5 & TxJabber) tp->stats.tx_errors++;
if (csr5 & PCIBusError) kprintf(KERN_ERR "%s: PCI Fatal Bus Error, %8.8x.\n", dev->name, csr5);
if (csr5 & TxFIFOUnderflow) {
if ((tp->csr6 & 0xC000) != 0xC000) {
tp->csr6 += 0x4000; // Bump up the Tx threshold
} else {
tp->csr6 |= 0x00200000; // Store-n-forward
}
kprintf(KERN_WARNING "%s: Tx threshold increased, new CSR6 %x.\n", dev->name, tp->csr6);
}
if (csr5 & TxDied) {
// This is normal when changing Tx modes
kprintf(KERN_WARNING "%s: The transmitter stopped. CSR5 is %x, CSR6 %x, new CSR6 %x.\n",
dev->name, csr5, readl(ioaddr + CSR6), tp->csr6);
}
if (csr5 & (TxDied | TxFIFOUnderflow | PCIBusError)) {
// Restart the transmit process
writel(tp->csr6 | RxOn, ioaddr + CSR6);
writel(tp->csr6 | RxOn | TxOn, ioaddr + CSR6);
}
if (csr5 & (RxStopped | RxNoBuf)) {
// Missed a Rx frame or mode change
tp->stats.rx_missed_errors += readl(ioaddr + CSR8) & 0xffff;
if (tp->flags & COMET_MAC_ADDR) {
writel(tp->mc_filter[0], ioaddr + 0xAC);
writel(tp->mc_filter[1], ioaddr + 0xB0);
}
tulip_rx(dev);
if (csr5 & RxNoBuf) tp->rx_dead = 1;
writel(tp->csr6 | RxOn | TxOn, ioaddr + CSR6);
}
if (csr5 & TimerInt) {
kprintf(KERN_ERR "%s: Re-enabling interrupts, %8.8x.\n", dev->name, csr5);
writel(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7);
}
if (csr5 & (TPLnkPass | TPLnkFail | 0x08000000)) {
if (tp->link_change) (tp->link_change)(dev, csr5);
}
// Clear all error sources, included undocumented ones!
writel(0x0800f7ba, ioaddr + CSR5);
}
if (--work_budget < 0) {
kprintf(KERN_WARNING "%s: Too much work during an interrupt, csr5=0x%8.8x.\n", dev->name, csr5);
// Acknowledge all interrupt sources
writel(0x8001ffff, ioaddr + CSR5);
if (tp->flags & HAS_INTR_MITIGATION) {
// Josip Loncaric at ICASE did extensive experimentation
// to develop a good interrupt mitigation setting
writel(0x8b240000, ioaddr + CSR11);
} else {
// Mask all interrupting sources, set timer to re-enable
writel(((~csr5) & 0x0001ebef) | AbnormalIntr | TimerInt, ioaddr + CSR7);
writel(0x0012, ioaddr + CSR11);
}
break;
}
}
//kprintf(KERN_DEBUG "%s: exiting interrupt, csr5=%#4.4x.\n", dev->name, readl(ioaddr + CSR5));
eoi(tp->irq);
}
static int tulip_handler(struct context *ctxt, void *arg) {
struct dev *dev = (struct dev *) arg;
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
// Queue DPC to service interrupt
//kprintf("%s: interrupt\n", dev->name);
queue_irq_dpc(&tp->dpc, tulip_dpc, dev);
return 0;
}
static int tulip_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
return -ENOSYS;
}
static int tulip_attach(struct dev *dev, struct eth_addr *hwaddr) {
struct tulip_private *tp = dev->privdata;
*hwaddr = tp->hwaddr;
return 0;
}
static int tulip_detach(struct dev *dev) {
return 0;
}
struct driver tulip_driver = {
"tulip",
DEV_TYPE_PACKET,
tulip_ioctl,
NULL,
NULL,
tulip_attach,
tulip_detach,
tulip_transmit,
tulip_set_rx_mode
};
static struct dev *tulip_probe(struct unit *pdev, struct board *board, int ioaddr, char *opts) {
struct dev *dev;
struct tulip_private *tp;
// See note below on the multiport cards.
static unsigned char last_phys_addr[6] = {0x00, 'S', 'a', 'n', 'o', 's'};
static int last_irq = 0;
static int multiport_cnt = 0; // For four-port boards w/one EEPROM
unsigned char chip_rev;
int i;
unsigned short sum;
unsigned char ee_data[EEPROM_SIZE];
int chip_idx = board->flags & 0xff;
int irq = get_unit_irq(pdev);
// Bring the 21041/21143 out of sleep mode. Caution: Snooze mode does not work with some boards!
if (tulip_tbl[chip_idx].flags & HAS_PWRDWN) pci_write_config_dword(pdev, 0x40, 0x00000000);
if (readl(ioaddr + CSR5) == 0xffffffff) {
kprintf(KERN_ERR "The Tulip chip at %#lx is not functioning.\n", ioaddr);
return 0;
}
// Allocate private data structures (quadword aligned)
tp = kmalloc(sizeof(*tp));
// Check for the very unlikely case of no memory
if (tp == NULL) return NULL;
memset(tp, 0, sizeof(*tp));
// Create new device
tp->devno = dev_make("eth#", &tulip_driver, pdev, tp);
if (tp->devno == NODEV) return NULL;
dev = device(tp->devno);
tp->next_module = root_tulip_dev;
root_tulip_dev = dev;
chip_rev = pci_read_config_byte(pdev, 0x08 /*PCI_REVISION_ID*/);
kprintf(KERN_INFO "%s: %s rev %d at %#3lx,", dev->name, board->productname, chip_rev, ioaddr);
// Stop the Tx and Rx processes
writel(readl(ioaddr + CSR6) & ~TxOn & ~RxOn, ioaddr + CSR6);
// Clear the missed-packet counter
readl(ioaddr + CSR8);
if (chip_idx == DC21041 && readl(ioaddr + CSR9) & 0x8000) {
kprintf(" 21040 compatible mode,");
chip_idx = DC21040;
}
// The SROM/EEPROM interface varies dramatically
sum = 0;
if (chip_idx == DC21040) {
writel(0, ioaddr + CSR9); // Reset the pointer with a dummy write
for (i = 0; i < 6; i++) {
int value, boguscnt = 100000;
do {
value = readl(ioaddr + CSR9);
} while (value < 0 && --boguscnt > 0);
tp->hwaddr.addr[i] = value;
sum += value & 0xff;
}
} else if (chip_idx == LC82C168) {
for (i = 0; i < 3; i++) {
int value, boguscnt = 100000;
writel(0x600 | i, ioaddr + 0x98);
do {
value = readl(ioaddr + CSR9);
} while (value < 0 && --boguscnt > 0);
((unsigned short *) tp->hwaddr.addr)[i] = value;
sum += value & 0xffff;
}
} else if (chip_idx == COMET) {
// No need to read the EEPROM
*(unsigned long *) (tp->hwaddr.addr) = readl(ioaddr + 0xA4);
*(unsigned short *) (tp->hwaddr.addr + 4) = readl(ioaddr + 0xA8);
for (i = 0; i < 6; i++) sum += tp->hwaddr.addr[i];
} else {
// A serial EEPROM interface, we read now and sort it out later
int sa_offset = 0;
int ee_addr_size = read_eeprom(ioaddr, 0xff, 8) & 0x40000 ? 8 : 6;
int eeprom_word_cnt = 1 << ee_addr_size;
for (i = 0; i < eeprom_word_cnt; i++) {
((unsigned short *) ee_data)[i] = read_eeprom(ioaddr, i, ee_addr_size);
}
// DEC now has a specification (see Notes) but early board makers
// just put the address in the first EEPROM locations
// This does memcmp(eedata, eedata + 16, 8)
for (i = 0; i < 8; i ++) {
if (ee_data[i] != ee_data[16 + i]) sa_offset = 20;
}
if (chip_idx == CONEXANT) {
// Check that the tuple type and length is correct
if (ee_data[0x198] == 0x04 && ee_data[0x199] == 6) sa_offset = 0x19A;
} else if (ee_data[0] == 0xff && ee_data[1] == 0xff && ee_data[2] == 0) {
sa_offset = 2; // Grrr, damn Matrox boards
multiport_cnt = 4;
}
for (i = 0; i < 6; i ++) {
tp->hwaddr.addr[i] = ee_data[i + sa_offset];
sum += ee_data[i + sa_offset];
}
}
// Lite-On boards have the address byte-swapped
if ((tp->hwaddr.addr[0] == 0xA0 || tp->hwaddr.addr[0] == 0xC0) && tp->hwaddr.addr[1] == 0x00) {
for (i = 0; i < 6; i += 2) {
unsigned char tmp = tp->hwaddr.addr[i];
tp->hwaddr.addr[i] = tp->hwaddr.addr[i + 1];
tp->hwaddr.addr[i + 1] = tmp;
}
}
// On the Zynx 315 Etherarray and other multiport boards only the
// first Tulip has an EEPROM.
// The addresses of the subsequent ports are derived from the first.
// Many PCI BIOSes also incorrectly report the IRQ line, so we correct
// that here as well
if (sum == 0 || sum == 6 * 0xff) {
kprintf(" EEPROM not present,");
for (i = 0; i < 5; i++) tp->hwaddr.addr[i] = last_phys_addr[i];
tp->hwaddr.addr[i] = last_phys_addr[i] + 1;
// Patch up x86 BIOS bug
if (last_irq) irq = last_irq;
}
for (i = 0; i < 6; i++) kprintf("%c%2.2X", i ? ':' : ' ', last_phys_addr[i] = tp->hwaddr.addr[i]);
kprintf(", IRQ %d.\n", irq);
last_irq = irq;
// We do a request_region() to register /proc/ioports info
//request_region(ioaddr, pci_id_tbl[chip_idx].io_size, dev->name);
tp->iobase = ioaddr;
tp->irq = irq;
tp->pci_dev = pdev;
tp->chip_id = chip_idx;
tp->revision = chip_rev;
tp->flags = tulip_tbl[chip_idx].flags | (board->flags & 0xffffff00);
tp->csr0 = csr0;
// BugFixes: The 21143-TD hangs with PCI Write-and-Invalidate cycles.
// And the ASIX must have a burst limit or horrible things happen
if (chip_idx == DC21143 && chip_rev == 65) {
tp->csr0 &= ~0x01000000;
} else if (tp->flags & IS_ASIX) {
tp->csr0 |= 0x2000;
}
// We support a zillion ways to set the media type
#ifdef TULIP_FULL_DUPLEX
tp->full_duplex = 1;
tp->full_duplex_lock = 1;
#endif
#ifdef TULIP_DEFAULT_MEDIA
tp->default_port = TULIP_DEFAULT_MEDIA;
#endif
#ifdef TULIP_NO_MEDIA_SWITCH
tp->medialock = 1;
#endif
// Set options
if (opts) {
tp->default_port = get_num_option(opts, "defaultport", tp->default_port);
tp->full_duplex = get_num_option(opts, "fullduplex", tp->full_duplex);
//tp->mtu = get_num_option(opts, "mtu", tp->mtu);
}
// ???
//if (dev->mem_start) tp->default_port = dev->mem_start & 0x1f;
if (tp->default_port) {
kprintf(KERN_INFO "%s: Transceiver selection forced to %s.\n", dev->name, medianame[tp->default_port & MEDIA_MASK]);
tp->medialock = 1;
if (media_cap[tp->default_port] & MediaAlwaysFD) tp->full_duplex = 1;
}
if (tp->full_duplex) tp->full_duplex_lock = 1;
if (media_cap[tp->default_port] & MediaIsMII) {
unsigned short media2advert[] = {0x20, 0x40, 0x03e0, 0x60, 0x80, 0x100, 0x200};
tp->mii_advertise = media2advert[tp->default_port - 9];
tp->mii_advertise |= (tp->flags & HAS_8023X); // Matching bits!
}
if (tp->flags & HAS_MEDIA_TABLE) {
memcpy(tp->eeprom, ee_data, sizeof(tp->eeprom));
parse_eeprom(dev);
}
if (tp->flags & HAS_NWAY) {
tp->link_change = nway_lnk_change;
} else if (tp->flags & HAS_PNICNWAY) {
tp->link_change = pnic_lnk_change;
}
start_link(dev);
if (chip_idx == COMET) {
// Set the Comet LED configuration
writel(0xf0000000, ioaddr + CSR9);
}
return dev;
}
static int tulip_open(struct dev *dev) {
struct tulip_private *tp = (struct tulip_private *) dev->privdata;
long ioaddr = tp->iobase;
int next_tick = 3*HZ;
// Wake the chip from sleep/snooze mode
if (tp->flags & HAS_PWRDWN) pci_write_config_dword(tp->pci_dev, 0x40, 0);
// On some chip revs we must set the MII/SYM port before the reset!?
if (tp->mii_cnt || (tp->mtable && tp->mtable->has_mii)) writel(0x00040000, ioaddr + CSR6);
// Reset the chip, holding bit 0 set at least 50 PCI cycles
writel(0x00000001, ioaddr + CSR0);
// This would be done after interrupts are initialized, but we do not want
// to frob the transceiver only to fail later
init_dpc(&tp->dpc);
register_interrupt(&tp->intr, IRQ2INTR(tp->irq), tulip_handler, dev);
enable_irq(tp->irq);
// Deassert reset.
// Wait the specified 50 PCI cycles after a reset by initializing
// Tx and Rx queues and the address filter list
writel(tp->csr0, ioaddr + CSR0);
//kprintf(KERN_DEBUG "%s: tulip_open() irq %d.\n", dev->name, tp->irq);
tulip_init_ring(dev);
init_sem(&tp->tx_sem, TX_RING_SIZE);
if (tp->chip_id == PNIC2) {
unsigned long addr_high = (tp->hwaddr.addr[1] << 8) + (tp->hwaddr.addr[0] << 0);
// This address setting does not appear to impact chip operation??
writel((tp->hwaddr.addr[5] << 8) + tp->hwaddr.addr[4] +
(tp->hwaddr.addr[3] << 24) + (tp->hwaddr.addr[2] << 16),
ioaddr + 0xB0);
writel(addr_high + (addr_high << 16), ioaddr + 0xB8);
}
if (tp->flags & MC_HASH_ONLY) {
unsigned long addr_low = *(unsigned long *) tp->hwaddr.addr;
unsigned long addr_high = *(unsigned short *) (tp->hwaddr.addr + 4);
if (tp->flags & IS_ASIX) {
writel(0, ioaddr + CSR13);
writel(addr_low, ioaddr + CSR14);
writel(1, ioaddr + CSR13);
writel(addr_high, ioaddr + CSR14);
} else if (tp->flags & COMET_MAC_ADDR) {
writel(addr_low, ioaddr + 0xA4);
writel(addr_high, ioaddr + 0xA8);
writel(0, ioaddr + 0xAC);
writel(0, ioaddr + 0xB0);
}
}
writel(virt2phys(tp->rx_ring), ioaddr + CSR3);
writel(virt2phys(tp->tx_ring), ioaddr + CSR4);
if (!tp->full_duplex_lock) tp->full_duplex = 0;
init_media(dev);
if (media_cap[tp->if_port] & MediaIsMII) check_duplex(dev);
tulip_set_rx_mode(dev);
// Start the Tx to process setup frame
writel(tp->csr6, ioaddr + CSR6);
writel(tp->csr6 | TxOn, ioaddr + CSR6);
// Enable interrupts by setting the interrupt mask
writel(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR5);
writel(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7);
writel(tp->csr6 | TxOn | RxOn, ioaddr + CSR6);
writel(0, ioaddr + CSR2); // Rx poll demand
// Set the timer to switch to check for link beat and perhaps switch
// to an alternate media type
init_timer(&tp->timer, tulip_tbl[tp->chip_id].media_timer, dev);
mod_timer(&tp->timer, get_ticks() + next_tick);
return 0;
}
int __declspec(dllexport) install(struct unit *unit, char *opts) {
struct board *board;
struct dev *dev;
int ioaddr;
#ifndef USE_IO_OPS
struct resource *memres;
#endif
// Check license
if (license() != LICENSE_GPL) kprintf(KERN_WARNING "notice: tulip driver is under GPL license\n");
// Check for PCI device
if (unit->bus->bustype != BUSTYPE_PCI) return -EINVAL;
// Determine NIC type
board = lookup_board(board_tbl, unit);
if (!board) return -EIO;
unit->vendorname = board->vendorname;
unit->productname = board->productname;
// Determine I/O address
#ifdef USE_IO_OPS
ioaddr = get_unit_iobase(unit);
#else
memres = get_unit_resource(unit, RESOURCE_MEM, 0);
ioaddr = (int) iomap(memres->start, memres->len);
#endif
// Enable bus mastering
pci_enable_busmastering(unit);
// Probe device
dev = tulip_probe(unit, board, ioaddr, opts);
if (!dev) return -ENODEV;
return tulip_open(dev);
}
int __stdcall start(hmodule_t hmod, int reason, void *reserved2) {
return 1;
}