patch-2.1.25 linux/drivers/net/cs89x0.c
Next file: linux/drivers/net/cs89x0.h
Previous file: linux/drivers/net/bpqether.c
Back to the patch index
Back to the overall index
- Lines: 1183
- Date:
Sun Feb 2 15:18:36 1997
- Orig file:
v2.1.24/linux/drivers/net/cs89x0.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v2.1.24/linux/drivers/net/cs89x0.c linux/drivers/net/cs89x0.c
@@ -0,0 +1,1182 @@
+/* cs89x0.c: A Crystal Semiconductor CS89[02]0 driver for linux. */
+/*
+ Written 1996 by Russell Nelson, with reference to skeleton.c
+ written 1993-1994 by Donald Becker.
+
+ This software may be used and distributed according to the terms
+ of the GNU Public License, incorporated herein by reference.
+
+ The author may be reached at nelson@crynwr.com, Crynwr
+ Software, 11 Grant St., Potsdam, NY 13676
+
+ Changelog:
+
+ Mike Cruse : mcruse@cti-ltd.com
+ : Changes for Linux 2.0 compatibility.
+ : Added dev_id parameter in net_interrupt(),
+ : request_irq() and free_irq(). Just NULL for now.
+
+ Mike Cruse : Added MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT macros
+ : in net_open() and net_close() so kerneld would know
+ : that the module is in use and wouldn't eject the
+ : driver prematurely.
+
+ Mike Cruse : Rewrote init_module() and cleanup_module using 8390.c
+ : as an example. Disabled autoprobing in init_module(),
+ : not a good thing to do to other devices while Linux
+ : is running from all accounts.
+*/
+
+static char *version =
+"cs89x0.c:v1.02 11/26/96 Russell Nelson <nelson@crynwr.com>\n";
+
+/* ======================= configure the driver here ======================= */
+
+/* we also support 1.2.13 */
+#define SUPPORTS_1_2_13 0
+
+
+/* use 0 for production, 1 for verification, >2 for debug */
+#ifndef NET_DEBUG
+#define NET_DEBUG 2
+#endif
+
+/* ======================= end of configuration ======================= */
+
+
+/* Always include 'config.h' first in case the user wants to turn on
+ or override something. */
+#include <linux/config.h>
+#ifdef MODULE
+#include <linux/module.h>
+#include <linux/version.h>
+#else
+#define MOD_INC_USE_COUNT
+#define MOD_DEC_USE_COUNT
+#endif
+
+#define PRINTK(x) printk x
+
+/*
+ Sources:
+
+ Crynwr packet driver epktisa.
+
+ Crystal Semiconductor data sheets.
+
+*/
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/malloc.h>
+#include <linux/string.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <linux/errno.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include "cs89x0.h"
+
+/* First, a few definitions that the brave might change. */
+/* A zero-terminated list of I/O addresses to be probed. */
+static unsigned int netcard_portlist[] =
+ { 0x300, 0x320, 0x340, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0};
+
+static unsigned int net_debug = NET_DEBUG;
+
+/* The number of low I/O ports used by the ethercard. */
+#define NETCARD_IO_EXTENT 16
+
+/* Information that need to be kept for each board. */
+struct net_local {
+ struct net_device_stats stats;
+ int chip_type; /* one of: CS8900, CS8920, CS8920M */
+ char chip_revision; /* revision letter of the chip ('A'...) */
+ int send_cmd; /* the propercommand used to send a packet. */
+ int auto_neg_cnf;
+ int adapter_cnf;
+ int isa_config;
+ int irq_map;
+ int rx_mode;
+ int curr_rx_cfg;
+ int linectl;
+ int send_underrun; /* keep track of how many underruns in a row we get */
+ struct sk_buff *skb;
+};
+
+/* Index to functions, as function prototypes. */
+
+extern int cs89x0_probe(struct device *dev);
+
+static int cs89x0_probe1(struct device *dev, int ioaddr);
+static int net_open(struct device *dev);
+static int net_send_packet(struct sk_buff *skb, struct device *dev);
+#if SUPPORTS_1_2_13
+static void net_interrupt(int irq, struct pt_regs *regs);
+static void set_multicast_list(struct device *dev, int num_addrs, void *addrs);
+#else
+static void net_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static void set_multicast_list(struct device *dev);
+#endif
+static void net_rx(struct device *dev);
+static int net_close(struct device *dev);
+static struct net_device_stats *net_get_stats(struct device *dev);
+static void reset_chip(struct device *dev);
+static int get_eeprom_data(struct device *dev, int off, int len, int *buffer);
+static int get_eeprom_cksum(int off, int len, int *buffer);
+static int set_mac_address(struct device *dev, void *addr);
+
+
+/* Example routines you must write ;->. */
+#define tx_done(dev) 1
+
+
+/* Check for a network adaptor of this type, and return '0' iff one exists.
+ If dev->base_addr == 0, probe all likely locations.
+ If dev->base_addr == 1, always return failure.
+ If dev->base_addr == 2, allocate space for the device and return success
+ (detachable devices only).
+ */
+#ifdef HAVE_DEVLIST
+/* Support for a alternate probe manager, which will eliminate the
+ boilerplate below. */
+struct netdev_entry netcard_drv =
+{"netcard", cs89x0_probe1, NETCARD_IO_EXTENT, netcard_portlist};
+#else
+int
+cs89x0_probe(struct device *dev)
+{
+ int i;
+ int base_addr = dev ? dev->base_addr : 0;
+
+ if (base_addr > 0x1ff) /* Check a single specified location. */
+ return cs89x0_probe1(dev, base_addr);
+ else if (base_addr != 0) /* Don't probe at all. */
+ return ENXIO;
+
+ for (i = 0; netcard_portlist[i]; i++) {
+ int ioaddr = netcard_portlist[i];
+ if (check_region(ioaddr, NETCARD_IO_EXTENT))
+ continue;
+ if (cs89x0_probe1(dev, ioaddr) == 0)
+ return 0;
+ }
+ printk("cs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP\n");
+ return ENODEV;
+}
+#endif
+
+int inline
+readreg(struct device *dev, int portno)
+{
+ outw(portno, dev->base_addr + ADD_PORT);
+ return inw(dev->base_addr + DATA_PORT);
+}
+
+void inline
+writereg(struct device *dev, int portno, int value)
+{
+ outw(portno, dev->base_addr + ADD_PORT);
+ outw(value, dev->base_addr + DATA_PORT);
+}
+
+
+int inline
+readword(struct device *dev, int portno)
+{
+ return inw(dev->base_addr + portno);
+}
+
+void inline
+writeword(struct device *dev, int portno, int value)
+{
+ outw(value, dev->base_addr + portno);
+}
+
+int
+wait_eeprom_ready(struct device *dev)
+{
+ int timeout = jiffies;
+ /* check to see if the EEPROM is ready, a timeout is used -
+ just in case EEPROM is ready when SI_BUSY in the
+ PP_SelfST is clear */
+ while(readreg(dev, PP_SelfST) & SI_BUSY)
+ if (jiffies - timeout >= 40)
+ return -1;
+ return 0;
+}
+
+int
+get_eeprom_data(struct device *dev, int off, int len, int *buffer)
+{
+ int i;
+
+ if (net_debug > 3) printk("EEPROM data from %x for %x:\n",off,len);
+ for (i = 0; i < len; i++) {
+ if (wait_eeprom_ready(dev) < 0) return -1;
+ /* Now send the EEPROM read command and EEPROM location to read */
+ writereg(dev, PP_EECMD, (off + i) | EEPROM_READ_CMD);
+ if (wait_eeprom_ready(dev) < 0) return -1;
+ buffer[i] = readreg(dev, PP_EEData);
+ if (net_debug > 3) printk("%04x ", buffer[i]);
+ }
+ if (net_debug > 3) printk("\n");
+ return 0;
+}
+
+int
+get_eeprom_cksum(int off, int len, int *buffer)
+{
+ int i, cksum;
+
+ cksum = 0;
+ for (i = 0; i < len; i++)
+ cksum += buffer[i];
+ cksum &= 0xffff;
+ if (cksum == 0)
+ return 0;
+ return -1;
+}
+
+/* This is the real probe routine. Linux has a history of friendly device
+ probes on the ISA bus. A good device probes avoids doing writes, and
+ verifies that the correct device exists and functions. */
+
+static int cs89x0_probe1(struct device *dev, int ioaddr)
+{
+ struct net_local *lp;
+ static unsigned version_printed = 0;
+ int i;
+ unsigned rev_type = 0;
+ int eeprom_buff[CHKSUM_LEN];
+
+ /* Initialize the device structure. */
+ if (dev->priv == NULL) {
+ dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
+ memset(dev->priv, 0, sizeof(struct net_local));
+ }
+ lp = (struct net_local *)dev->priv;
+
+ /* if they give us an odd I/O address, then do ONE write to
+ the address port, to get it back to address zero, where we
+ expect to find the EISA signature word. */
+ if (ioaddr & 1) {
+ ioaddr &= ~1;
+ if ((inw(ioaddr + ADD_PORT) & ADD_MASK) != ADD_SIG)
+ return ENODEV;
+ outw(PP_ChipID, ioaddr + ADD_PORT);
+ }
+
+ if (inw(ioaddr + DATA_PORT) != CHIP_EISA_ID_SIG)
+ return ENODEV;
+
+ /* Fill in the 'dev' fields. */
+ dev->base_addr = ioaddr;
+
+ /* get the chip type */
+ rev_type = readreg(dev, PRODUCT_ID_ADD);
+ lp->chip_type = rev_type &~ REVISON_BITS;
+ lp->chip_revision = ((rev_type & REVISON_BITS) >> 8) + 'A';
+
+ /* Check the chip type and revision in order to set the correct send command
+ CS8920 revision C and CS8900 revision F can use the faster send. */
+ lp->send_cmd = TX_AFTER_381;
+ if (lp->chip_type == CS8900 && lp->chip_revision >= 'F')
+ lp->send_cmd = TX_NOW;
+ if (lp->chip_type != CS8900 && lp->chip_revision >= 'C')
+ lp->send_cmd = TX_NOW;
+
+ if (net_debug && version_printed++ == 0)
+ printk(version);
+
+ printk("%s: cs89%c0%s rev %c found at %#3lx",
+ dev->name,
+ lp->chip_type==CS8900?'0':'2',
+ lp->chip_type==CS8920M?"M":"",
+ lp->chip_revision,
+ dev->base_addr);
+
+ reset_chip(dev);
+
+ /* First check to see if an EEPROM is attached*/
+ if ((readreg(dev, PP_SelfST) & EEPROM_PRESENT) == 0)
+ printk("\ncs89x0: No EEPROM, relying on command line....\n");
+ else if (get_eeprom_data(dev, START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff) < 0) {
+ printk("\ncs89x0: EEPROM read failed, relying on command line.\n");
+ } else if (get_eeprom_cksum(START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff) < 0) {
+ printk("\ncs89x0: EEPROM checksum bad, relyong on command line\n");
+ } else {
+ /* get transmission control word but keep the autonegotiation bits */
+ if (!lp->auto_neg_cnf) lp->auto_neg_cnf = eeprom_buff[AUTO_NEG_CNF_OFFSET/2];
+ /* Store adapter configuration */
+ if (!lp->adapter_cnf) lp->adapter_cnf = eeprom_buff[ADAPTER_CNF_OFFSET/2];
+ /* Store ISA configuration */
+ lp->isa_config = eeprom_buff[ISA_CNF_OFFSET/2];
+ /* store the initial memory base address */
+ dev->mem_start = eeprom_buff[PACKET_PAGE_OFFSET/2] << 8;
+ for (i = 0; i < ETH_ALEN/2; i++) {
+ dev->dev_addr[i*2] = eeprom_buff[i];
+ dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;
+ }
+ }
+
+
+ printk(" media %s%s%s",
+ (lp->adapter_cnf & A_CNF_10B_T)?"RJ-45,":"",
+ (lp->adapter_cnf & A_CNF_AUI)?"AUI,":"",
+ (lp->adapter_cnf & A_CNF_10B_2)?"BNC,":"");
+
+ lp->irq_map = 0xffff;
+
+ /* If this is a CS8900 then no pnp soft */
+ if (lp->chip_type != CS8900 &&
+ /* Check if the ISA IRQ has been set */
+ (i = readreg(dev, PP_CS8920_ISAINT) & 0xff,
+ (i != 0 && i < CS8920_NO_INTS))) {
+ if (!dev->irq)
+ dev->irq = i;
+ } else {
+ i = lp->isa_config & INT_NO_MASK;
+ if (lp->chip_type == CS8900) {
+ /* the table that follows is dependent upon how you wired up your cs8900
+ * in your system. The table is the same as the cs8900 engineering demo
+ * board. irq_map also depends on the contents of the table. Also see
+ * write_irq, which is the reverse mapping of the table below. */
+ switch(i) {
+ case 0: i = 10; break;
+ case 1: i = 11; break;
+ case 2: i = 12; break;
+ case 3: i = 5; break;
+ default: printk("\ncs89x0: bug: isa_config is %d\n", i);
+ }
+ lp->irq_map = CS8900_IRQ_MAP; /* fixed IRQ map for CS8900 */
+ } else {
+ int irq_map_buff[IRQ_MAP_LEN/2];
+
+ if (get_eeprom_data(dev, IRQ_MAP_EEPROM_DATA,
+ IRQ_MAP_LEN/2,
+ irq_map_buff) >= 0) {
+ if ((irq_map_buff[0] & 0xff) == PNP_IRQ_FRMT)
+ lp->irq_map = (irq_map_buff[0]>>8) | (irq_map_buff[1] << 8);
+ }
+ }
+ if (!dev->irq)
+ dev->irq = i;
+ }
+
+ printk(" IRQ %d", dev->irq);
+
+
+ /* print the ethernet address. */
+ for (i = 0; i < ETH_ALEN; i++)
+ printk(" %2.2x", dev->dev_addr[i]);
+
+ /* Grab the region so we can find another board if autoIRQ fails. */
+ request_region(ioaddr, NETCARD_IO_EXTENT,"cs89x0");
+
+ dev->open = net_open;
+ dev->stop = net_close;
+ dev->hard_start_xmit = net_send_packet;
+ dev->get_stats = net_get_stats;
+ dev->set_multicast_list = &set_multicast_list;
+ dev->set_mac_address = &set_mac_address;
+
+ /* Fill in the fields of the device structure with ethernet values. */
+ ether_setup(dev);
+
+ printk("\n");
+ return 0;
+}
+
+
+
+void
+reset_chip(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ int ioaddr = dev->base_addr;
+ int reset_start_time;
+
+ writereg(dev, PP_SelfCTL, readreg(dev, PP_SelfCTL) | POWER_ON_RESET);
+
+ /* wait 30 ms */
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies + 3;
+ schedule();
+
+ if (lp->chip_type != CS8900) {
+ /* Hardware problem requires PNP registers to be reconfigured after a reset */
+ outw(PP_CS8920_ISAINT, ioaddr + ADD_PORT);
+ outb(dev->irq, ioaddr + DATA_PORT);
+ outb(0, ioaddr + DATA_PORT + 1);
+
+ outw(PP_CS8920_ISAMemB, ioaddr + ADD_PORT);
+ outb((dev->mem_start >> 8) & 0xff, ioaddr + DATA_PORT);
+ outb((dev->mem_start >> 24) & 0xff, ioaddr + DATA_PORT + 1);
+ }
+ /* Wait until the chip is reset */
+ reset_start_time = jiffies;
+ while( (readreg(dev, PP_SelfST) & INIT_DONE) == 0 && jiffies - reset_start_time < 2)
+ ;
+}
+
+
+void
+control_dc_dc(struct device *dev, int on_not_off)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned int selfcontrol;
+ int timenow = jiffies;
+ /* control the DC to DC convertor in the SelfControl register. */
+
+ selfcontrol = HCB1_ENBL; /* Enable the HCB1 bit as an output */
+ if (((lp->adapter_cnf & A_CNF_DC_DC_POLARITY) != 0) ^ on_not_off)
+ selfcontrol |= HCB1;
+ else
+ selfcontrol &= ~HCB1;
+ writereg(dev, PP_SelfCTL, selfcontrol);
+
+ /* Wait for the DC/DC converter to power up - 500ms */
+ while (jiffies - timenow < 100)
+ ;
+
+}
+
+static int
+detect_tp(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ int timenow = jiffies;
+
+ if (net_debug > 1) printk("%s: Attempting TP\n", dev->name);
+
+ /* If connected to another full duplex capable 10-Base-T card the link pulses
+ seem to be lost when the auto detect bit in the LineCTL is set.
+ To overcome this the auto detect bit will be cleared whilst testing the
+ 10-Base-T interface. This would not be necessary for the sparrow chip but
+ is simpler to do it anyway. */
+ writereg(dev, PP_LineCTL, lp->linectl &~ AUI_ONLY);
+ control_dc_dc(dev, 0);
+
+ /* Delay for the hardware to work out if the TP cable is present - 150ms */
+ for (timenow = jiffies; jiffies - timenow < 15; )
+ ;
+ if ((readreg(dev, PP_LineST) & LINK_OK) == 0)
+ return 0;
+
+ if (lp->chip_type != CS8900) {
+
+ writereg(dev, PP_AutoNegCTL, lp->auto_neg_cnf & AUTO_NEG_MASK);
+
+ if ((lp->auto_neg_cnf & AUTO_NEG_BITS) == AUTO_NEG_ENABLE) {
+ printk("%s: negotiating duplex...\n",dev->name);
+ while (readreg(dev, PP_AutoNegST) & AUTO_NEG_BUSY) {
+ if (jiffies - timenow > 4000) {
+ printk("**** Full / half duplex auto-negotiation timed out ****\n");
+ break;
+ }
+ }
+ }
+ if (readreg(dev, PP_AutoNegST) & FDX_ACTIVE)
+ printk("%s: using full duplex\n", dev->name);
+ else
+ printk("%s: using half duplex\n", dev->name);
+ }
+
+ return A_CNF_MEDIA_10B_T;
+}
+
+/* send a test packet - return true if carrier bits are ok */
+int
+send_test_pkt(struct device *dev)
+{
+ int ioaddr = dev->base_addr;
+ char test_packet[] = { 0,0,0,0,0,0, 0,0,0,0,0,0,
+ 0, 46, /* A 46 in network order */
+ 0, 0, /* DSAP=0 & SSAP=0 fields */
+ 0xf3, 0 /* Control (Test Req + P bit set) */ };
+ long timenow = jiffies;
+
+ writereg(dev, PP_LineCTL, readreg(dev, PP_LineCTL) | SERIAL_TX_ON);
+
+ memcpy(test_packet, dev->dev_addr, ETH_ALEN);
+ memcpy(test_packet+ETH_ALEN, dev->dev_addr, ETH_ALEN);
+
+ outw(TX_AFTER_ALL, ioaddr + TX_CMD_PORT);
+ outw(ETH_ZLEN, ioaddr + TX_LEN_PORT);
+
+ /* Test to see if the chip has allocated memory for the packet */
+ while (jiffies - timenow < 5)
+ if (readreg(dev, PP_BusST) & READY_FOR_TX_NOW)
+ break;
+ if (jiffies - timenow >= 5)
+ return 0; /* this shouldn't happen */
+
+ /* Write the contents of the packet */
+ if (dev->mem_start) {
+ memcpy((void *)dev->mem_start + PP_TxFrame, test_packet, ETH_ZLEN);
+ } else {
+ outsw(ioaddr + TX_FRAME_PORT,test_packet,(ETH_ZLEN+1) >>1);
+ }
+
+ if (net_debug > 1) printk("Sending test packet ");
+ /* wait a couple of jiffies for packet to be received */
+ for (timenow = jiffies; jiffies - timenow < 3; )
+ ;
+ if ((readreg(dev, PP_TxEvent) & TX_SEND_OK_BITS) == TX_OK) {
+ if (net_debug > 1) printk("succeeded\n");
+ return 1;
+ }
+ if (net_debug > 1) printk("failed\n");
+ return 0;
+}
+
+
+int
+detect_aui(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ if (net_debug > 1) printk("%s: Attempting AUI\n", dev->name);
+ control_dc_dc(dev, 0);
+
+ writereg(dev, PP_LineCTL, (lp->linectl &~ AUTO_AUI_10BASET) | AUI_ONLY);
+
+ if (send_test_pkt(dev))
+ return A_CNF_MEDIA_AUI;
+ else
+ return 0;
+}
+
+int
+detect_bnc(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ if (net_debug > 1) printk("%s: Attempting BNC\n", dev->name);
+ control_dc_dc(dev, 1);
+
+ writereg(dev, PP_LineCTL, (lp->linectl &~ AUTO_AUI_10BASET) | AUI_ONLY);
+
+ if (send_test_pkt(dev))
+ return A_CNF_MEDIA_10B_2;
+ else
+ return 0;
+}
+
+
+void
+write_irq(struct device *dev, int chip_type, int irq)
+{
+ int i;
+
+ if (chip_type == CS8900) {
+ switch(irq) {
+ case 10: i = 0; break;
+ case 11: i = 1; break;
+ case 12: i = 2; break;
+ case 5: i = 3; break;
+ default: i = 3; break;
+ }
+ writereg(dev, PP_CS8900_ISAINT, i);
+ } else {
+ writereg(dev, PP_CS8920_ISAINT, irq);
+ }
+}
+
+/* Open/initialize the board. This is called (in the current kernel)
+ sometime after booting when the 'ifconfig' program is run.
+
+ This routine should set everything up anew at each open, even
+ registers that "should" only need to be set once at boot, so that
+ there is non-reboot way to recover if something goes wrong.
+ */
+static int
+net_open(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ int result = 0;
+ int i;
+
+ if (dev->irq < 2) {
+ /* Allow interrupts to be generated by the chip */
+ writereg(dev, PP_BusCTL, ENABLE_IRQ | MEMORY_ON);
+ for (i = 2; i < CS8920_NO_INTS; i++) if ((1 << dev->irq) & lp->irq_map) {
+#if SUPPORTS_1_2_13
+ if (request_irq (i, NULL, 0, "bogus") != -EBUSY) {
+#else
+ if (request_irq (i, NULL, 0, "bogus", NULL) != -EBUSY) {
+#endif
+#if 0
+ /* Twinkle the interrupt, and check if it's seen. */
+ autoirq_setup(0);
+ write_irq(dev, lp->chip_type, i);
+ writereg(dev, PP_BufCFG, GENERATE_SW_INTERRUPT);
+ if (i == autoirq_report(0) /* It's a good IRQ line! */
+ && request_irq (dev->irq = i, &net_interrupt, 0, "cs89x0") == 0)
+ break;
+#else
+ write_irq(dev, lp->chip_type, i);
+ writereg(dev, PP_BufCFG, GENERATE_SW_INTERRUPT);
+#if SUPPORTS_1_2_13
+ if (request_irq (dev->irq = i, &net_interrupt, 0, "cs89x0") == 0)
+#else
+ if (request_irq (dev->irq = i, &net_interrupt, 0, "cs89x0", NULL) == 0)
+#endif
+ break;
+#endif
+ }
+ }
+
+
+ if (i >= CS8920_NO_INTS) {
+ writereg(dev, PP_BusCTL, 0); /* disable interrupts. */
+ return -EAGAIN;
+ }
+ } else {
+ if (((1 << dev->irq) & lp->irq_map) == 0) {
+ printk("%s: IRQ %d is not in our map of allowable IRQs, which is %x\n",
+ dev->name, dev->irq, lp->irq_map);
+ return -EAGAIN;
+ }
+ writereg(dev, PP_BusCTL, ENABLE_IRQ | MEMORY_ON);
+ write_irq(dev, lp->chip_type, dev->irq);
+#if SUPPORTS_1_2_13
+ if (request_irq(dev->irq, &net_interrupt, 0, "cs89x0")) {
+#else
+ if (request_irq(dev->irq, &net_interrupt, 0, "cs89x0", NULL)) {
+#endif
+ return -EAGAIN;
+ }
+ }
+
+ irq2dev_map[dev->irq] = dev;
+
+ /* set the Ethernet address */
+ for (i=0; i < ETH_ALEN/2; i++)
+ writereg(dev, PP_IA+i*2, dev->dev_addr[i*2] | (dev->dev_addr[i*2+1] << 8));
+
+ /* while we're testing the interface, leave interrupts disabled */
+ writereg(dev, PP_BusCTL, MEMORY_ON);
+
+ /* Set the LineCTL quintuplet based on adapter configuration read from EEPROM */
+ if ((lp->adapter_cnf & A_CNF_EXTND_10B_2) && (lp->adapter_cnf & A_CNF_LOW_RX_SQUELCH))
+ lp->linectl = LOW_RX_SQUELCH;
+ else
+ lp->linectl = 0;
+
+ /* check to make sure that they have the "right" hardware available */
+ switch(lp->adapter_cnf & A_CNF_MEDIA_TYPE) {
+ case A_CNF_MEDIA_10B_T: result = lp->adapter_cnf & A_CNF_10B_T; break;
+ case A_CNF_MEDIA_AUI: result = lp->adapter_cnf & A_CNF_AUI; break;
+ case A_CNF_MEDIA_10B_2: result = lp->adapter_cnf & A_CNF_10B_2; break;
+ default: result = lp->adapter_cnf & (A_CNF_10B_T | A_CNF_AUI | A_CNF_10B_2);
+ }
+ if (!result) {
+ printk("%s: EEPROM is configured for unavailable media\n", dev->name);
+ release_irq:
+ writereg(dev, PP_LineCTL, readreg(dev, PP_LineCTL) & ~(SERIAL_TX_ON | SERIAL_RX_ON));
+#if SUPPORTS_1_2_13
+ free_irq(dev->irq);
+#else
+ free_irq(dev->irq, NULL);
+#endif
+ irq2dev_map[dev->irq] = 0;
+ return -EAGAIN;
+ }
+
+ /* set the hardware to the configured choice */
+ switch(lp->adapter_cnf & A_CNF_MEDIA_TYPE) {
+ case A_CNF_MEDIA_10B_T:
+ result = detect_tp(dev);
+ if (!result) printk("%s: 10Base-T (RJ-45) has no cable\n", dev->name);
+ if (lp->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */
+ result = A_CNF_MEDIA_10B_T; /* Yes! I don't care if I see a link pulse */
+ break;
+ case A_CNF_MEDIA_AUI:
+ result = detect_aui(dev);
+ if (!result) printk("%s: 10Base-5 (AUI) has no cable\n", dev->name);
+ if (lp->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */
+ result = A_CNF_MEDIA_AUI; /* Yes! I don't care if I see a carrrier */
+ break;
+ case A_CNF_MEDIA_10B_2:
+ result = detect_bnc(dev);
+ if (!result) printk("%s: 10Base-2 (BNC) has no cable\n", dev->name);
+ if (lp->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */
+ result = A_CNF_MEDIA_10B_2; /* Yes! I don't care if I can xmit a packet */
+ break;
+ case A_CNF_MEDIA_AUTO:
+ writereg(dev, PP_LineCTL, lp->linectl | AUTO_AUI_10BASET);
+ if (lp->adapter_cnf & A_CNF_10B_T)
+ if ((result = detect_tp(dev)) != 0)
+ break;
+ if (lp->adapter_cnf & A_CNF_AUI)
+ if ((result = detect_aui(dev)) != 0)
+ break;
+ if (lp->adapter_cnf & A_CNF_10B_2)
+ if ((result = detect_bnc(dev)) != 0)
+ break;
+ printk("%s: no media detected\n", dev->name);
+ goto release_irq;
+ }
+ switch(result) {
+ case 0: printk("%s: no network cable attached to configured media\n", dev->name);
+ goto release_irq;
+ case A_CNF_MEDIA_10B_T: printk("%s: using 10Base-T (RJ-45)\n", dev->name);break;
+ case A_CNF_MEDIA_AUI: printk("%s: using 10Base-5 (AUI)\n", dev->name);break;
+ case A_CNF_MEDIA_10B_2: printk("%s: using 10Base-2 (BNC)\n", dev->name);break;
+ default: printk("%s: unexpected result was %x\n", dev->name, result); goto release_irq;
+ }
+
+ /* Turn on both receive and transmit operations */
+ writereg(dev, PP_LineCTL, readreg(dev, PP_LineCTL) | SERIAL_RX_ON | SERIAL_TX_ON);
+
+ /* Receive only error free packets addressed to this card */
+ lp->rx_mode = 0;
+ writereg(dev, PP_RxCTL, DEF_RX_ACCEPT);
+
+ lp->curr_rx_cfg = RX_OK_ENBL | RX_CRC_ERROR_ENBL;
+ if (lp->isa_config & STREAM_TRANSFER)
+ lp->curr_rx_cfg |= RX_STREAM_ENBL;
+
+ writereg(dev, PP_RxCFG, lp->curr_rx_cfg);
+
+ writereg(dev, PP_TxCFG, TX_LOST_CRS_ENBL | TX_SQE_ERROR_ENBL | TX_OK_ENBL |
+ TX_LATE_COL_ENBL | TX_JBR_ENBL | TX_ANY_COL_ENBL | TX_16_COL_ENBL);
+
+ writereg(dev, PP_BufCFG, READY_FOR_TX_ENBL | RX_MISS_COUNT_OVRFLOW_ENBL |
+ TX_COL_COUNT_OVRFLOW_ENBL | TX_UNDERRUN_ENBL);
+
+ /* now that we've got our act together, enable everything */
+ writereg(dev, PP_BusCTL, ENABLE_IRQ
+ );
+ dev->tbusy = 0;
+ dev->interrupt = 0;
+ dev->start = 1;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int
+net_send_packet(struct sk_buff *skb, struct device *dev)
+{
+ if (dev->tbusy) {
+ /* If we get here, some higher level has decided we are broken.
+ There should really be a "kick me" function call instead. */
+ int tickssofar = jiffies - dev->trans_start;
+ if (tickssofar < 5)
+ return 1;
+ if (net_debug > 0) printk("%s: transmit timed out, %s?\n", dev->name,
+ tx_done(dev) ? "IRQ conflict" : "network cable problem");
+ /* Try to restart the adaptor. */
+ dev->tbusy=0;
+ dev->trans_start = jiffies;
+ }
+
+ /* If some higher layer thinks we've missed an tx-done interrupt
+ we are passed NULL. Caution: dev_tint() handles the cli()/sti()
+ itself. */
+ if (skb == NULL) {
+ dev_tint(dev);
+ return 0;
+ }
+
+ /* Block a timer-based transmit from overlapping. This could better be
+ done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */
+ if (set_bit(0, (void*)&dev->tbusy) != 0)
+ printk("%s: Transmitter access conflict.\n", dev->name);
+ else {
+ struct net_local *lp = (struct net_local *)dev->priv;
+ short ioaddr = dev->base_addr;
+ unsigned long flags;
+
+ if (net_debug > 3)printk("%s: sent %ld byte packet of type %x\n", dev->name, skb->len, (skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]);
+
+ /* keep the upload from being interrupted, since we
+ ask the chip to start transmitting before the
+ whole packet has been completely uploaded. */
+ save_flags(flags);
+ cli();
+
+ /* initiate a transmit sequence */
+ outw(lp->send_cmd, ioaddr + TX_CMD_PORT);
+ outw(skb->len, ioaddr + TX_LEN_PORT);
+
+ /* Test to see if the chip has allocated memory for the packet */
+ if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == 0) {
+ /* Gasp! It hasn't. But that shouldn't happen since
+ we're waiting for TxOk, so return 1 and requeue this packet. */
+ restore_flags(flags);
+ return 1;
+ }
+
+ /* Write the contents of the packet */
+ outsw(ioaddr + TX_FRAME_PORT,skb->data,(skb->len+1) >>1);
+
+ restore_flags(flags);
+ dev->trans_start = jiffies;
+ }
+ dev_kfree_skb (skb, FREE_WRITE);
+
+ return 0;
+}
+
+/* The typical workload of the driver:
+ Handle the network interface interrupts. */
+static void
+#if SUPPORTS_1_2_13
+net_interrupt(int irq, struct pt_regs * regs)
+#else
+net_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+#endif
+{
+ struct device *dev = (struct device *)(irq2dev_map[irq]);
+ struct net_local *lp;
+ int ioaddr, status;
+
+ if (dev == NULL) {
+ printk ("net_interrupt(): irq %d for unknown device.\n", irq);
+ return;
+ }
+ if (dev->interrupt)
+ printk("%s: Re-entering the interrupt handler.\n", dev->name);
+ dev->interrupt = 1;
+
+ ioaddr = dev->base_addr;
+ lp = (struct net_local *)dev->priv;
+
+ /* we MUST read all the events out of the ISQ, otherwise we'll never
+ get interrupted again. As a consequence, we can't have any limit
+ on the number of times we loop in the interrupt handler. The
+ hardware guarantees that eventually we'll run out of events. Of
+ course, if you're on a slow machine, and packets are arriving
+ faster than you can read them off, you're screwed. Hasta la
+ vista, baby! */
+ while ((status = readword(dev, ISQ_PORT))) {
+ if (net_debug > 4)printk("%s: event=%04x\n", dev->name, status);
+ switch(status & ISQ_EVENT_MASK) {
+ case ISQ_RECEIVER_EVENT:
+ /* Got a packet(s). */
+ net_rx(dev);
+ break;
+ case ISQ_TRANSMITTER_EVENT:
+ lp->stats.tx_packets++;
+ dev->tbusy = 0;
+ mark_bh(NET_BH); /* Inform upper layers. */
+ if ((status & TX_OK) == 0) lp->stats.tx_errors++;
+ if (status & TX_LOST_CRS) lp->stats.tx_carrier_errors++;
+ if (status & TX_SQE_ERROR) lp->stats.tx_heartbeat_errors++;
+ if (status & TX_LATE_COL) lp->stats.tx_window_errors++;
+ if (status & TX_16_COL) lp->stats.tx_aborted_errors++;
+ break;
+ case ISQ_BUFFER_EVENT:
+ if (status & READY_FOR_TX) {
+ /* we tried to transmit a packet earlier,
+ but inexplicably ran out of buffers.
+ That shouldn't happen since we only ever
+ load one packet. Shrug. Do the right
+ thing anyway. */
+ dev->tbusy = 0;
+ mark_bh(NET_BH); /* Inform upper layers. */
+ }
+ if (status & TX_UNDERRUN) {
+ if (net_debug > 0) printk("%s: transmit underrun\n", dev->name);
+ lp->send_underrun++;
+ if (lp->send_underrun == 3) lp->send_cmd = TX_AFTER_381;
+ else if (lp->send_underrun == 6) lp->send_cmd = TX_AFTER_ALL;
+ }
+ break;
+ case ISQ_RX_MISS_EVENT:
+ lp->stats.rx_missed_errors += (status >>6);
+ break;
+ case ISQ_TX_COL_EVENT:
+ lp->stats.collisions += (status >>6);
+ break;
+ }
+ }
+ dev->interrupt = 0;
+ return;
+}
+
+/* We have a good packet(s), get it/them out of the buffers. */
+static void
+net_rx(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ int ioaddr = dev->base_addr;
+ struct sk_buff *skb;
+ int status, length;
+
+ status = inw(ioaddr + RX_FRAME_PORT);
+ length = inw(ioaddr + RX_FRAME_PORT);
+ if ((status & RX_OK) == 0) {
+ lp->stats.rx_errors++;
+ if (status & RX_RUNT) lp->stats.rx_length_errors++;
+ if (status & RX_EXTRA_DATA) lp->stats.rx_length_errors++;
+ if (status & RX_CRC_ERROR) if (!(status & (RX_EXTRA_DATA|RX_RUNT)))
+ /* per str 172 */
+ lp->stats.rx_crc_errors++;
+ if (status & RX_DRIBBLE) lp->stats.rx_frame_errors++;
+ return;
+ }
+
+ /* Malloc up new buffer. */
+ skb = alloc_skb(length, GFP_ATOMIC);
+ if (skb == NULL) {
+ printk("%s: Memory squeeze, dropping packet.\n", dev->name);
+ lp->stats.rx_dropped++;
+ return;
+ }
+ skb->len = length;
+ skb->dev = dev;
+
+ insw(ioaddr + RX_FRAME_PORT, skb->data, length >> 1);
+ if (length & 1)
+ skb->data[length-1] = inw(ioaddr + RX_FRAME_PORT);
+
+ if (net_debug > 3)printk("%s: received %d byte packet of type %x\n",
+ dev->name, length,
+ (skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]);
+#if !SUPPORTS_1_2_13
+ skb->protocol=eth_type_trans(skb,dev);
+#endif
+ netif_rx(skb);
+ lp->stats.rx_packets++;
+ return;
+}
+
+/* The inverse routine to net_open(). */
+static int
+net_close(struct device *dev)
+{
+
+ writereg(dev, PP_RxCFG, 0);
+ writereg(dev, PP_TxCFG, 0);
+ writereg(dev, PP_BufCFG, 0);
+ writereg(dev, PP_BusCTL, 0);
+
+ dev->start = 0;
+
+#if SUPPORTS_1_2_13
+ free_irq(dev->irq);
+#else
+ free_irq(dev->irq, NULL);
+#endif
+
+ irq2dev_map[dev->irq] = 0;
+
+ /* Update the statistics here. */
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+
+}
+
+/* Get the current statistics. This may be called with the card open or
+ closed. */
+static struct net_device_stats *
+net_get_stats(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ cli();
+ /* Update the statistics from the device registers. */
+ lp->stats.rx_missed_errors += (readreg(dev, PP_RxMiss) >> 6);
+ lp->stats.collisions += (readreg(dev, PP_TxCol) >> 6);
+ sti();
+
+ return &lp->stats;
+}
+
+#if SUPPORTS_1_2_13
+/* Set or clear the multicast filter for this adaptor.
+ num_addrs == -1 Promiscuous mode, receive all packets
+ num_addrs == 0 Normal mode, clear multicast list
+ num_addrs > 0 Multicast mode, receive normal and MC packets, and do
+ best-effort filtering.
+ */
+static void
+set_multicast_list(struct device *dev, int num_addrs, void *addrs)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ if (num_addrs == 0)
+ lp->rx_mode = 0;
+ else if (num_addrs > 0)
+ lp->rx_mode = RX_MULTCAST_ACCEPT;
+ else if (num_addrs == -1)
+ lp->rx_mode = RX_ALL_ACCEPT;
+
+ writereg(dev, PP_RxCTL, DEF_RX_ACCEPT | lp->rx_mode);
+
+ /* in promiscuous mode, we accept errored packets, so we have to enable interrupts on them also */
+ writereg(dev, PP_RxCFG, lp->curr_rx_cfg |
+ (lp->rx_mode == RX_ALL_ACCEPT? (RX_CRC_ERROR_ENBL|RX_RUNT_ENBL|RX_EXTRA_DATA_ENBL) : 0));
+
+}
+#else
+static void set_multicast_list(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ if(dev->flags&IFF_PROMISC)
+ {
+ lp->rx_mode = RX_ALL_ACCEPT;
+ }
+ else if((dev->flags&IFF_ALLMULTI)||dev->mc_list)
+ {
+ /* The multicast-accept list is initialized to accept-all, and we
+ rely on higher-level filtering for now. */
+ lp->rx_mode = RX_MULTCAST_ACCEPT;
+ }
+ else
+ lp->rx_mode = 0;
+
+ writereg(dev, PP_RxCTL, DEF_RX_ACCEPT | lp->rx_mode);
+
+ /* in promiscuous mode, we accept errored packets, so we have to enable interrupts on them also */
+ writereg(dev, PP_RxCFG, lp->curr_rx_cfg |
+ (lp->rx_mode == RX_ALL_ACCEPT? (RX_CRC_ERROR_ENBL|RX_RUNT_ENBL|RX_EXTRA_DATA_ENBL) : 0));
+}
+#endif
+
+
+static int
+set_mac_address(struct device *dev, void *addr)
+{
+ int i;
+ if (dev->start)
+ return -EBUSY;
+ printk("%s: Setting MAC address to ", dev->name);
+ for (i = 0; i < 6; i++)
+ printk(" %2.2x", dev->dev_addr[i] = ((unsigned char *)addr)[i]);
+ printk(".\n");
+ /* set the Ethernet address */
+ for (i=0; i < ETH_ALEN/2; i++)
+ writereg(dev, PP_IA+i*2, dev->dev_addr[i*2] | (dev->dev_addr[i*2+1] << 8));
+
+ return 0;
+}
+#ifdef MODULE
+#if SUPPORTS_1_2_13
+char kernel_version[] = UTS_RELEASE;
+#endif
+static char namespace[16] = "";
+static struct device dev_cs89x0 = {
+ NULL,
+ 0, 0, 0, 0,
+ 0, 0,
+ 0, 0, 0, NULL, NULL };
+
+int io=0;
+int irq=0;
+#endif
+#ifdef MODULE
+int debug=1;
+char *media="auto";
+char *duplex="f";
+
+/*
+* media=t - specify media type
+ or media=2
+ or media=aui
+ or medai=auto
+* duplex=f - specify forced half/full/autonegotiate duplex
+ or duplex=h
+ or duplex=auto
+* debug=# - debug level
+
+
+* Default Chip Configuration:
+ * DMA Burst = enabled
+ * IOCHRDY Enabled = enabled
+ * UseSA = enabled
+ * CS8900 defaults to half-duplex if not specified on command-line
+ * CS8920 defaults to autoneg if not specified on command-line
+ * Use reset defaults for other config parameters
+
+* Assumptions:
+ * media type specified is supported (circuitry is present)
+ * if memory address is > 1MB, then required mem decode hw is present
+ * if 10B-2, then agent other than driver will enable DC/DC converter
+ (hw or software util)
+
+
+*/
+
+int
+init_module(void)
+{
+ struct net_local *lp;
+
+ net_debug = debug;
+ dev_cs89x0.name = namespace;
+ dev_cs89x0.irq = irq;
+ dev_cs89x0.base_addr = io;
+ dev_cs89x0.init = cs89x0_probe;
+ dev_cs89x0.priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
+ memset(dev_cs89x0.priv, 0, sizeof(struct net_local));
+ lp = (struct net_local *)dev_cs89x0.priv;
+
+ /* boy, they'd better get these right */
+ if (!strcmp(media, "rj45"))
+ lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T;
+ if (!strcmp(media, "aui"))
+ lp->adapter_cnf = A_CNF_MEDIA_AUI | A_CNF_AUI;
+ if (!strcmp(media, "bnc"))
+ lp->adapter_cnf = A_CNF_MEDIA_10B_2 | A_CNF_10B_2;
+
+ if (!strcmp(duplex, "auto"))
+ lp->auto_neg_cnf = AUTO_NEG_ENABLE;
+
+ if (io == 0) {
+ printk(KERN_NOTICE "cs89x0.c: Module autoprobing not allowed.\n");
+ printk(KERN_NOTICE "cs89x0.c: Append io=0xNNN\n");
+ return -EPERM;
+ }
+ if (register_netdev(&dev_cs89x0) != 0) {
+ printk(KERN_WARNING "cs89x0.c: No card found at 0x%x\n", io);
+ return -ENXIO;
+ }
+ return 0;
+}
+
+void
+cleanup_module(void)
+{
+
+#endif
+#ifdef MODULE
+ outw(0, dev_cs89x0.base_addr + ADD_PORT);
+#endif
+#ifdef MODULE
+
+ if (dev_cs89x0.priv != NULL) {
+ /* Free up the private structure, or leak memory :-) */
+ kfree(dev_cs89x0.priv);
+ dev_cs89x0.priv = NULL; /* gets re-allocated by cs89x0_probe1 */
+ /* If we don't do this, we can't re-insmod it later. */
+ release_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT);
+ unregister_netdev(&dev_cs89x0);
+ }
+}
+#endif /* MODULE */
+
+/*
+ * Local variables:
+ * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/include -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -DMODULE -DCONFIG_MODVERSIONS -c cs89x0.c"
+ * version-control: t
+ * kept-new-versions: 5
+ * c-indent-level: 8
+ * tab-width: 8
+ * End:
+ *
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov