summaryrefslogtreecommitdiffstats
path: root/drivers/net/ncr885e.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ncr885e.c')
-rw-r--r--drivers/net/ncr885e.c1458
1 files changed, 1458 insertions, 0 deletions
diff --git a/drivers/net/ncr885e.c b/drivers/net/ncr885e.c
new file mode 100644
index 000000000..277f92520
--- /dev/null
+++ b/drivers/net/ncr885e.c
@@ -0,0 +1,1458 @@
+/*
+ * An Ethernet driver for the dual-function NCR 53C885 SCSI/Ethernet
+ * controller.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+static const char *version =
+"ncr885e.c:v0.8 11/30/98 dan@synergymicro.com\n";
+
+#include <linux/config.h>
+
+#ifdef MODULE
+#ifdef MODVERSIONS
+#include <linux/modversions.h>
+#endif
+#include <linux/module.h>
+#include <linux/version.h>
+#else
+#define MOD_INC_USE_COUNT
+#define MOD_DEC_USE_COUNT
+#endif
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/malloc.h>
+#include <linux/netdevice.h>
+#include <linux/pci.h>
+#include <linux/malloc.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/dbdma.h>
+#include <asm/uaccess.h>
+
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+
+#include "ncr885e.h"
+#include "ncr885_debug.h"
+
+static const char *chipname = "ncr885e";
+
+/* debugging flags */
+#if 0
+#define DEBUG_FUNC 0x0001
+#define DEBUG_PACKET 0x0002
+#define DEBUG_CMD 0x0004
+#define DEBUG_CHANNEL 0x0008
+#define DEBUG_INT 0x0010
+#define DEBUG_RX 0x0020
+#define DEBUG_TX 0x0040
+#define DEBUG_DMA 0x0080
+#define DEBUG_MAC 0x0100
+#define DEBUG_DRIVER 0x0200
+#define DEBUG_ALL 0x1fff
+#endif
+
+#ifdef DEBUG_NCR885E
+#define NCR885E_DEBUG 0
+#else
+#define NCR885E_DEBUG 0
+#endif
+
+/* The 885's Ethernet PCI device id. */
+#ifndef PCI_DEVICE_ID_NCR_53C885_ETHERNET
+#define PCI_DEVICE_ID_NCR_53C885_ETHERNET 0x0701
+#endif
+
+#define NR_RX_RING 8
+#define NR_TX_RING 8
+#define MAX_TX_ACTIVE (NR_TX_RING-1)
+#define NCMDS_TX NR_TX_RING
+
+#define RX_BUFLEN (ETH_FRAME_LEN + 8)
+#define TX_TIMEOUT 5*HZ
+
+#define NCR885E_TOTAL_SIZE 0xe0
+
+#define TXSR (1<<6) /* tx: xfer status written */
+#define TXABORT (1<<7) /* tx: abort */
+#define EOP (1<<7) /* rx: end of packet written to buffer */
+
+int ncr885e_debug = NCR885E_DEBUG;
+static int print_version = 0;
+
+struct ncr885e_private {
+
+ /* preserve a 1-1 marking with buffs */
+ struct dbdma_cmd *head;
+ struct dbdma_cmd *tx_cmds;
+ struct dbdma_cmd *rx_cmds;
+ struct dbdma_cmd *stop_cmd;
+
+ struct sk_buff *tx_skbufs[NR_TX_RING];
+ struct sk_buff *rx_skbufs[NR_RX_RING];
+
+ int rx_current;
+ int rx_dirty;
+
+ int tx_dirty;
+ int tx_current;
+
+ unsigned short tx_status[NR_TX_RING];
+
+ unsigned char tx_fullup;
+ unsigned char tx_active;
+
+ struct net_device_stats stats;
+
+ struct net_device *dev;
+
+ struct timer_list tx_timeout;
+ int timeout_active;
+
+ spinlock_t lock;
+};
+
+#ifdef MODULE
+static struct net_device *root_dev = NULL;
+#endif
+
+
+static int ncr885e_open( struct net_device *dev );
+static int ncr885e_close( struct net_device *dev );
+static void ncr885e_rx( struct net_device *dev );
+static void ncr885e_tx( struct net_device *dev );
+static int ncr885e_probe1( struct net_device *dev, unsigned long ioaddr,
+ unsigned char irq );
+static int ncr885e_xmit_start( struct sk_buff *skb, struct net_device *dev );
+static struct net_device_stats *ncr885e_stats( struct net_device *dev );
+static void ncr885e_set_multicast( struct net_device *dev );
+static void ncr885e_config( struct net_device *dev );
+static int ncr885e_set_address( struct net_device *dev, void *addr );
+static void ncr885e_interrupt( int irq, void *dev_id, struct pt_regs *regs );
+static void show_dbdma_cmd( volatile struct dbdma_cmd *cmd );
+#if 0
+static int read_eeprom( unsigned int ioadddr, int location );
+#endif
+
+#ifdef NCR885E_DEBUG_MII
+static void show_mii( unsigned long ioaddr );
+static int read_mii( unsigned long ioaddr, int reg );
+static void write_mii( unsigned long ioaddr, int reg, int data );
+#endif /* NCR885E_DEBUG_MII */
+
+#define TX_RESET_FLAGS (TX_CHANNEL_RUN|TX_CHANNEL_PAUSE|TX_CHANNEL_WAKE)
+#define RX_RESET_FLAGS (RX_CHANNEL_RUN|RX_CHANNEL_PAUSE|RX_CHANNEL_WAKE)
+
+
+#if 0
+static int
+debug_ioctl( struct net_device *dev, struct ifreq *req, int cmd )
+{
+ unsigned long ioaddr = dev->base_addr;
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ struct ncr885e_private *data;
+ struct ncr885e_regs *regs;
+ unsigned long flags;
+
+ union {
+ struct ncr885e_regs dump;
+ struct ncr885e_private priv;
+ } temp;
+
+ switch( cmd ) {
+
+ /* dump the rx ring status */
+ case NCR885E_GET_PRIV:
+
+ data = (struct ncr885e_private *) &req->ifr_data;
+
+ if ( verify_area(VERIFY_WRITE, &req->ifr_data,
+ sizeof( struct ncr885e_private )))
+ return -EFAULT;
+
+ memcpy((char *) &temp.priv, sp, sizeof( struct ncr885e_private ));
+ copy_to_user( data, (char *) &temp.priv, sizeof( struct ncr885e_private));
+ break;
+
+ case NCR885E_GET_REGS:
+
+ regs = (struct ncr885e_regs *) &req->ifr_data;
+
+ if ( verify_area( VERIFY_WRITE, &req->ifr_data,
+ sizeof( struct ncr885e_regs )))
+ return -EFAULT;
+
+ spin_lock_irqsave( &sp->lock, flags );
+
+ temp.dump.tx_status = inl( ioaddr + TX_CHANNEL_STATUS );
+ temp.dump.rx_status = inl( ioaddr + RX_CHANNEL_STATUS );
+ temp.dump.mac_config = inl( ioaddr + MAC_CONFIG );
+ temp.dump.tx_control = inl( ioaddr + TX_CHANNEL_CONTROL );
+ temp.dump.rx_control = inl( ioaddr + RX_CHANNEL_CONTROL );
+ temp.dump.tx_cmd_ptr = inl( ioaddr + TX_CMD_PTR_LO );
+ temp.dump.rx_cmd_ptr = inl( ioaddr + RX_CMD_PTR_LO );
+ temp.dump.int_status = inl( ioaddr + INTERRUPT_STATUS_REG );
+
+ spin_unlock_irqrestore( &sp->lock, flags );
+ copy_to_user( regs, (char *) &temp.dump, sizeof( struct ncr885e_regs ));
+
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+#endif
+
+/* Enable interrupts on the 53C885 */
+static inline void
+ncr885e_enable( struct net_device *dev )
+
+{
+ unsigned long ioaddr = dev->base_addr;
+ unsigned short reg;
+
+ reg = inw(ioaddr + INTERRUPT_ENABLE);
+ outw(reg | INTERRUPT_INTE, ioaddr + INTERRUPT_ENABLE);
+}
+
+/* Disable interrupts on the 53c885 */
+static inline void
+ncr885e_disable( struct net_device *dev )
+
+{
+ unsigned long ioaddr = dev->base_addr;
+ unsigned short reg;
+
+ reg = inw( ioaddr + INTERRUPT_ENABLE );
+ outw( reg & ~INTERRUPT_INTE, ioaddr + INTERRUPT_ENABLE );
+}
+
+
+static inline void
+ncr885e_reset( struct net_device *dev )
+
+{
+ unsigned short reg;
+ unsigned long cntl;
+ int i;
+ unsigned long ioaddr = dev->base_addr;
+
+ if (ncr885e_debug > 1)
+ printk( KERN_INFO "%s: Resetting 53C885...\n", dev->name );
+
+ /* disable interrupts on the 53C885 */
+ ncr885e_disable( dev );
+
+ /* disable rx in the MAC */
+ reg = inw( ioaddr + MAC_CONFIG );
+ outw( reg & ~MAC_CONFIG_RXEN, ioaddr + MAC_CONFIG );
+
+ for( i=0; i < 100; i++ ) {
+
+ if ( !(inw( ioaddr + MAC_CONFIG ) & MAC_CONFIG_RXEN ))
+ break;
+ udelay( 10 );
+ }
+
+ reg = inw( ioaddr + MAC_CONFIG );
+ outw( reg | MAC_CONFIG_SRST, ioaddr + MAC_CONFIG );
+ outw( reg, ioaddr + MAC_CONFIG );
+
+ /* disable both rx and tx DBDMA channels */
+ outl( TX_DBDMA_ENABLE << 16, ioaddr + TX_CHANNEL_CONTROL );
+ outl( RX_DBDMA_ENABLE << 16, ioaddr + RX_CHANNEL_CONTROL );
+
+ for( i=0; i < 100; i++ ) {
+
+ if ( !(inw( ioaddr + TX_CHANNEL_STATUS ) & TX_DBDMA_ENABLE ) &&
+ !(inw( ioaddr + RX_CHANNEL_STATUS ) & RX_DBDMA_ENABLE ))
+ break;
+ udelay( 10 );
+ }
+
+ /* perform a "software reset" */
+ cntl = inl( ioaddr + DBDMA_CONTROL );
+ outl( cntl | DBDMA_SRST, ioaddr + DBDMA_CONTROL );
+
+ for( i=0; i < 100; i++ ) {
+
+ if ( !(inl( ioaddr + DBDMA_CONTROL ) & DBDMA_SRST ))
+ break;
+ udelay( 10 );
+ }
+
+ /* books says that a software reset should be done to the MAC, as
+ well. This true??? */
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: reset complete\n", dev->name );
+
+}
+
+
+/* configure the 53C885 chip.
+
+ The DBDMA command descriptors on the 53C885 can be programmed to
+ branch, interrupt or pause conditionally or always by using the
+ interrupt, branch and wait select registers. */
+
+static void
+ncr885e_config( struct net_device *dev )
+
+{
+ unsigned long ioaddr = dev->base_addr;
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: Configuring 53C885.\n", dev->name );
+
+ ncr885e_reset( dev );
+
+ /* The 53C885 can be programmed to perform conditional DBDMA
+ branches, interrupts or waits.
+
+ Neither channel makes use of "wait", as it requires that the
+ DBDMA engine to be restarted. Don't go there. The rx channel
+ will branch upon the successful reception of a packet ('EOP' in
+ the xfer_status field). The branch address is to the STOP
+ DBDMA command descriptor, which shuts down the rx channel until
+ the interrupt is serviced. */
+
+ /* cause tx channel to stop after "status received" */
+ outl( 0, ioaddr + TX_INT_SELECT );
+ outl( (TX_WAIT_STAT_RECV << 16) | TX_WAIT_STAT_RECV,
+ ioaddr + TX_WAIT_SELECT );
+ outl( 0, ioaddr + TX_BRANCH_SELECT );
+
+ /* cause rx channel to branch to the STOP descriptor on "End-of-Packet" */
+#if 0
+ outl( (RX_INT_SELECT_EOP << 16) | RX_INT_SELECT_EOP,
+ ioaddr + RX_INT_SELECT );
+#else
+ outl( 0, ioaddr + RX_INT_SELECT );
+#endif
+#if 0
+ outl( 0, ioaddr + RX_WAIT_SELECT );
+#else
+ outl( (RX_WAIT_SELECT_EOP << 16) | RX_WAIT_SELECT_EOP,
+ ioaddr + RX_WAIT_SELECT );
+#endif
+#if 1
+ outl( 0, ioaddr + RX_BRANCH_SELECT );
+#else
+ outl( (RX_BRANCH_SELECT_EOP << 16) | RX_BRANCH_SELECT_EOP,
+ ioaddr + RX_BRANCH_SELECT );
+#endif
+
+ /* configure DBDMA */
+ outl( (DBDMA_BE | DBDMA_DPMRLE | DBDMA_TDPCE |
+ DBDMA_DDPE | DBDMA_TDPE |
+ (DBDMA_BURST_4 << DBDMA_TX_BST_SHIFT) |
+ (DBDMA_BURST_4 << DBDMA_RX_BST_SHIFT) |
+ (DBDMA_TX_ARBITRATION_DEFAULT) |
+ (DBDMA_RX_ARBITRATION_DEFAULT)), ioaddr + DBDMA_CONTROL );
+
+ outl( 0, ioaddr + TX_THRESHOLD );
+
+ /* disable MAC loopback */
+ outl( (MAC_CONFIG_ITXA | MAC_CONFIG_RXEN | MAC_CONFIG_RETRYL |
+ MAC_CONFIG_PADEN | (0x18 << 16)),
+ ioaddr + MAC_CONFIG );
+
+ /* configure MAC */
+ outl( (MAC_CONFIG_ITXA | MAC_CONFIG_RXEN | MAC_CONFIG_RETRYL |
+ MAC_CONFIG_PADEN | ( 0x18 << 16)), ioaddr + MAC_CONFIG );
+
+ outw( (0x1018), ioaddr + NBTOB_INTP_GAP );
+
+ /* clear and enable interrupts */
+ inw( ioaddr + INTERRUPT_CLEAR );
+ ncr885e_enable( dev );
+
+ /* and enable them in the chip */
+ outl( (INTERRUPT_INTE|INTERRUPT_TX_MASK|INTERRUPT_RX_MASK)<<16,
+ ioaddr + INTERRUPT_ENABLE - 2);
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: 53C885 config complete.\n", dev->name );
+
+ return;
+}
+
+
+
+/*
+ transmit interrupt */
+
+static void
+ncr885e_tx( struct net_device *dev )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ volatile struct dbdma_cmd *cp, *dp;
+ unsigned short txbits, xfer;
+ int i;
+
+ del_timer( &sp->tx_timeout );
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: ncr885e_tx: active=%d, dirty=%d, current=%d\n",
+ dev->name, sp->tx_active, sp->tx_dirty, sp->tx_current );
+
+ sp->timeout_active = 0;
+
+ i = sp->tx_dirty;
+ cp = sp->tx_cmds + (i*3);
+ dp = cp+1;
+ sp->tx_active--;
+
+ xfer = inw( &dp->xfer_status );
+ txbits = inw( &sp->tx_status[i] );
+
+ if (ncr885e_debug > 4) {
+ show_dbdma_cmd( cp );
+ show_dbdma_cmd( dp );
+ }
+
+ /* get xmit result */
+ txbits = inw( &sp->tx_status[i] );
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: tx xfer=%04x, txbits=%04x\n", dev->name,
+ xfer, txbits );
+
+ /* look for any channel status (?) */
+ if ( xfer ) {
+
+ dev_kfree_skb( sp->tx_skbufs[i] );
+ mark_bh( NET_BH );
+
+ if ( txbits & TX_STATUS_TXOK ) {
+ sp->stats.tx_packets++;
+ sp->stats.tx_bytes += inw( &cp->req_count );
+ }
+
+ /* dropped packets */
+ if ( txbits & (TX_STATUS_TDLC|TX_STATUS_TDEC) ) {
+ sp->stats.tx_dropped++;
+ }
+
+ /* add the collisions */
+ sp->stats.collisions += ( txbits & 0x04 );
+
+ }
+
+ dev->tbusy = 0;
+
+ return;
+}
+
+/* rx interrupt handling */
+static void
+ncr885e_rx( struct net_device *dev )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ volatile struct dbdma_cmd *cp;
+ struct sk_buff *skb;
+ int i, nb;
+ unsigned short status;
+ unsigned char *data, *stats;
+ unsigned long rxbits, ioaddr = dev->base_addr;
+
+ i = sp->rx_current;
+ cp = sp->rx_cmds + (i*2);
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: ncr885e_rx dirty=%d, current=%d (cp@%p)\n",
+ dev->name, sp->rx_dirty, sp->rx_current, cp );
+
+ nb = inw( &cp->req_count ) - inw( &cp->res_count );
+ status = inw( &cp->xfer_status );
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO "%s: (rx %d) bytes=%d, xfer_status=%04x\n",
+ dev->name, i, nb, status );
+
+ if ( status ) {
+
+ skb = sp->rx_skbufs[i];
+ data = skb->data;
+ stats = data + nb - 3;
+ rxbits = (stats[0]|stats[1]<<8|stats[2]<<16);
+
+ if (ncr885e_debug > 3)
+ printk( KERN_INFO " rx_bits=%06lx\n", rxbits );
+
+ skb->dev = dev;
+ skb_put( skb, nb-3 );
+ skb->protocol = eth_type_trans( skb, dev );
+ netif_rx( skb );
+ sp->rx_skbufs[i] = 0;
+
+ if ( rxbits & RX_STATUS_RXOK ) {
+ sp->stats.rx_packets++;
+ sp->stats.rx_bytes += nb;
+ }
+
+ if ( rxbits & RX_STATUS_MCAST )
+ sp->stats.multicast++;
+
+ }
+
+ sp->rx_dirty = sp->rx_current;
+
+ if ( ++sp->rx_current >= NR_RX_RING )
+ sp->rx_current = 0;
+
+ /* fix up the one we just trashed */
+ cp = sp->rx_cmds + (sp->rx_dirty * 2);
+
+ skb = dev_alloc_skb( RX_BUFLEN + 2 );
+ if ( skb != 0 ) {
+ skb_reserve( skb, 2 );
+ sp->rx_skbufs[sp->rx_dirty] = skb;
+ }
+
+ if (ncr885e_debug > 2)
+ printk( KERN_INFO "%s: ncr885e_rx: using ring index %d, filling cp @ %p\n",
+ dev->name, sp->rx_current, cp );
+
+ outw( RX_BUFLEN, &cp->req_count );
+ outw( 0, &cp->res_count );
+ data = skb->data;
+ outl( virt_to_bus( data ), &cp->phy_addr );
+ outw( 0, &cp->xfer_status );
+
+ cp = sp->rx_cmds + (sp->rx_current * 2);
+
+ /* restart rx DMA */
+ outl( virt_to_bus( cp ), ioaddr + RX_CMD_PTR_LO );
+ outl( (RX_DBDMA_ENABLE << 16)|RX_CHANNEL_RUN,
+ ioaddr + RX_CHANNEL_CONTROL );
+
+ return;
+}
+
+static void
+ncr885e_misc_ints( struct net_device *dev, unsigned short status )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ struct dbdma_cmd *cp;
+ unsigned long ioaddr = dev->base_addr;
+
+ if (ncr885e_debug > 1)
+ printk( KERN_INFO "miscellaneous interrupt handled; status=%02x\n",
+ status );
+
+ /* various transmit errors */
+ if ( status &
+ (INTERRUPT_PPET | INTERRUPT_PBFT | INTERRUPT_IIDT) ) {
+
+ /* illegal instruction in tx dma */
+ if ( status & INTERRUPT_IIDT ) {
+
+ cp = (struct dbdma_cmd *) bus_to_virt( inl( ioaddr + TX_CMD_PTR_LO ));
+ printk( KERN_INFO "%s: tx illegal insn:\n", dev->name );
+ printk( KERN_INFO " tx DBDMA - cmd = %p, status = %04x\n",
+ cp, inw( ioaddr + TX_CHANNEL_STATUS ));
+ printk( KERN_INFO " command = %04x, phy_addr=%08x, req_count=%04x\n",
+ inw( &cp->command ), inw( &cp->phy_addr ), inw( &cp->req_count ));
+ }
+
+ if ( status & INTERRUPT_PPET )
+ printk( KERN_INFO "%s: tx PCI parity error\n", dev->name );
+
+ if ( status & INTERRUPT_PBFT )
+ printk( KERN_INFO "%s: tx PCI bus fault\n", dev->name );
+ }
+
+ /* look for rx errors */
+ if ( status &
+ (INTERRUPT_PPER | INTERRUPT_PBFR | INTERRUPT_IIDR)) {
+
+ /* illegal instruction in rx dma */
+ if ( status & INTERRUPT_IIDR ) {
+#if 0
+ cmd = inl( ioaddr + RX_CMD_PTR_LO );
+#endif
+ printk( KERN_ERR "%s: rx illegal DMA instruction:\n", dev->name );
+ printk( KERN_ERR " channel status=%04x,\n",
+ inl( ioaddr + RX_CHANNEL_STATUS ));
+#if 0
+ show_dbdma_cmd( bus_to_virt( inl( ioaddr + RX_CMD_PTR_LO )));
+ printk( KERN_ERR " instr (%08x) %08x %08x %08x\n",
+ (int) cmd, cmd[0], cmd[1], cmd[2] );
+#endif
+ }
+
+ /* PCI parity error */
+ if ( status & INTERRUPT_PPER )
+ printk( KERN_INFO "%s: rx PCI parity error\n", dev->name );
+
+ if ( status & INTERRUPT_PBFR )
+ printk( KERN_INFO "%s: rx PCI bus fault\n", dev->name );
+
+ sp->stats.rx_errors++;
+ }
+
+ if ( status & INTERRUPT_WI ) {
+ printk( KERN_INFO "%s: link pulse\n", dev->name );
+ }
+
+ /* bump any counters */
+
+
+ return;
+}
+
+static void
+ncr885e_interrupt( int irq, void *dev_id, struct pt_regs *regs )
+
+{
+ struct net_device *dev = (struct net_device *) dev_id;
+ struct ncr885e_private *sp;
+ unsigned short status;
+ int ioaddr;
+
+ if ( dev == NULL ) {
+ printk( KERN_ERR "symba: Interrupt IRQ %d for unknown device\n", irq );
+ return;
+ }
+
+ ioaddr = dev->base_addr;
+ sp = (struct ncr885e_private *) dev->priv;
+ spin_lock( &sp->lock );
+
+ if ( dev->interrupt ) {
+ printk( KERN_ERR "%s: Re-entering interrupt handler...\n",
+ dev->name );
+ }
+
+ dev->interrupt = 1;
+ status = inw( ioaddr + INTERRUPT_CLEAR );
+
+ if (ncr885e_debug > 2)
+ printk( KERN_INFO "%s: 53C885 interrupt 0x%02x\n", dev->name, status );
+
+ /* handle non-tx and rx interrupts first */
+ if ( status & ~(INTERRUPT_DIT|INTERRUPT_DIR))
+ ncr885e_misc_ints( dev, status );
+
+ /* look for tx interrupt: more to transmit, DBDMA stopped, or tx done */
+ if ( ( status & INTERRUPT_DIT ) ) {
+
+ if (ncr885e_debug > 2)
+ printk( KERN_INFO "%s: tx int; int=%02x, chan stat=%02x\n",
+ dev->name, status, inw( ioaddr + TX_CHANNEL_STATUS ));
+
+ /* turn off timer */
+ del_timer( &sp->tx_timeout );
+ sp->timeout_active = 0;
+
+ /* stop DMA */
+ outl( TX_DBDMA_ENABLE << 16, ioaddr + TX_CHANNEL_CONTROL );
+
+ ncr885e_tx( dev );
+ }
+
+ if ( status & INTERRUPT_DIR ) {
+
+ if ( ncr885e_debug > 2 )
+ printk( KERN_INFO "%s: rx interrupt; int=%02x, rx channel stat=%02x\n",
+ dev->name, status, inw( ioaddr + RX_CHANNEL_STATUS ));
+
+ /* stop DMA */
+ outl( RX_DBDMA_ENABLE << 16, ioaddr + RX_CHANNEL_CONTROL );
+
+ /* and handle the interrupt */
+ ncr885e_rx( dev );
+ }
+
+ dev->interrupt = 0;
+ spin_unlock( &sp->lock );
+
+ return;
+}
+
+
+/* doesn't set the address permanently, however... */
+static int
+ncr885e_set_address( struct net_device *dev, void *addr )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ struct sockaddr *saddr = addr;
+ unsigned long flags;
+ unsigned short reg[3];
+ unsigned char *ioaddr, *p;
+ int i;
+
+ memcpy( dev->dev_addr, saddr->sa_data, dev->addr_len );
+
+ p = (unsigned char *) dev->dev_addr;
+ printk( KERN_INFO "%s: setting new MAC address - ", dev->name );
+#if 0
+ for( p = (unsigned char *) dev->dev_addr, i=0; i < 6; i++, p++ )
+ printk("%c%2.2x", i ? ':' : ' ', *p );
+#endif
+
+
+ p = (unsigned char *) &reg;
+ for( i=0; i < 6; i++ )
+ p[i] = dev->dev_addr[i];
+
+#if 0
+ printk("%s: Setting new mac address - ", dev->name );
+ for( i=0; i < 6; i++ ) {
+ printk("%02x", i ? ':' : ' ', p[i] );
+ }
+
+ printk("\n");
+#endif
+
+ /* stop rx for the change */
+ outl( RX_DBDMA_ENABLE << 16, ioaddr + RX_CHANNEL_CONTROL );
+
+ spin_lock_irqsave( &sp->lock, flags );
+
+ ioaddr = (unsigned char *) dev->base_addr;
+
+ for( i = 0; i < 3; i++ ) {
+ reg[i] = ((reg[i] & 0xff) << 8) | ((reg[i] >> 8) & 0xff);
+ printk("%04x ", reg[i] );
+ outw( reg[i], ioaddr + STATION_ADDRESS_0 + (i*2));
+ }
+ printk("\n");
+
+ spin_unlock_irqrestore( &sp->lock, flags );
+
+ /* restart rx */
+ outl((RX_DBDMA_ENABLE << 16)|RX_CHANNEL_RUN,
+ ioaddr + RX_CHANNEL_CONTROL );
+
+ return 0;
+}
+
+static void
+ncr885e_tx_timeout( unsigned long data )
+
+{
+ struct net_device *dev = (struct net_device *) data;
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ unsigned long flags, ioaddr;
+ int i;
+
+ save_flags( flags );
+ cli();
+
+ ioaddr = dev->base_addr;
+ sp->timeout_active = 0;
+ i = sp->tx_dirty;
+
+ /* if we weren't active, bail... */
+ if ( sp->tx_active == 0 ) {
+ printk( KERN_INFO "%s: ncr885e_timeout...tx not active!\n", dev->name );
+ goto out;
+ }
+
+ printk( KERN_ERR "%s: 53C885 timed out. Resetting...\n", dev->name );
+
+ /* disable rx and tx DMA */
+ outl( (TX_DBDMA_ENABLE << 16), ioaddr + TX_CHANNEL_CONTROL );
+ outl( (RX_DBDMA_ENABLE << 16), ioaddr + RX_CHANNEL_CONTROL );
+
+ /* reset the chip */
+ ncr885e_config( dev );
+ ncr885e_enable( dev );
+
+ /* clear the wedged skb in the tx ring */
+ sp->tx_active = 0;
+ ++sp->stats.tx_errors;
+
+ if ( sp->tx_skbufs[i] ) {
+ dev_kfree_skb( sp->tx_skbufs[i] );
+ sp->tx_skbufs[i] = 0;
+ }
+
+ /* start anew from the beginning of the ring buffer (why not?) */
+ sp->tx_current = 0;
+ dev->tbusy = 0;
+ mark_bh( NET_BH );
+
+ /* restart rx dma */
+ outl( (RX_DBDMA_ENABLE << 16) | RX_CHANNEL_RUN,
+ ioaddr + RX_CHANNEL_CONTROL );
+ out:
+
+ restore_flags( flags );
+}
+
+static inline void
+ncr885e_set_timeout( struct net_device *dev )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if ( sp->timeout_active )
+ del_timer( &sp->tx_timeout );
+
+ sp->tx_timeout.expires = jiffies + TX_TIMEOUT;
+ sp->tx_timeout.function = ncr885e_tx_timeout;
+ sp->tx_timeout.data = (unsigned long) dev;
+ add_timer( &sp->tx_timeout );
+ sp->timeout_active = 1;
+ restore_flags( flags );
+}
+
+
+/*
+ * The goal is to set up DBDMA such that the rx ring contains only
+ * one DMA descriptor per ring element and the tx ring has two (using
+ * the cool features of branch- and wait-select. However, I'm not sure
+ * if it's possible. For now, we plod through it with 3 descriptors
+ * for tx, and two for rx.
+ */
+
+static int
+ncr885e_open( struct net_device *dev )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ unsigned long ioaddr = dev->base_addr;
+ struct sk_buff *skb;
+ int i, size;
+ char *data;
+ struct dbdma_cmd *cp;
+ unsigned long flags;
+
+ /* allocate enough space for the tx and rx rings and a STOP descriptor */
+ size = (sizeof( struct dbdma_cmd ) *
+ ((NR_TX_RING * 3) + (NR_RX_RING * 2) + 1));
+
+ cp = kmalloc( size, GFP_KERNEL );
+
+ if ( cp == 0 ) {
+ printk( KERN_ERR "Insufficient memory (%d bytes) for DBDMA\n", size );
+ return -ENOMEM;
+ }
+
+ spin_lock_init( &sp->lock );
+ spin_lock_irqsave( &sp->lock, flags );
+
+ memset((char *) cp, 0, size );
+ sp->head = cp;
+
+ sp->stop_cmd = cp;
+ outl( DBDMA_STOP, &cp->command );
+
+ sp->rx_cmds = ++cp;
+
+ for( i = 0; i < NR_RX_RING; i++ ) {
+
+ cp = sp->rx_cmds + (i*2);
+ skb = dev_alloc_skb( RX_BUFLEN + 2 );
+
+ /* if there is insufficient memory, make this last ring use a
+ static buffer and leave the loop with that skb as final one */
+ if ( skb == 0 ) {
+ printk( KERN_ERR "%s: insufficient memory for rx ring buffer\n",
+ dev->name );
+ break;
+ }
+
+ skb_reserve( skb, 2 );
+ sp->rx_skbufs[i] = skb;
+ data = skb->data;
+
+ /* The DMA commands here are done such that an EOP is the only
+ way that we should get an interrupt. This means that we could
+ fill more than one skbuff before getting the interrupt at EOP. */
+
+ /* Handle rx DMA such that it always interrupts.... */
+ outw( (INPUT_MORE|INTR_ALWAYS), &cp->command );
+ outw( RX_BUFLEN, &cp->req_count );
+ outw( 0, &cp->res_count );
+ outl( virt_to_bus( data ), &cp->phy_addr );
+ outl( virt_to_bus( sp->stop_cmd ), &cp->cmd_dep );
+ outw( 0, &cp->xfer_status );
+#if 0
+ printk( KERN_INFO "rx at %p\n", cp );
+ show_dbdma_cmd( cp );
+#endif
+ ++cp;
+
+ outw( DBDMA_STOP, &cp->command );
+
+ }
+
+ /* initialize to all rx buffers are available, fill limit is the end */
+ sp->rx_dirty = 0;
+ sp->rx_current = 0;
+
+ /* fill the tx ring */
+ sp->tx_cmds = cp+1;
+
+ for( i = 0; i < NR_TX_RING; i++ ) {
+
+ /* minimal setup for tx command */
+ cp = sp->tx_cmds + (i*3);
+ outw( OUTPUT_LAST, &cp->command );
+ if (ncr885e_debug > 3) {
+ printk( KERN_INFO "tx OUTPUT_LAST at %p\n", cp );
+ show_dbdma_cmd( cp );
+ }
+
+ /* full setup for the status cmd */
+ cp++;
+ outw( INPUT_LAST|INTR_ALWAYS|WAIT_IFCLR, &cp->command );
+ outl( virt_to_bus( &sp->tx_status[i] ), &cp->phy_addr );
+ outw( 2, &cp->req_count );
+ if ( ncr885e_debug > 3) {
+ printk( KERN_INFO "tx INPUT_LAST cmd at %p\n", cp );
+ show_dbdma_cmd( cp );
+ }
+
+ ++cp;
+ outw( DBDMA_STOP, &cp->command );
+
+ }
+#if 0
+ /* chain the last tx DMA command to the STOP cmd */
+ outw((INPUT_LAST|INTR_ALWAYS|BR_ALWAYS), &cp->command );
+ outl( virt_to_bus( sp->stop_cmd ), &cp->cmd_dep );
+#endif
+ sp->tx_active = 0;
+ sp->tx_current = 0;
+ sp->tx_dirty = 0;
+
+ spin_unlock_irqrestore( &sp->lock, flags );
+
+ /* the order seems important here for some reason. If the MPIC isn't
+ enabled before the ethernet chip is enabled, shrapnel from the
+ bootloader causes us to receive interrupts even though we've not
+ yet enabled the tx channel. Go figure. It'd be better to configure
+ the chip in the probe1() routine, but then we don't see interrupts
+ at all. Everything looks all right on the logic analyzer, but... */
+
+ ncr885e_config( dev );
+
+ /* enable ethernet interrupts */
+ if ( request_irq( dev->irq, &ncr885e_interrupt, SA_SHIRQ, chipname, dev )) {
+ printk( KERN_ERR "%s: can't get irq %d\n", dev->name, dev->irq );
+ return -EAGAIN;
+ }
+
+ (void) inw( ioaddr + INTERRUPT_CLEAR );
+
+ ncr885e_enable( dev );
+
+ /* start rx DBDMA */
+ outl( virt_to_bus( sp->rx_cmds ), ioaddr + RX_CMD_PTR_LO );
+ outl( (RX_DBDMA_ENABLE << 16)|RX_CHANNEL_RUN,
+ ioaddr + RX_CHANNEL_CONTROL );
+
+ dev->start = 1;
+ dev->tbusy = 0;
+ dev->interrupt = 0;
+
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+static int
+ncr885e_xmit_start( struct sk_buff *skb, struct net_device *dev )
+
+{
+ struct ncr885e_private *sp = (struct ncr885e_private *) dev->priv;
+ volatile struct dbdma_cmd *cp, *dp;
+ unsigned long flags, ioaddr = dev->base_addr;
+ int len, next, fill, entry;
+
+ if ( ncr885e_debug > 3)
+ printk( KERN_INFO "%s: xmit_start len=%d, dirty=%d, current=%d, active=%d\n",
+ dev->name, skb->len, sp->tx_dirty, sp->tx_current, sp->tx_active );
+
+ spin_lock_irqsave( &sp->lock, flags );
+
+ /* find the free slot in the ring buffer */
+ fill = sp->tx_current;
+ next = fill + 1;
+
+ if ( next >= NR_TX_RING )
+ next = 0;
+
+ /* mark ourselves as busy, even if we have too many packets waiting */
+ dev->tbusy = 1;
+
+ /* see if it's necessary to defer this packet */
+ if ( sp->tx_active >= MAX_TX_ACTIVE ) {
+ spin_unlock_irqrestore( &sp->lock, flags );
+ return -1;
+ }
+
+ sp->tx_active++; /* bump "active tx" count */
+ sp->tx_current = next; /* and show that we've used this buffer */
+ sp->tx_dirty = fill; /* and mark this one to get picked up */
+
+ len = skb->len;
+
+ if ( len > ETH_FRAME_LEN ) {
+ printk( KERN_DEBUG "%s: xmit frame too long (%d)\n", dev->name, len );
+ len = ETH_FRAME_LEN;
+ }
+
+ /* get index into the tx DBDMA chain */
+ entry = fill * 3;
+ sp->tx_skbufs[fill] = skb;
+ cp = sp->tx_cmds + entry;
+ dp = cp + 1;
+
+ /* update the rest of the OUTPUT_MORE descriptor */
+ outw( len, &cp->req_count );
+ outl( virt_to_bus( skb->data ), &cp->phy_addr );
+ outw( 0, &cp->xfer_status );
+ outw( 0, &cp->res_count );
+
+ /* and finish off the INPUT_MORE */
+ outw( 0, &dp->xfer_status );
+ outw( 0, &dp->res_count );
+ sp->tx_status[fill] = 0;
+ outl( virt_to_bus( &sp->tx_status[fill] ), &dp->phy_addr );
+
+ if ( ncr885e_debug > 2 )
+ printk(KERN_INFO "%s: xmit_start: active %d, tx_current %d, tx_dirty %d\n",
+ dev->name, sp->tx_active, sp->tx_current, sp->tx_dirty );
+
+ if ( ncr885e_debug > 4 ) {
+ show_dbdma_cmd( cp );
+ show_dbdma_cmd( dp );
+ }
+
+
+ /* restart the tx DMA engine */
+ outl( virt_to_bus( cp ), ioaddr + TX_CMD_PTR_LO );
+ outl( (TX_DBDMA_ENABLE << 16)|TX_CHANNEL_RUN,
+ ioaddr + TX_CHANNEL_CONTROL );
+
+ ncr885e_set_timeout( dev );
+
+ spin_unlock_irqrestore( &sp->lock, flags );
+ dev->trans_start = jiffies;
+
+ return 0;
+}
+
+static int
+ncr885e_close(struct net_device *dev)
+
+{
+ int i;
+ struct ncr885e_private *np = (struct ncr885e_private *) dev->priv;
+ unsigned long ioaddr = dev->base_addr;
+
+ dev->start = 0;
+ dev->tbusy = 1;
+
+ spin_lock( &np->lock );
+
+ printk(KERN_INFO "%s: NCR885E Ethernet closing...\n", dev->name );
+
+ if (ncr885e_debug > 1)
+ printk(KERN_DEBUG "%s: Shutting down Ethernet chip\n", dev->name);
+
+ ncr885e_disable(dev);
+
+ del_timer(&np->tx_timeout);
+
+ /* flip off rx and tx */
+ outl( (RX_DBDMA_ENABLE << 16), ioaddr + RX_CHANNEL_CONTROL );
+ outl( (TX_DBDMA_ENABLE << 16), ioaddr + TX_CHANNEL_CONTROL );
+
+ /* free up the IRQ */
+ free_irq( dev->irq, dev );
+
+ for( i = 0; i < NR_RX_RING; i++ ) {
+ if (np->rx_skbufs[i])
+ dev_kfree_skb( np->rx_skbufs[i] );
+ np->rx_skbufs[i] = 0;
+ }
+#if 0
+ for (i = 0; i < NR_TX_RING; i++) {
+ if (np->tx_skbufs[i])
+ dev_kfree_skb(np->tx_skbufs[i]);
+ np->tx_skbufs[i] = 0;
+ }
+#endif
+ spin_unlock( &np->lock );
+
+ kfree( np->head );
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+
+/*
+ * multicast promiscuous mode isn't used here. Allow code in the
+ * IP stack to determine which multicast packets are good or bad....
+ * (this avoids having to use the hash table registers)
+ */
+static void
+ncr885e_set_multicast( struct net_device *dev )
+
+{
+ int ioaddr = dev->base_addr;
+
+ if ( ncr885e_debug > 3 )
+ printk("%s: set_multicast: dev->flags = %x, AF=%04x\n",
+ dev->name, dev->flags, inw( ioaddr + ADDRESS_FILTER ));
+
+ if ( dev->flags & IFF_PROMISC ) {
+ printk( KERN_INFO "%s: Promiscuous mode enabled.\n", dev->name );
+ outw( ADDRESS_RPPRO, ioaddr + ADDRESS_FILTER );
+ }
+
+ /* accept all multicast packets without checking the mc_list. */
+ else if ( dev->flags & IFF_ALLMULTI ) {
+ printk( KERN_INFO "%s: Enabling all multicast packets.\n",
+ dev->name );
+ outw( ADDRESS_RPPRM, ioaddr + ADDRESS_FILTER );
+ }
+
+ /* enable broadcast rx */
+ else {
+ outw( ADDRESS_RPABC, ioaddr + ADDRESS_FILTER );
+ }
+}
+
+static struct net_device_stats *
+ncr885e_stats( struct net_device *dev )
+
+{
+ struct ncr885e_private *np = (struct ncr885e_private *) dev->priv;
+
+ return &np->stats;
+}
+
+/* By this function, we're certain that we have a 885 Ethernet controller
+ * so we finish setting it up and wrap up all the required Linux ethernet
+ * configuration.
+ */
+
+static int
+ncr885e_probe1( struct net_device *dev, unsigned long ioaddr, unsigned char irq )
+
+{
+ struct ncr885e_private *sp;
+ unsigned short station_addr[3], val;
+ unsigned char *p;
+ int i;
+
+ dev = init_etherdev( dev, 0 );
+
+ /* construct private data for the 885 ethernet */
+ dev->priv = kmalloc( sizeof( struct ncr885e_private ), GFP_KERNEL );
+
+ if ( dev->priv == NULL )
+ return -ENOMEM;
+
+ sp = (struct ncr885e_private *) dev->priv;
+ memset( sp, 0, sizeof( struct ncr885e_private ));
+
+ /* snag the station address and display it */
+ for( i = 0; i < 3; i++ ) {
+ val = inw( ioaddr + STATION_ADDRESS_0 + (i*2));
+ station_addr[i] = ((val >> 8) & 0xff) | ((val << 8) & 0xff00);
+ }
+
+ printk( KERN_INFO "%s: %s at %08lx,", dev->name, chipname, ioaddr );
+
+ p = (unsigned char *) &station_addr;
+
+ for( i=0; i < 6; i++ ) {
+ dev->dev_addr[i] = *p;
+ printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i] );
+ p++;
+ }
+
+ printk(", IRQ %d.\n", irq );
+
+ request_region( ioaddr, NCR885E_TOTAL_SIZE, dev->name );
+
+ /* set up a timer */
+ init_timer( &sp->tx_timeout );
+ sp->timeout_active = 0;
+
+ dev->base_addr = ioaddr;
+ dev->irq = irq;
+
+ ether_setup( dev );
+
+ /* everything else */
+ dev->open = ncr885e_open;
+ dev->stop = ncr885e_close;
+ dev->get_stats = ncr885e_stats;
+ dev->hard_start_xmit = ncr885e_xmit_start;
+ dev->set_multicast_list = ncr885e_set_multicast;
+ dev->set_mac_address = ncr885e_set_address;
+
+ return 0;
+}
+
+/* Since the NCR 53C885 is a multi-function chip, I'm not worrying about
+ * trying to get the the device(s) in slot order. For our (Synergy's)
+ * purpose, there's just a single 53C885 on the board and we don't
+ * worry about the rest.
+ */
+
+int __init ncr885e_probe( struct net_device *dev )
+{
+ struct pci_dev *pdev = NULL;
+ unsigned int ioaddr, chips = 0;
+ unsigned short cmd;
+ unsigned char irq, latency;
+
+ while(( pdev = pci_find_device( PCI_VENDOR_ID_NCR,
+ PCI_DEVICE_ID_NCR_53C885_ETHERNET,
+ pdev )) != NULL ) {
+
+ if ( !print_version ) {
+ print_version++;
+ printk( KERN_INFO "%s", version );
+ }
+
+ /* Use I/O space */
+ pci_read_config_dword( pdev, PCI_BASE_ADDRESS_0, &ioaddr );
+ pci_read_config_byte( pdev, PCI_INTERRUPT_LINE, &irq );
+
+ ioaddr &= ~3;
+ /* Adjust around the Grackle... */
+#ifdef CONFIG_GEMINI
+ ioaddr |= 0xfe000000;
+#endif
+
+ if ( check_region( ioaddr, NCR885E_TOTAL_SIZE ))
+ continue;
+
+ /* finish off the probe */
+ if ( !(ncr885e_probe1( dev, ioaddr, irq ))) {
+
+ chips++;
+
+ /* Access is via I/O space, bus master enabled... */
+ pci_read_config_word( pdev, PCI_COMMAND, &cmd );
+
+ if ( !(cmd & PCI_COMMAND_MASTER) ) {
+ printk( KERN_INFO " PCI master bit not set! Now setting.\n");
+ cmd |= PCI_COMMAND_MASTER;
+ pci_write_config_word( pdev, PCI_COMMAND, cmd );
+ }
+
+ if ( !(cmd & PCI_COMMAND_IO) ) {
+ printk( KERN_INFO " Enabling I/O space.\n" );
+ cmd |= PCI_COMMAND_IO;
+ pci_write_config_word( pdev, PCI_COMMAND, cmd );
+ }
+
+ pci_read_config_byte( pdev, PCI_LATENCY_TIMER, &latency );
+
+ if ( latency < 10 ) {
+ printk( KERN_INFO " PCI latency timer (CFLT) is unreasonably"
+ " low at %d. Setting to 255.\n", latency );
+ pci_write_config_byte( pdev, PCI_LATENCY_TIMER, 255 );
+ }
+ }
+ }
+
+ if ( !chips )
+ return -ENODEV;
+ else
+ return 0;
+}
+
+/* debugging to peek at dma descriptors */
+static void
+show_dbdma_cmd( volatile struct dbdma_cmd *cmd )
+
+{
+ printk( KERN_INFO " cmd %04x, physaddr %08x, req_count %04x\n",
+ inw( &cmd->command ), inl( &cmd->phy_addr ), inw( &cmd->req_count ));
+ printk( KERN_INFO " res_count %04x, xfer_status %04x, branch %08x\n",
+ inw( &cmd->res_count ), inw( &cmd->xfer_status ),inl( &cmd->cmd_dep ));
+}
+
+#if 0
+static int
+read_eeprom( unsigned int ioaddr, int location )
+
+{
+ int loop;
+ unsigned char val;
+
+ outb( (location & 0xff), ioaddr + EE_WORD_ADDR );
+
+ /* take spillover from location in control reg */
+ outb(EE_CONTROL_RND_READB | (location & (0x7<<8)), ioaddr + EE_CONTROL);
+
+ loop = 1000;
+ while( (inb( ioaddr + EE_STATUS) & EE_SEB) &&
+ (loop > 0) ) {
+ udelay( 10 );
+ loop--;
+ }
+
+ if ( inb( ioaddr + EE_STATUS ) & EE_SEE ) {
+ printk("%s: Serial EEPROM read error\n", chipname);
+ val = 0xff;
+ }
+
+ else
+ val = inb( ioaddr + EE_READ_DATA );
+
+ return (int) val;
+}
+#endif
+
+#ifdef NCR885E_DEBUG_MII
+static void
+show_mii( unsigned long ioaddr )
+
+{
+ int phyctrl, phystat, phyadvert, phypartner, phyexpan;
+
+ phyctrl = read_mii( ioaddr, MII_AUTO_NEGOTIATION_CONTROL );
+ phystat = read_mii( ioaddr, MII_AUTO_NEGOTIATION_STATUS );
+ phyadvert = read_mii( ioaddr, MII_AUTO_NEGOTIATION_ADVERTISEMENT );
+ phypartner = read_mii( ioaddr, MII_AUTO_NEGOTIATION_LINK_PARTNER );
+ phyexpan = read_mii( ioaddr, MII_AUTO_NEGOTIATION_EXPANSION );
+
+ printk( KERN_INFO "PHY: advert=%d %s, partner=%s %s, link=%d, %s%s\n",
+ (phyadvert & MANATECH_100BASETX_FULL_DUPLEX ? 100 : 10),
+ (phyctrl & MANC_AUTO_NEGOTIATION_ENABLE ? "auto" : "fixed"),
+ (phypartner & MANLP_ACKNOWLEDGE ?
+ (phypartner & MANATECH_100BASETX_FULL_DUPLEX ? "100" : "10") :
+ "?"),
+ (phyexpan & MANE_LINK_PARTNER_AUTO_ABLE ? "auto" : "fixed"),
+ (phyctrl & MANC_PHY_SPEED_100 ? 100 : 10),
+ (phystat & MANS_LINK_STATUS ? "up" : "down"),
+ (phyexpan & MANE_PARALLEL_DETECTION_FAULT ? " PD-fault" : "" ));
+ return;
+}
+
+
+static int
+read_mii( unsigned long ioaddr, int reg )
+
+{
+ int timeout;
+
+
+ timeout = 100000;
+
+ while( inw( ioaddr + MII_INDICATOR ) & MII_BUSY ) {
+
+ if ( timeout-- < 0 ) {
+ printk( KERN_INFO "Timed out waiting for MII\n" );
+ return -1;
+ }
+ }
+
+ outw( (1<<8) + reg, ioaddr + MII_ADDRESS );
+ outw( MIIM_RSTAT, ioaddr + MIIM_COMMAND );
+
+ timeout = 100000;
+ while( inw( ioaddr + MII_INDICATOR ) & MII_BUSY ) {
+ if ( timeout-- < 0 ) {
+ printk( KERN_INFO "Timed out waiting for MII\n" );
+ return -1;
+ }
+ }
+
+ return( inw( ioaddr + MII_READ_DATA ));
+}
+
+static void
+write_mii( unsigned long ioaddr, int reg, int data )
+
+{
+ int timeout=100000;
+
+ printk( KERN_INFO "MII indicator: %02x\n", inw( ioaddr + MII_INDICATOR ));
+
+ while( inw( ioaddr + MII_INDICATOR ) & MII_BUSY ) {
+ if ( timeout-- <= 0 ) {
+ printk( KERN_INFO "Timeout waiting to write to MII\n" );
+ return;
+ }
+ udelay( 10 );
+ }
+
+ outw( (1<<8) + reg, ioaddr + MII_ADDRESS );
+ outw( data, ioaddr + MII_WRITE_DATA );
+
+ return;
+}
+
+#endif /* NCR885E_DEBUG_MII */
+
+#ifdef MODULE
+#if defined(LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x20118
+MODULE_AUTHOR("dan@synergymicro.com");
+MODULE_DESCRIPTION("Symbios 53C885 Ethernet driver");
+MODULE_PARM(debug, "i");
+#endif
+
+static int debug = 1;
+
+int
+init_module(void)
+{
+ if ( debug >= 0)
+ ncr885e_debug = debug;
+
+ return ncr885e_probe( NULL );
+}
+
+void
+cleanup_module(void)
+{
+ struct ncr885e_private *np;
+
+ if ( root_dev ) {
+
+ unregister_netdev( root_dev );
+ np = (struct ncr885e_private *) root_dev->priv;
+ release_region( root_dev->base_addr, NCR885E_TOTAL_SIZE );
+ kfree( root_dev->priv );
+ root_dev = NULL;
+ }
+}
+#endif /* MODULE */
+
+/*
+ * Local variables:
+ * compile-command: "gcc -DMODULE -DMODVERSIONS -D__KERNEL__ -I../../include -Wall -Wstrict-prototypes -O6 -c symba.c"
+ * End:
+ */