diff options
Diffstat (limited to 'drivers/net/ncr885e.c')
-rw-r--r-- | drivers/net/ncr885e.c | 1458 |
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 *) ® + 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: + */ |