diff options
Diffstat (limited to 'drivers/net/ethernet/fujitsu/eth16i.c')
-rw-r--r-- | drivers/net/ethernet/fujitsu/eth16i.c | 1484 |
1 files changed, 1484 insertions, 0 deletions
diff --git a/drivers/net/ethernet/fujitsu/eth16i.c b/drivers/net/ethernet/fujitsu/eth16i.c new file mode 100644 index 000000000000..b0e2313af3d1 --- /dev/null +++ b/drivers/net/ethernet/fujitsu/eth16i.c @@ -0,0 +1,1484 @@ +/* eth16i.c An ICL EtherTeam 16i and 32 EISA ethernet driver for Linux + + Written 1994-1999 by Mika Kuoppala + + Copyright (C) 1994-1999 by Mika Kuoppala + Based on skeleton.c and heavily on at1700.c by Donald Becker + + This software may be used and distributed according to the terms + of the GNU General Public License, incorporated herein by reference. + + The author may be reached as miku@iki.fi + + This driver supports following cards : + - ICL EtherTeam 16i + - ICL EtherTeam 32 EISA + (Uses true 32 bit transfers rather than 16i compatibility mode) + + Example Module usage: + insmod eth16i.o io=0x2a0 mediatype=bnc + + mediatype can be one of the following: bnc,tp,dix,auto,eprom + + 'auto' will try to autoprobe mediatype. + 'eprom' will use whatever type defined in eprom. + + I have benchmarked driver with PII/300Mhz as a ftp client + and 486/33Mhz as a ftp server. Top speed was 1128.37 kilobytes/sec. + + Sources: + - skeleton.c a sample network driver core for linux, + written by Donald Becker <becker@scyld.com> + - at1700.c a driver for Allied Telesis AT1700, written + by Donald Becker. + - e16iSRV.asm a Netware 3.X Server Driver for ICL EtherTeam16i + written by Markku Viima + - The Fujitsu MB86965 databook. + + Author thanks following persons due to their valueble assistance: + Markku Viima (ICL) + Ari Valve (ICL) + Donald Becker + Kurt Huwig <kurt@huwig.de> + + Revision history: + + Version Date Description + + 0.01 15.12-94 Initial version (card detection) + 0.02 23.01-95 Interrupt is now hooked correctly + 0.03 01.02-95 Rewrote initialization part + 0.04 07.02-95 Base skeleton done... + Made a few changes to signature checking + to make it a bit reliable. + - fixed bug in tx_buf mapping + - fixed bug in initialization (DLC_EN + wasn't enabled when initialization + was done.) + 0.05 08.02-95 If there were more than one packet to send, + transmit was jammed due to invalid + register write...now fixed + 0.06 19.02-95 Rewrote interrupt handling + 0.07 13.04-95 Wrote EEPROM read routines + Card configuration now set according to + data read from EEPROM + 0.08 23.06-95 Wrote part that tries to probe used interface + port if AUTO is selected + + 0.09 01.09-95 Added module support + + 0.10 04.09-95 Fixed receive packet allocation to work + with kernels > 1.3.x + + 0.20 20.09-95 Added support for EtherTeam32 EISA + + 0.21 17.10-95 Removed the unnecessary extern + init_etherdev() declaration. Some + other cleanups. + + 0.22 22.02-96 Receive buffer was not flushed + correctly when faulty packet was + received. Now fixed. + + 0.23 26.02-96 Made resetting the adapter + more reliable. + + 0.24 27.02-96 Rewrote faulty packet handling in eth16i_rx + + 0.25 22.05-96 kfree() was missing from cleanup_module. + + 0.26 11.06-96 Sometimes card was not found by + check_signature(). Now made more reliable. + + 0.27 23.06-96 Oops. 16 consecutive collisions halted + adapter. Now will try to retransmit + MAX_COL_16 times before finally giving up. + + 0.28 28.10-97 Added dev_id parameter (NULL) for free_irq + + 0.29 29.10-97 Multiple card support for module users + + 0.30 30.10-97 Fixed irq allocation bug. + (request_irq moved from probe to open) + + 0.30a 21.08-98 Card detection made more relaxed. Driver + had problems with some TCP/IP-PROM boots + to find the card. Suggested by + Kurt Huwig <kurt@huwig.de> + + 0.31 28.08-98 Media interface port can now be selected + with module parameters or kernel + boot parameters. + + 0.32 31.08-98 IRQ was never freed if open/close + pair wasn't called. Now fixed. + + 0.33 10.09-98 When eth16i_open() was called after + eth16i_close() chip never recovered. + Now more shallow reset is made on + close. + + 0.34 29.06-99 Fixed one bad #ifdef. + Changed ioaddr -> io for consistency + + 0.35 01.07-99 transmit,-receive bytes were never + updated in stats. + + Bugs: + In some cases the media interface autoprobing code doesn't find + the correct interface type. In this case you can + manually choose the interface type in DOS with E16IC.EXE which is + configuration software for EtherTeam16i and EtherTeam32 cards. + This is also true for IRQ setting. You cannot use module + parameter to configure IRQ of the card (yet). + + To do: + - Real multicast support + - Rewrite the media interface autoprobing code. Its _horrible_ ! + - Possibly merge all the MB86965 specific code to external + module for use by eth16.c and Donald's at1700.c + - IRQ configuration with module parameter. I will do + this when i will get enough info about setting + irq without configuration utility. +*/ + +static char *version = + "eth16i.c: v0.35 01-Jul-1999 Mika Kuoppala (miku@iki.fi)\n"; + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/bitops.h> +#include <linux/jiffies.h> +#include <linux/io.h> + +#include <asm/system.h> +#include <asm/dma.h> + + + +/* Few macros */ +#define BITSET(ioaddr, bnum) ((outb(((inb(ioaddr)) | (bnum)), ioaddr))) +#define BITCLR(ioaddr, bnum) ((outb(((inb(ioaddr)) & (~(bnum))), ioaddr))) + +/* This is the I/O address space for Etherteam 16i adapter. */ +#define ETH16I_IO_EXTENT 32 + +/* Ticks before deciding that transmit has timed out */ +#define TX_TIMEOUT (400*HZ/1000) + +/* Maximum loop count when receiving packets */ +#define MAX_RX_LOOP 20 + +/* Some interrupt masks */ +#define ETH16I_INTR_ON 0xef8a /* Higher is receive mask */ +#define ETH16I_INTR_OFF 0x0000 + +/* Buffers header status byte meanings */ +#define PKT_GOOD BIT(5) +#define PKT_GOOD_RMT BIT(4) +#define PKT_SHORT BIT(3) +#define PKT_ALIGN_ERR BIT(2) +#define PKT_CRC_ERR BIT(1) +#define PKT_RX_BUF_OVERFLOW BIT(0) + +/* Transmit status register (DLCR0) */ +#define TX_STATUS_REG 0 +#define TX_DONE BIT(7) +#define NET_BUSY BIT(6) +#define TX_PKT_RCD BIT(5) +#define CR_LOST BIT(4) +#define TX_JABBER_ERR BIT(3) +#define COLLISION BIT(2) +#define COLLISIONS_16 BIT(1) + +/* Receive status register (DLCR1) */ +#define RX_STATUS_REG 1 +#define RX_PKT BIT(7) /* Packet received */ +#define BUS_RD_ERR BIT(6) +#define SHORT_PKT_ERR BIT(3) +#define ALIGN_ERR BIT(2) +#define CRC_ERR BIT(1) +#define RX_BUF_OVERFLOW BIT(0) + +/* Transmit Interrupt Enable Register (DLCR2) */ +#define TX_INTR_REG 2 +#define TX_INTR_DONE BIT(7) +#define TX_INTR_COL BIT(2) +#define TX_INTR_16_COL BIT(1) + +/* Receive Interrupt Enable Register (DLCR3) */ +#define RX_INTR_REG 3 +#define RX_INTR_RECEIVE BIT(7) +#define RX_INTR_SHORT_PKT BIT(3) +#define RX_INTR_CRC_ERR BIT(1) +#define RX_INTR_BUF_OVERFLOW BIT(0) + +/* Transmit Mode Register (DLCR4) */ +#define TRANSMIT_MODE_REG 4 +#define LOOPBACK_CONTROL BIT(1) +#define CONTROL_OUTPUT BIT(2) + +/* Receive Mode Register (DLCR5) */ +#define RECEIVE_MODE_REG 5 +#define RX_BUFFER_EMPTY BIT(6) +#define ACCEPT_BAD_PACKETS BIT(5) +#define RECEIVE_SHORT_ADDR BIT(4) +#define ACCEPT_SHORT_PACKETS BIT(3) +#define REMOTE_RESET BIT(2) + +#define ADDRESS_FILTER_MODE BIT(1) | BIT(0) +#define REJECT_ALL 0 +#define ACCEPT_ALL 3 +#define MODE_1 1 /* NODE ID, BC, MC, 2-24th bit */ +#define MODE_2 2 /* NODE ID, BC, MC, Hash Table */ + +/* Configuration Register 0 (DLCR6) */ +#define CONFIG_REG_0 6 +#define DLC_EN BIT(7) +#define SRAM_CYCLE_TIME_100NS BIT(6) +#define SYSTEM_BUS_WIDTH_8 BIT(5) /* 1 = 8bit, 0 = 16bit */ +#define BUFFER_WIDTH_8 BIT(4) /* 1 = 8bit, 0 = 16bit */ +#define TBS1 BIT(3) +#define TBS0 BIT(2) +#define SRAM_BS1 BIT(1) /* 00=8kb, 01=16kb */ +#define SRAM_BS0 BIT(0) /* 10=32kb, 11=64kb */ + +#ifndef ETH16I_TX_BUF_SIZE /* 0 = 2kb, 1 = 4kb */ +#define ETH16I_TX_BUF_SIZE 3 /* 2 = 8kb, 3 = 16kb */ +#endif +#define TX_BUF_1x2048 0 +#define TX_BUF_2x2048 1 +#define TX_BUF_2x4098 2 +#define TX_BUF_2x8192 3 + +/* Configuration Register 1 (DLCR7) */ +#define CONFIG_REG_1 7 +#define POWERUP BIT(5) + +/* Transmit start register */ +#define TRANSMIT_START_REG 10 +#define TRANSMIT_START_RB 2 +#define TX_START BIT(7) /* Rest of register bit indicate*/ + /* number of packets in tx buffer*/ +/* Node ID registers (DLCR8-13) */ +#define NODE_ID_0 8 +#define NODE_ID_RB 0 + +/* Hash Table registers (HT8-15) */ +#define HASH_TABLE_0 8 +#define HASH_TABLE_RB 1 + +/* Buffer memory ports */ +#define BUFFER_MEM_PORT_LB 8 +#define DATAPORT BUFFER_MEM_PORT_LB +#define BUFFER_MEM_PORT_HB 9 + +/* 16 Collision control register (BMPR11) */ +#define COL_16_REG 11 +#define HALT_ON_16 0x00 +#define RETRANS_AND_HALT_ON_16 0x02 + +/* Maximum number of attempts to send after 16 concecutive collisions */ +#define MAX_COL_16 10 + +/* DMA Burst and Transceiver Mode Register (BMPR13) */ +#define TRANSCEIVER_MODE_REG 13 +#define TRANSCEIVER_MODE_RB 2 +#define IO_BASE_UNLOCK BIT(7) +#define LOWER_SQUELCH_TRESH BIT(6) +#define LINK_TEST_DISABLE BIT(5) +#define AUI_SELECT BIT(4) +#define DIS_AUTO_PORT_SEL BIT(3) + +/* Filter Self Receive Register (BMPR14) */ +#define FILTER_SELF_RX_REG 14 +#define SKIP_RX_PACKET BIT(2) +#define FILTER_SELF_RECEIVE BIT(0) + +/* EEPROM Control Register (BMPR 16) */ +#define EEPROM_CTRL_REG 16 + +/* EEPROM Data Register (BMPR 17) */ +#define EEPROM_DATA_REG 17 + +/* NMC93CSx6 EEPROM Control Bits */ +#define CS_0 0x00 +#define CS_1 0x20 +#define SK_0 0x00 +#define SK_1 0x40 +#define DI_0 0x00 +#define DI_1 0x80 + +/* NMC93CSx6 EEPROM Instructions */ +#define EEPROM_READ 0x80 + +/* NMC93CSx6 EEPROM Addresses */ +#define E_NODEID_0 0x02 +#define E_NODEID_1 0x03 +#define E_NODEID_2 0x04 +#define E_PORT_SELECT 0x14 + #define E_PORT_BNC 0x00 + #define E_PORT_DIX 0x01 + #define E_PORT_TP 0x02 + #define E_PORT_AUTO 0x03 + #define E_PORT_FROM_EPROM 0x04 +#define E_PRODUCT_CFG 0x30 + + +/* Macro to slow down io between EEPROM clock transitions */ +#define eeprom_slow_io() do { int _i = 40; while(--_i > 0) { inb(0x80); }}while(0) + +/* Jumperless Configuration Register (BMPR19) */ +#define JUMPERLESS_CONFIG 19 + +/* ID ROM registers, writing to them also resets some parts of chip */ +#define ID_ROM_0 24 +#define ID_ROM_7 31 +#define RESET ID_ROM_0 + +/* This is the I/O address list to be probed when seeking the card */ +static unsigned int eth16i_portlist[] __initdata = { + 0x260, 0x280, 0x2A0, 0x240, 0x340, 0x320, 0x380, 0x300, 0 +}; + +static unsigned int eth32i_portlist[] __initdata = { + 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0x7000, 0x8000, + 0x9000, 0xA000, 0xB000, 0xC000, 0xD000, 0xE000, 0xF000, 0 +}; + +/* This is the Interrupt lookup table for Eth16i card */ +static unsigned int eth16i_irqmap[] __initdata = { 9, 10, 5, 15, 0 }; +#define NUM_OF_ISA_IRQS 4 + +/* This is the Interrupt lookup table for Eth32i card */ +static unsigned int eth32i_irqmap[] __initdata = { 3, 5, 7, 9, 10, 11, 12, 15, 0 }; +#define EISA_IRQ_REG 0xc89 +#define NUM_OF_EISA_IRQS 8 + +static unsigned int eth16i_tx_buf_map[] = { 2048, 2048, 4096, 8192 }; + +/* Use 0 for production, 1 for verification, >2 for debug */ +#ifndef ETH16I_DEBUG +#define ETH16I_DEBUG 0 +#endif +static unsigned int eth16i_debug = ETH16I_DEBUG; + +/* Information for each board */ + +struct eth16i_local { + unsigned char tx_started; + unsigned char tx_buf_busy; + unsigned short tx_queue; /* Number of packets in transmit buffer */ + unsigned short tx_queue_len; + unsigned int tx_buf_size; + unsigned long open_time; + unsigned long tx_buffered_packets; + unsigned long tx_buffered_bytes; + unsigned long col_16; + spinlock_t lock; +}; + +/* Function prototypes */ + +static int eth16i_probe1(struct net_device *dev, int ioaddr); +static int eth16i_check_signature(int ioaddr); +static int eth16i_probe_port(int ioaddr); +static void eth16i_set_port(int ioaddr, int porttype); +static int eth16i_send_probe_packet(int ioaddr, unsigned char *b, int l); +static int eth16i_receive_probe_packet(int ioaddr); +static int eth16i_get_irq(int ioaddr); +static int eth16i_read_eeprom(int ioaddr, int offset); +static int eth16i_read_eeprom_word(int ioaddr); +static void eth16i_eeprom_cmd(int ioaddr, unsigned char command); +static int eth16i_open(struct net_device *dev); +static int eth16i_close(struct net_device *dev); +static netdev_tx_t eth16i_tx(struct sk_buff *skb, struct net_device *dev); +static void eth16i_rx(struct net_device *dev); +static void eth16i_timeout(struct net_device *dev); +static irqreturn_t eth16i_interrupt(int irq, void *dev_id); +static void eth16i_reset(struct net_device *dev); +static void eth16i_timeout(struct net_device *dev); +static void eth16i_skip_packet(struct net_device *dev); +static void eth16i_multicast(struct net_device *dev); +static void eth16i_select_regbank(unsigned char regbank, int ioaddr); +static void eth16i_initialize(struct net_device *dev, int boot); + +#if 0 +static int eth16i_set_irq(struct net_device *dev); +#endif + +#ifdef MODULE +static ushort eth16i_parse_mediatype(const char* s); +#endif + +static char cardname[] __initdata = "ICL EtherTeam 16i/32"; + +static int __init do_eth16i_probe(struct net_device *dev) +{ + int i; + int ioaddr; + int base_addr = dev->base_addr; + + if(eth16i_debug > 4) + printk(KERN_DEBUG "Probing started for %s\n", cardname); + + if(base_addr > 0x1ff) /* Check only single location */ + return eth16i_probe1(dev, base_addr); + else if(base_addr != 0) /* Don't probe at all */ + return -ENXIO; + + /* Seek card from the ISA io address space */ + for(i = 0; (ioaddr = eth16i_portlist[i]) ; i++) + if(eth16i_probe1(dev, ioaddr) == 0) + return 0; + + /* Seek card from the EISA io address space */ + for(i = 0; (ioaddr = eth32i_portlist[i]) ; i++) + if(eth16i_probe1(dev, ioaddr) == 0) + return 0; + + return -ENODEV; +} + +#ifndef MODULE +struct net_device * __init eth16i_probe(int unit) +{ + struct net_device *dev = alloc_etherdev(sizeof(struct eth16i_local)); + int err; + + if (!dev) + return ERR_PTR(-ENOMEM); + + sprintf(dev->name, "eth%d", unit); + netdev_boot_setup_check(dev); + + err = do_eth16i_probe(dev); + if (err) + goto out; + return dev; +out: + free_netdev(dev); + return ERR_PTR(err); +} +#endif + +static const struct net_device_ops eth16i_netdev_ops = { + .ndo_open = eth16i_open, + .ndo_stop = eth16i_close, + .ndo_start_xmit = eth16i_tx, + .ndo_set_rx_mode = eth16i_multicast, + .ndo_tx_timeout = eth16i_timeout, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static int __init eth16i_probe1(struct net_device *dev, int ioaddr) +{ + struct eth16i_local *lp = netdev_priv(dev); + static unsigned version_printed; + int retval; + + /* Let's grab the region */ + if (!request_region(ioaddr, ETH16I_IO_EXTENT, cardname)) + return -EBUSY; + + /* + The MB86985 chip has on register which holds information in which + io address the chip lies. First read this register and compare + it to our current io address and if match then this could + be our chip. + */ + + if(ioaddr < 0x1000) { + if(eth16i_portlist[(inb(ioaddr + JUMPERLESS_CONFIG) & 0x07)] + != ioaddr) { + retval = -ENODEV; + goto out; + } + } + + /* Now we will go a bit deeper and try to find the chip's signature */ + + if(eth16i_check_signature(ioaddr) != 0) { + retval = -ENODEV; + goto out; + } + + /* + Now it seems that we have found a ethernet chip in this particular + ioaddr. The MB86985 chip has this feature, that when you read a + certain register it will increase it's io base address to next + configurable slot. Now when we have found the chip, first thing is + to make sure that the chip's ioaddr will hold still here. + */ + + eth16i_select_regbank(TRANSCEIVER_MODE_RB, ioaddr); + outb(0x00, ioaddr + TRANSCEIVER_MODE_REG); + + outb(0x00, ioaddr + RESET); /* Reset some parts of chip */ + BITSET(ioaddr + CONFIG_REG_0, BIT(7)); /* Disable the data link */ + + if( (eth16i_debug & version_printed++) == 0) + printk(KERN_INFO "%s", version); + + dev->base_addr = ioaddr; + dev->irq = eth16i_get_irq(ioaddr); + + /* Try to obtain interrupt vector */ + + if ((retval = request_irq(dev->irq, (void *)ð16i_interrupt, 0, cardname, dev))) { + printk(KERN_WARNING "%s at %#3x, but is unusable due to conflicting IRQ %d.\n", + cardname, ioaddr, dev->irq); + goto out; + } + + printk(KERN_INFO "%s: %s at %#3x, IRQ %d, ", + dev->name, cardname, ioaddr, dev->irq); + + + /* Now we will have to lock the chip's io address */ + eth16i_select_regbank(TRANSCEIVER_MODE_RB, ioaddr); + outb(0x38, ioaddr + TRANSCEIVER_MODE_REG); + + eth16i_initialize(dev, 1); /* Initialize rest of the chip's registers */ + + /* Now let's same some energy by shutting down the chip ;) */ + BITCLR(ioaddr + CONFIG_REG_1, POWERUP); + + /* Initialize the device structure */ + dev->netdev_ops = ð16i_netdev_ops; + dev->watchdog_timeo = TX_TIMEOUT; + spin_lock_init(&lp->lock); + + retval = register_netdev(dev); + if (retval) + goto out1; + return 0; +out1: + free_irq(dev->irq, dev); +out: + release_region(ioaddr, ETH16I_IO_EXTENT); + return retval; +} + + +static void eth16i_initialize(struct net_device *dev, int boot) +{ + int ioaddr = dev->base_addr; + int i, node_w = 0; + unsigned char node_byte = 0; + + /* Setup station address */ + eth16i_select_regbank(NODE_ID_RB, ioaddr); + for(i = 0 ; i < 3 ; i++) { + unsigned short node_val = eth16i_read_eeprom(ioaddr, E_NODEID_0 + i); + ((unsigned short *)dev->dev_addr)[i] = ntohs(node_val); + } + + for(i = 0; i < 6; i++) { + outb( ((unsigned char *)dev->dev_addr)[i], ioaddr + NODE_ID_0 + i); + if(boot) { + printk("%02x", inb(ioaddr + NODE_ID_0 + i)); + if(i != 5) + printk(":"); + } + } + + /* Now we will set multicast addresses to accept none */ + eth16i_select_regbank(HASH_TABLE_RB, ioaddr); + for(i = 0; i < 8; i++) + outb(0x00, ioaddr + HASH_TABLE_0 + i); + + /* + Now let's disable the transmitter and receiver, set the buffer ram + cycle time, bus width and buffer data path width. Also we shall + set transmit buffer size and total buffer size. + */ + + eth16i_select_regbank(2, ioaddr); + + node_byte = 0; + node_w = eth16i_read_eeprom(ioaddr, E_PRODUCT_CFG); + + if( (node_w & 0xFF00) == 0x0800) + node_byte |= BUFFER_WIDTH_8; + + node_byte |= SRAM_BS1; + + if( (node_w & 0x00FF) == 64) + node_byte |= SRAM_BS0; + + node_byte |= DLC_EN | SRAM_CYCLE_TIME_100NS | (ETH16I_TX_BUF_SIZE << 2); + + outb(node_byte, ioaddr + CONFIG_REG_0); + + /* We shall halt the transmitting, if 16 collisions are detected */ + outb(HALT_ON_16, ioaddr + COL_16_REG); + +#ifdef MODULE + /* if_port already set by init_module() */ +#else + dev->if_port = (dev->mem_start < E_PORT_FROM_EPROM) ? + dev->mem_start : E_PORT_FROM_EPROM; +#endif + + /* Set interface port type */ + if(boot) { + static const char * const porttype[] = { + "BNC", "DIX", "TP", "AUTO", "FROM_EPROM" + }; + + switch(dev->if_port) + { + + case E_PORT_FROM_EPROM: + dev->if_port = eth16i_read_eeprom(ioaddr, E_PORT_SELECT); + break; + + case E_PORT_AUTO: + dev->if_port = eth16i_probe_port(ioaddr); + break; + + case E_PORT_BNC: + case E_PORT_TP: + case E_PORT_DIX: + break; + } + + printk(" %s interface.\n", porttype[dev->if_port]); + + eth16i_set_port(ioaddr, dev->if_port); + } + + /* Set Receive Mode to normal operation */ + outb(MODE_2, ioaddr + RECEIVE_MODE_REG); +} + +static int eth16i_probe_port(int ioaddr) +{ + int i; + int retcode; + unsigned char dummy_packet[64]; + + /* Powerup the chip */ + outb(0xc0 | POWERUP, ioaddr + CONFIG_REG_1); + + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + + eth16i_select_regbank(NODE_ID_RB, ioaddr); + + for(i = 0; i < 6; i++) { + dummy_packet[i] = inb(ioaddr + NODE_ID_0 + i); + dummy_packet[i+6] = inb(ioaddr + NODE_ID_0 + i); + } + + dummy_packet[12] = 0x00; + dummy_packet[13] = 0x04; + memset(dummy_packet + 14, 0, sizeof(dummy_packet) - 14); + + eth16i_select_regbank(2, ioaddr); + + for(i = 0; i < 3; i++) { + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + BITCLR(ioaddr + CONFIG_REG_0, DLC_EN); + eth16i_set_port(ioaddr, i); + + if(eth16i_debug > 1) + printk(KERN_DEBUG "Set port number %d\n", i); + + retcode = eth16i_send_probe_packet(ioaddr, dummy_packet, 64); + if(retcode == 0) { + retcode = eth16i_receive_probe_packet(ioaddr); + if(retcode != -1) { + if(eth16i_debug > 1) + printk(KERN_DEBUG "Eth16i interface port found at %d\n", i); + return i; + } + } + else { + if(eth16i_debug > 1) + printk(KERN_DEBUG "TRANSMIT_DONE timeout when probing interface port\n"); + } + } + + if( eth16i_debug > 1) + printk(KERN_DEBUG "Using default port\n"); + + return E_PORT_BNC; +} + +static void eth16i_set_port(int ioaddr, int porttype) +{ + unsigned short temp = 0; + + eth16i_select_regbank(TRANSCEIVER_MODE_RB, ioaddr); + outb(LOOPBACK_CONTROL, ioaddr + TRANSMIT_MODE_REG); + + temp |= DIS_AUTO_PORT_SEL; + + switch(porttype) { + + case E_PORT_BNC : + temp |= AUI_SELECT; + break; + + case E_PORT_TP : + break; + + case E_PORT_DIX : + temp |= AUI_SELECT; + BITSET(ioaddr + TRANSMIT_MODE_REG, CONTROL_OUTPUT); + break; + } + + outb(temp, ioaddr + TRANSCEIVER_MODE_REG); + + if(eth16i_debug > 1) { + printk(KERN_DEBUG "TRANSMIT_MODE_REG = %x\n", inb(ioaddr + TRANSMIT_MODE_REG)); + printk(KERN_DEBUG "TRANSCEIVER_MODE_REG = %x\n", + inb(ioaddr+TRANSCEIVER_MODE_REG)); + } +} + +static int eth16i_send_probe_packet(int ioaddr, unsigned char *b, int l) +{ + unsigned long starttime; + + outb(0xff, ioaddr + TX_STATUS_REG); + + outw(l, ioaddr + DATAPORT); + outsw(ioaddr + DATAPORT, (unsigned short *)b, (l + 1) >> 1); + + starttime = jiffies; + outb(TX_START | 1, ioaddr + TRANSMIT_START_REG); + + while( (inb(ioaddr + TX_STATUS_REG) & 0x80) == 0) { + if( time_after(jiffies, starttime + TX_TIMEOUT)) { + return -1; + } + } + + return 0; +} + +static int eth16i_receive_probe_packet(int ioaddr) +{ + unsigned long starttime; + + starttime = jiffies; + + while((inb(ioaddr + TX_STATUS_REG) & 0x20) == 0) { + if( time_after(jiffies, starttime + TX_TIMEOUT)) { + + if(eth16i_debug > 1) + printk(KERN_DEBUG "Timeout occurred waiting transmit packet received\n"); + starttime = jiffies; + while((inb(ioaddr + RX_STATUS_REG) & 0x80) == 0) { + if( time_after(jiffies, starttime + TX_TIMEOUT)) { + if(eth16i_debug > 1) + printk(KERN_DEBUG "Timeout occurred waiting receive packet\n"); + return -1; + } + } + + if(eth16i_debug > 1) + printk(KERN_DEBUG "RECEIVE_PACKET\n"); + return 0; /* Found receive packet */ + } + } + + if(eth16i_debug > 1) { + printk(KERN_DEBUG "TRANSMIT_PACKET_RECEIVED %x\n", inb(ioaddr + TX_STATUS_REG)); + printk(KERN_DEBUG "RX_STATUS_REG = %x\n", inb(ioaddr + RX_STATUS_REG)); + } + + return 0; /* Return success */ +} + +#if 0 +static int eth16i_set_irq(struct net_device* dev) +{ + const int ioaddr = dev->base_addr; + const int irq = dev->irq; + int i = 0; + + if(ioaddr < 0x1000) { + while(eth16i_irqmap[i] && eth16i_irqmap[i] != irq) + i++; + + if(i < NUM_OF_ISA_IRQS) { + u8 cbyte = inb(ioaddr + JUMPERLESS_CONFIG); + cbyte = (cbyte & 0x3F) | (i << 6); + outb(cbyte, ioaddr + JUMPERLESS_CONFIG); + return 0; + } + } + else { + printk(KERN_NOTICE "%s: EISA Interrupt cannot be set. Use EISA Configuration utility.\n", dev->name); + } + + return -1; + +} +#endif + +static int __init eth16i_get_irq(int ioaddr) +{ + unsigned char cbyte; + + if( ioaddr < 0x1000) { + cbyte = inb(ioaddr + JUMPERLESS_CONFIG); + return eth16i_irqmap[((cbyte & 0xC0) >> 6)]; + } else { /* Oh..the card is EISA so method getting IRQ different */ + unsigned short index = 0; + cbyte = inb(ioaddr + EISA_IRQ_REG); + while( (cbyte & 0x01) == 0) { + cbyte = cbyte >> 1; + index++; + } + return eth32i_irqmap[index]; + } +} + +static int __init eth16i_check_signature(int ioaddr) +{ + int i; + unsigned char creg[4] = { 0 }; + + for(i = 0; i < 4 ; i++) { + + creg[i] = inb(ioaddr + TRANSMIT_MODE_REG + i); + + if(eth16i_debug > 1) + printk("eth16i: read signature byte %x at %x\n", + creg[i], + ioaddr + TRANSMIT_MODE_REG + i); + } + + creg[0] &= 0x0F; /* Mask collision cnr */ + creg[2] &= 0x7F; /* Mask DCLEN bit */ + +#if 0 + /* + This was removed because the card was sometimes left to state + from which it couldn't be find anymore. If there is need + to more strict check still this have to be fixed. + */ + if( ! ((creg[0] == 0x06) && (creg[1] == 0x41)) ) { + if(creg[1] != 0x42) + return -1; + } +#endif + + if( !((creg[2] == 0x36) && (creg[3] == 0xE0)) ) { + creg[2] &= 0x40; + creg[3] &= 0x03; + + if( !((creg[2] == 0x40) && (creg[3] == 0x00)) ) + return -1; + } + + if(eth16i_read_eeprom(ioaddr, E_NODEID_0) != 0) + return -1; + + if((eth16i_read_eeprom(ioaddr, E_NODEID_1) & 0xFF00) != 0x4B00) + return -1; + + return 0; +} + +static int eth16i_read_eeprom(int ioaddr, int offset) +{ + int data = 0; + + eth16i_eeprom_cmd(ioaddr, EEPROM_READ | offset); + outb(CS_1, ioaddr + EEPROM_CTRL_REG); + data = eth16i_read_eeprom_word(ioaddr); + outb(CS_0 | SK_0, ioaddr + EEPROM_CTRL_REG); + + return data; +} + +static int eth16i_read_eeprom_word(int ioaddr) +{ + int i; + int data = 0; + + for(i = 16; i > 0; i--) { + outb(CS_1 | SK_0, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + outb(CS_1 | SK_1, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + data = (data << 1) | + ((inb(ioaddr + EEPROM_DATA_REG) & DI_1) ? 1 : 0); + + eeprom_slow_io(); + } + + return data; +} + +static void eth16i_eeprom_cmd(int ioaddr, unsigned char command) +{ + int i; + + outb(CS_0 | SK_0, ioaddr + EEPROM_CTRL_REG); + outb(DI_0, ioaddr + EEPROM_DATA_REG); + outb(CS_1 | SK_0, ioaddr + EEPROM_CTRL_REG); + outb(DI_1, ioaddr + EEPROM_DATA_REG); + outb(CS_1 | SK_1, ioaddr + EEPROM_CTRL_REG); + + for(i = 7; i >= 0; i--) { + short cmd = ( (command & (1 << i)) ? DI_1 : DI_0 ); + outb(cmd, ioaddr + EEPROM_DATA_REG); + outb(CS_1 | SK_0, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + outb(CS_1 | SK_1, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + } +} + +static int eth16i_open(struct net_device *dev) +{ + struct eth16i_local *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + + /* Powerup the chip */ + outb(0xc0 | POWERUP, ioaddr + CONFIG_REG_1); + + /* Initialize the chip */ + eth16i_initialize(dev, 0); + + /* Set the transmit buffer size */ + lp->tx_buf_size = eth16i_tx_buf_map[ETH16I_TX_BUF_SIZE & 0x03]; + + if(eth16i_debug > 0) + printk(KERN_DEBUG "%s: transmit buffer size %d\n", + dev->name, lp->tx_buf_size); + + /* Now enable Transmitter and Receiver sections */ + BITCLR(ioaddr + CONFIG_REG_0, DLC_EN); + + /* Now switch to register bank 2, for run time operation */ + eth16i_select_regbank(2, ioaddr); + + lp->open_time = jiffies; + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + + /* Turn on interrupts*/ + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + + netif_start_queue(dev); + return 0; +} + +static int eth16i_close(struct net_device *dev) +{ + struct eth16i_local *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + + eth16i_reset(dev); + + /* Turn off interrupts*/ + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + netif_stop_queue(dev); + + lp->open_time = 0; + + /* Disable transmit and receive */ + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + + /* Reset the chip */ + /* outb(0xff, ioaddr + RESET); */ + /* outw(0xffff, ioaddr + TX_STATUS_REG); */ + + outb(0x00, ioaddr + CONFIG_REG_1); + + return 0; +} + +static void eth16i_timeout(struct net_device *dev) +{ + struct eth16i_local *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + /* + If we get here, some higher level has decided that + we are broken. There should really be a "kick me" + function call instead. + */ + + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + printk(KERN_WARNING "%s: transmit timed out with status %04x, %s ?\n", + dev->name, + inw(ioaddr + TX_STATUS_REG), (inb(ioaddr + TX_STATUS_REG) & TX_DONE) ? + "IRQ conflict" : "network cable problem"); + + dev->trans_start = jiffies; /* prevent tx timeout */ + + /* Let's dump all registers */ + if(eth16i_debug > 0) { + printk(KERN_DEBUG "%s: timeout: %02x %02x %02x %02x %02x %02x %02x %02x.\n", + dev->name, inb(ioaddr + 0), + inb(ioaddr + 1), inb(ioaddr + 2), + inb(ioaddr + 3), inb(ioaddr + 4), + inb(ioaddr + 5), + inb(ioaddr + 6), inb(ioaddr + 7)); + + printk(KERN_DEBUG "%s: transmit start reg: %02x. collision reg %02x\n", + dev->name, inb(ioaddr + TRANSMIT_START_REG), + inb(ioaddr + COL_16_REG)); + printk(KERN_DEBUG "lp->tx_queue = %d\n", lp->tx_queue); + printk(KERN_DEBUG "lp->tx_queue_len = %d\n", lp->tx_queue_len); + printk(KERN_DEBUG "lp->tx_started = %d\n", lp->tx_started); + } + dev->stats.tx_errors++; + eth16i_reset(dev); + dev->trans_start = jiffies; /* prevent tx timeout */ + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + netif_wake_queue(dev); +} + +static netdev_tx_t eth16i_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct eth16i_local *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + int status = 0; + ushort length = skb->len; + unsigned char *buf; + unsigned long flags; + + if (length < ETH_ZLEN) { + if (skb_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + length = ETH_ZLEN; + } + buf = skb->data; + + netif_stop_queue(dev); + + /* Turn off TX interrupts */ + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + /* We would be better doing the disable_irq tricks the 3c509 does, + that would make this suck a lot less */ + + spin_lock_irqsave(&lp->lock, flags); + + if( (length + 2) > (lp->tx_buf_size - lp->tx_queue_len)) { + if(eth16i_debug > 0) + printk(KERN_WARNING "%s: Transmit buffer full.\n", dev->name); + } + else { + outw(length, ioaddr + DATAPORT); + + if( ioaddr < 0x1000 ) + outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1); + else { + unsigned char frag = length % 4; + outsl(ioaddr + DATAPORT, buf, length >> 2); + if( frag != 0 ) { + outsw(ioaddr + DATAPORT, (buf + (length & 0xFFFC)), 1); + if( frag == 3 ) + outsw(ioaddr + DATAPORT, + (buf + (length & 0xFFFC) + 2), 1); + } + } + lp->tx_buffered_packets++; + lp->tx_buffered_bytes = length; + lp->tx_queue++; + lp->tx_queue_len += length + 2; + } + lp->tx_buf_busy = 0; + + if(lp->tx_started == 0) { + /* If the transmitter is idle..always trigger a transmit */ + outb(TX_START | lp->tx_queue, ioaddr + TRANSMIT_START_REG); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->tx_started = 1; + netif_wake_queue(dev); + } + else if(lp->tx_queue_len < lp->tx_buf_size - (ETH_FRAME_LEN + 2)) { + /* There is still more room for one more packet in tx buffer */ + netif_wake_queue(dev); + } + + spin_unlock_irqrestore(&lp->lock, flags); + + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + /* Turn TX interrupts back on */ + /* outb(TX_INTR_DONE | TX_INTR_16_COL, ioaddr + TX_INTR_REG); */ + status = 0; + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static void eth16i_rx(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + int boguscount = MAX_RX_LOOP; + + /* Loop until all packets have been read */ + while( (inb(ioaddr + RECEIVE_MODE_REG) & RX_BUFFER_EMPTY) == 0) { + + /* Read status byte from receive buffer */ + ushort status = inw(ioaddr + DATAPORT); + + /* Get the size of the packet from receive buffer */ + ushort pkt_len = inw(ioaddr + DATAPORT); + + if(eth16i_debug > 4) + printk(KERN_DEBUG "%s: Receiving packet mode %02x status %04x.\n", + dev->name, + inb(ioaddr + RECEIVE_MODE_REG), status); + + if( !(status & PKT_GOOD) ) { + dev->stats.rx_errors++; + + if( (pkt_len < ETH_ZLEN) || (pkt_len > ETH_FRAME_LEN) ) { + dev->stats.rx_length_errors++; + eth16i_reset(dev); + return; + } + else { + eth16i_skip_packet(dev); + dev->stats.rx_dropped++; + } + } + else { /* Ok so now we should have a good packet */ + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len + 3); + if( skb == NULL ) { + printk(KERN_WARNING "%s: Could'n allocate memory for packet (len %d)\n", + dev->name, pkt_len); + eth16i_skip_packet(dev); + dev->stats.rx_dropped++; + break; + } + + skb_reserve(skb,2); + + /* + Now let's get the packet out of buffer. + size is (pkt_len + 1) >> 1, cause we are now reading words + and it have to be even aligned. + */ + + if(ioaddr < 0x1000) + insw(ioaddr + DATAPORT, skb_put(skb, pkt_len), + (pkt_len + 1) >> 1); + else { + unsigned char *buf = skb_put(skb, pkt_len); + unsigned char frag = pkt_len % 4; + + insl(ioaddr + DATAPORT, buf, pkt_len >> 2); + + if(frag != 0) { + unsigned short rest[2]; + rest[0] = inw( ioaddr + DATAPORT ); + if(frag == 3) + rest[1] = inw( ioaddr + DATAPORT ); + + memcpy(buf + (pkt_len & 0xfffc), (char *)rest, frag); + } + } + + skb->protocol=eth_type_trans(skb, dev); + + if( eth16i_debug > 5 ) { + int i; + printk(KERN_DEBUG "%s: Received packet of length %d.\n", + dev->name, pkt_len); + for(i = 0; i < 14; i++) + printk(KERN_DEBUG " %02x", skb->data[i]); + printk(KERN_DEBUG ".\n"); + } + netif_rx(skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += pkt_len; + + } /* else */ + + if(--boguscount <= 0) + break; + + } /* while */ +} + +static irqreturn_t eth16i_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct eth16i_local *lp; + int ioaddr = 0, status; + int handled = 0; + + ioaddr = dev->base_addr; + lp = netdev_priv(dev); + + /* Turn off all interrupts from adapter */ + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + /* eth16i_tx won't be called */ + spin_lock(&lp->lock); + + status = inw(ioaddr + TX_STATUS_REG); /* Get the status */ + outw(status, ioaddr + TX_STATUS_REG); /* Clear status bits */ + + if (status) + handled = 1; + + if(eth16i_debug > 3) + printk(KERN_DEBUG "%s: Interrupt with status %04x.\n", dev->name, status); + + if( status & 0x7f00 ) { + + dev->stats.rx_errors++; + + if(status & (BUS_RD_ERR << 8) ) + printk(KERN_WARNING "%s: Bus read error.\n",dev->name); + if(status & (SHORT_PKT_ERR << 8) ) dev->stats.rx_length_errors++; + if(status & (ALIGN_ERR << 8) ) dev->stats.rx_frame_errors++; + if(status & (CRC_ERR << 8) ) dev->stats.rx_crc_errors++; + if(status & (RX_BUF_OVERFLOW << 8) ) dev->stats.rx_over_errors++; + } + if( status & 0x001a) { + + dev->stats.tx_errors++; + + if(status & CR_LOST) dev->stats.tx_carrier_errors++; + if(status & TX_JABBER_ERR) dev->stats.tx_window_errors++; + +#if 0 + if(status & COLLISION) { + dev->stats.collisions += + ((inb(ioaddr+TRANSMIT_MODE_REG) & 0xF0) >> 4); + } +#endif + if(status & COLLISIONS_16) { + if(lp->col_16 < MAX_COL_16) { + lp->col_16++; + dev->stats.collisions++; + /* Resume transmitting, skip failed packet */ + outb(0x02, ioaddr + COL_16_REG); + } + else { + printk(KERN_WARNING "%s: bailing out due to many consecutive 16-in-a-row collisions. Network cable problem?\n", dev->name); + } + } + } + + if( status & 0x00ff ) { /* Let's check the transmit status reg */ + + if(status & TX_DONE) { /* The transmit has been done */ + dev->stats.tx_packets = lp->tx_buffered_packets; + dev->stats.tx_bytes += lp->tx_buffered_bytes; + lp->col_16 = 0; + + if(lp->tx_queue) { /* Is there still packets ? */ + /* There was packet(s) so start transmitting and write also + how many packets there is to be sended */ + outb(TX_START | lp->tx_queue, ioaddr + TRANSMIT_START_REG); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->tx_started = 1; + } + else { + lp->tx_started = 0; + } + netif_wake_queue(dev); + } + } + + if( ( status & 0x8000 ) || + ( (inb(ioaddr + RECEIVE_MODE_REG) & RX_BUFFER_EMPTY) == 0) ) { + eth16i_rx(dev); /* We have packet in receive buffer */ + } + + /* Turn interrupts back on */ + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + + if(lp->tx_queue_len < lp->tx_buf_size - (ETH_FRAME_LEN + 2)) { + /* There is still more room for one more packet in tx buffer */ + netif_wake_queue(dev); + } + + spin_unlock(&lp->lock); + + return IRQ_RETVAL(handled); +} + +static void eth16i_skip_packet(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + + inw(ioaddr + DATAPORT); + inw(ioaddr + DATAPORT); + inw(ioaddr + DATAPORT); + + outb(SKIP_RX_PACKET, ioaddr + FILTER_SELF_RX_REG); + while( inb( ioaddr + FILTER_SELF_RX_REG ) != 0); +} + +static void eth16i_reset(struct net_device *dev) +{ + struct eth16i_local *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + + if(eth16i_debug > 1) + printk(KERN_DEBUG "%s: Resetting device.\n", dev->name); + + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + outw(0xffff, ioaddr + TX_STATUS_REG); + eth16i_select_regbank(2, ioaddr); + + lp->tx_started = 0; + lp->tx_buf_busy = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + BITCLR(ioaddr + CONFIG_REG_0, DLC_EN); +} + +static void eth16i_multicast(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + + if (!netdev_mc_empty(dev) || dev->flags&(IFF_ALLMULTI|IFF_PROMISC)) + { + outb(3, ioaddr + RECEIVE_MODE_REG); + } else { + outb(2, ioaddr + RECEIVE_MODE_REG); + } +} + +static void eth16i_select_regbank(unsigned char banknbr, int ioaddr) +{ + unsigned char data; + + data = inb(ioaddr + CONFIG_REG_1); + outb( ((data & 0xF3) | ( (banknbr & 0x03) << 2)), ioaddr + CONFIG_REG_1); +} + +#ifdef MODULE + +static ushort eth16i_parse_mediatype(const char* s) +{ + if(!s) + return E_PORT_FROM_EPROM; + + if (!strncmp(s, "bnc", 3)) + return E_PORT_BNC; + else if (!strncmp(s, "tp", 2)) + return E_PORT_TP; + else if (!strncmp(s, "dix", 3)) + return E_PORT_DIX; + else if (!strncmp(s, "auto", 4)) + return E_PORT_AUTO; + else + return E_PORT_FROM_EPROM; +} + +#define MAX_ETH16I_CARDS 4 /* Max number of Eth16i cards per module */ + +static struct net_device *dev_eth16i[MAX_ETH16I_CARDS]; +static int io[MAX_ETH16I_CARDS]; +#if 0 +static int irq[MAX_ETH16I_CARDS]; +#endif +static char* mediatype[MAX_ETH16I_CARDS]; +static int debug = -1; + +MODULE_AUTHOR("Mika Kuoppala <miku@iki.fi>"); +MODULE_DESCRIPTION("ICL EtherTeam 16i/32 driver"); +MODULE_LICENSE("GPL"); + + +module_param_array(io, int, NULL, 0); +MODULE_PARM_DESC(io, "eth16i I/O base address(es)"); + +#if 0 +module_param_array(irq, int, NULL, 0); +MODULE_PARM_DESC(irq, "eth16i interrupt request number"); +#endif + +module_param_array(mediatype, charp, NULL, 0); +MODULE_PARM_DESC(mediatype, "eth16i media type of interface(s) (bnc,tp,dix,auto,eprom)"); + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "eth16i debug level (0-6)"); + +int __init init_module(void) +{ + int this_dev, found = 0; + struct net_device *dev; + + for (this_dev = 0; this_dev < MAX_ETH16I_CARDS; this_dev++) { + dev = alloc_etherdev(sizeof(struct eth16i_local)); + if (!dev) + break; + + dev->base_addr = io[this_dev]; + + if(debug != -1) + eth16i_debug = debug; + + if(eth16i_debug > 1) + printk(KERN_NOTICE "eth16i(%d): interface type %s\n", this_dev, mediatype[this_dev] ? mediatype[this_dev] : "none" ); + + dev->if_port = eth16i_parse_mediatype(mediatype[this_dev]); + + if(io[this_dev] == 0) { + if (this_dev != 0) { /* Only autoprobe 1st one */ + free_netdev(dev); + break; + } + + printk(KERN_NOTICE "eth16i.c: Presently autoprobing (not recommended) for a single card.\n"); + } + + if (do_eth16i_probe(dev) == 0) { + dev_eth16i[found++] = dev; + continue; + } + printk(KERN_WARNING "eth16i.c No Eth16i card found (i/o = 0x%x).\n", + io[this_dev]); + free_netdev(dev); + break; + } + if (found) + return 0; + return -ENXIO; +} + +void __exit cleanup_module(void) +{ + int this_dev; + + for(this_dev = 0; this_dev < MAX_ETH16I_CARDS; this_dev++) { + struct net_device *dev = dev_eth16i[this_dev]; + + if (netdev_priv(dev)) { + unregister_netdev(dev); + free_irq(dev->irq, dev); + release_region(dev->base_addr, ETH16I_IO_EXTENT); + free_netdev(dev); + } + } +} +#endif /* MODULE */ |