patch-2.3.43 linux/drivers/net/oaknet.c

Next file: linux/drivers/net/pcmcia/pcnet_cs.c
Previous file: linux/drivers/net/ni65.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.42/linux/drivers/net/oaknet.c linux/drivers/net/oaknet.c
@@ -1,6 +1,6 @@
 /*
  *
- *    Copyright (c) 1999 Grant Erickson <grant@lcse.umn.edu>
+ *    Copyright (c) 1999-2000 Grant Erickson <grant@lcse.umn.edu>
  *
  *    Module name: oaknet.c
  *
@@ -9,12 +9,17 @@
  *      on-board the IBM PowerPC "Oak" evaluation board. Adapted from the
  *      various other 8390 drivers written by Donald Becker and Paul Gortmaker.
  *
+ *      Additional inspiration from the "tcd8390.c" driver from TiVo, Inc. 
+ *      and "enetLib.c" from IBM.
+ *
  */
 
 #include <linux/module.h>
 #include <linux/errno.h>
+#include <linux/delay.h>
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
+#include <linux/init.h>
 
 #include <asm/board.h>
 #include <asm/io.h>
@@ -32,23 +37,23 @@
 #define	FALSE	0
 #endif
 
-#define	OAKNET_CMD     		0x00
-#define OAKNET_DATA		0x10	/* NS-defined port window offset. */
-#define OAKNET_RESET		0x1f	/* A read resets, a write clears. */
-
 #define	OAKNET_START_PG		0x20	/* First page of TX buffer */
 #define	OAKNET_STOP_PG		0x40	/* Last pagge +1 of RX ring */
 
-#define	OAKNET_BASE		(dev->base_addr)
-
 #define	OAKNET_WAIT		(2 * HZ / 100)	/* 20 ms */
 
+/* Experimenting with some fixes for a broken driver... */
+
+#define	OAKNET_DISINT
+#define	OAKNET_HEADCHECK
+#define	OAKNET_RWFIX
+
 
 /* Global Variables */
 
-#if defined(MODULE)
-static struct net_device *oaknet_devs;
-#endif
+static const char *name = "National DP83902AV";
+
+static struct net_device *oaknet_devs = NULL;
 
 
 /* Function Prototypes */
@@ -85,20 +90,35 @@
  *   0 if OK, otherwise system error number on error.
  *
  */
-int
-oaknet_init(void)
+static int __init oaknet_init(void)
 {
 	register int i;
 	int reg0, regd;
-	struct net_device *dev = NULL;
-	unsigned long ioaddr = OAKNET_IO_BASE;
-	const char *name = "National DP83902AV";
+	struct net_device tmp, *dev = NULL;
+#if 0
+	unsigned long ioaddr = OAKNET_IO_BASE; 
+#else
+	unsigned long ioaddr = ioremap(OAKNET_IO_BASE, OAKNET_IO_SIZE);
+#endif
 	bd_t *bip = (bd_t *)__res;
 
+	/*
+	 * This MUST happen here because of the nic_* macros
+	 * which have an implicit dependency on dev->base_addr.
+	 */
+
+	tmp.base_addr = ioaddr;
+	dev = &tmp;
+
+	if (!request_region(OAKNET_IO_BASE, OAKNET_IO_SIZE, name))
+		return -EBUSY;
+
 	/* Quick register check to see if the device is really there. */
 
-	if ((reg0 = inb_p(ioaddr)) == 0xFF)
+	if ((reg0 = ei_ibp(ioaddr)) == 0xFF) {
+		release_region(OAKNET_IO_BASE, OAKNET_IO_SIZE);
 		return (ENODEV);
+	}
 
 	/*
 	 * That worked. Now a more thorough check, using the multicast
@@ -106,19 +126,20 @@
 	 * and semi-functional.
 	 */
 
-	outb_p(E8390_NODMA + E8390_PAGE1 + E8390_STOP, ioaddr + E8390_CMD);
-	regd = inb_p(ioaddr + 0x0D);
-	outb_p(0xFF, ioaddr + 0x0D);
-	outb_p(E8390_NODMA + E8390_PAGE0, ioaddr + E8390_CMD);
-	inb_p(ioaddr + EN0_COUNTER0);
+	ei_obp(E8390_NODMA + E8390_PAGE1 + E8390_STOP, ioaddr + E8390_CMD);
+	regd = ei_ibp(ioaddr + 0x0D);
+	ei_obp(0xFF, ioaddr + 0x0D);
+	ei_obp(E8390_NODMA + E8390_PAGE0, ioaddr + E8390_CMD);
+	ei_ibp(ioaddr + EN0_COUNTER0);
 
 	/* It's no good. Fix things back up and leave. */
 
-	if (inb_p(ioaddr + EN0_COUNTER0) != 0) {
-		outb_p(reg0, ioaddr);
-		outb_p(regd, ioaddr + 0x0D);
+	if (ei_ibp(ioaddr + EN0_COUNTER0) != 0) {
+		ei_obp(reg0, ioaddr);
+		ei_obp(regd, ioaddr + 0x0D);
 		dev->base_addr = 0;
 
+		release_region(dev->base_addr, OAKNET_IO_SIZE);
 		return (ENODEV);
 	}
 
@@ -127,8 +148,10 @@
 	 * sure its symbols are loaded.
 	 */
 
-	if (load_8390_module("oaknet.c"))
+	if (load_8390_module("oaknet.c")) {
+		release_region(dev->base_addr, OAKNET_IO_SIZE);
 		return (-ENOSYS);
+	}
 
 	/*
 	 * We're not using the old-style probing API, so we have to allocate
@@ -136,39 +159,31 @@
 	 */
 
 	dev = init_etherdev(0, 0);
-#if defined(MODULE)
 	oaknet_devs = dev;
-#endif
 
 	/*
 	 * This controller is on an embedded board, so the base address
 	 * and interrupt assignments are pre-assigned and unchageable.
 	 */
 
-	dev->base_addr = OAKNET_IO_BASE;
+	dev->base_addr = ioaddr;
 	dev->irq = OAKNET_INT;
 
 	/* Allocate 8390-specific device-private area and fields. */
 
 	if (ethdev_init(dev)) {
 		printk(" unable to get memory for dev->priv.\n");
+		release_region(dev->base_addr, OAKNET_IO_SIZE);
 		return (-ENOMEM);
 	}
 
 	/*
-	 * Just to be safe, reset the card as we cannot really* be sure
-	 * what state it was last left in.
-	 */
-
-	oaknet_reset_8390(dev);
-
-	/*
 	 * Disable all chip interrupts for now and ACK all pending
 	 * interrupts.
 	 */
 
-	outb_p(0x0, ioaddr + EN0_IMR);
-	outb_p(0xFF, ioaddr + EN0_ISR);
+	ei_obp(0x0, ioaddr + EN0_IMR);
+	ei_obp(0xFF, ioaddr + EN0_ISR);
 
 	/* Attempt to get the interrupt line */
 
@@ -176,12 +191,10 @@
 		printk("%s: unable to request interrupt %d.\n",
 		       dev->name, dev->irq);
 		kfree(dev->priv);
-		dev->priv = NULL;
+		release_region(dev->base_addr, OAKNET_IO_SIZE);
 		return (EAGAIN);
 	}
 
-	request_region(dev->base_addr, OAKNET_IO_SIZE, name);
-
 	/* Tell the world about what and where we've found. */
 
 	printk("%s: %s at", dev->name, name);
@@ -273,7 +286,7 @@
  *   This routine resets the DP83902 chip.
  *
  * Input(s):
- *  *dev - 
+ *  *dev - Pointer to the device structure for this driver.
  *
  * Output(s):
  *   N/A
@@ -285,36 +298,48 @@
 static void
 oaknet_reset_8390(struct net_device *dev)
 {
-	int base = OAKNET_BASE;
-	unsigned long start = jiffies;
+	int base = E8390_BASE;
 
-	outb(inb(base + OAKNET_RESET), base + OAKNET_RESET);
+	/*
+	 * We have no provision of reseting the controller as is done
+	 * in other drivers, such as "ne.c". However, the following
+	 * seems to work well enough in the TiVo driver.
+	 */
 
+	printk("Resetting %s...\n", dev->name);
+	ei_obp(E8390_STOP | E8390_NODMA | E8390_PAGE0, base + E8390_CMD);
 	ei_status.txing = 0;
 	ei_status.dmaing = 0;
 
-	/* This check shouldn't be necessary eventually */
-
-	while ((inb_p(base + EN0_ISR) & ENISR_RESET) == 0) {
-		if (jiffies - start > OAKNET_WAIT) {
-			printk("%s: reset didn't complete\n", dev->name);
-			break;
-		}
-	}
-
-	outb_p(ENISR_RESET, base + EN0_ISR);	/* ACK reset interrupt */
-
 	return;
 }
 
 /*
- * XXX - Document me.
+ * static void oaknet_get_8390_hdr()
+ *
+ * Description:
+ *   This routine grabs the 8390-specific header. It's similar to the
+ *   block input routine, but we don't need to be concerned with ring wrap
+ *   as the header will be at the start of a page, so we optimize accordingly.
+ *
+ * Input(s):
+ *  *dev       - Pointer to the device structure for this driver.
+ *  *hdr       - Pointer to storage for the 8390-specific packet header.
+ *   ring_page - ?
+ *
+ * Output(s):
+ *  *hdr       - Pointer to the 8390-specific packet header for the just-
+ *               received frame.
+ *
+ * Returns:
+ *   N/A
+ *
  */
 static void
 oaknet_get_8390_hdr(struct net_device *dev, struct e8390_pkt_hdr *hdr,
 		    int ring_page)
 {
-	int base = OAKNET_BASE;
+	int base = dev->base_addr;
 
 	/*
 	 * This should NOT happen. If it does, it is the LAST thing you'll
@@ -341,6 +366,10 @@
 		insb(base + OAKNET_DATA, hdr,
 		     sizeof(struct e8390_pkt_hdr));
 
+	/* Byte-swap the packet byte count */
+
+	hdr->count = le16_to_cpu(hdr->count);
+
 	outb_p(ENISR_RDC, base + EN0_ISR);	/* ACK Remote DMA interrupt */
 	ei_status.dmaing &= ~0x01;
 
@@ -367,38 +396,99 @@
 		return;
 	}
 
+#ifdef OAKNET_DISINT
+	save_flags(flags);
+	cli();
+#endif
+
 	ei_status.dmaing |= 0x01;
-	outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START, base + OAKNET_CMD);
-	outb_p(count & 0xff, base + EN0_RCNTLO);
-	outb_p(count >> 8, base + EN0_RCNTHI);
-	outb_p(ring_offset & 0xff, base + EN0_RSARLO);
-	outb_p(ring_offset >> 8, base + EN0_RSARHI);
-	outb_p(E8390_RREAD + E8390_START, base + OAKNET_CMD);
+	ei_obp(E8390_NODMA + E8390_PAGE0 + E8390_START, base + E8390_CMD);
+	ei_obp(count & 0xff, base + EN0_RCNTLO);
+	ei_obp(count >> 8, base + EN0_RCNTHI);
+	ei_obp(ring_offset & 0xff, base + EN0_RSARLO);
+	ei_obp(ring_offset >> 8, base + EN0_RSARHI);
+	ei_obp(E8390_RREAD + E8390_START, base + E8390_CMD);
 	if (ei_status.word16) {
-		insw(base + OAKNET_DATA, buf, count >> 1);
+		ei_isw(base + E8390_DATA, buf, count >> 1);
 		if (count & 0x01) {
-			buf[count-1] = inb(base + OAKNET_DATA);
+			buf[count - 1] = ei_ib(base + E8390_DATA);
+#ifdef OAKNET_HEADCHECK
+			bytes++;
+#endif
 		}
 	} else {
-		insb(base + OAKNET_DATA, buf, count);
+		ei_isb(base + E8390_DATA, buf, count);
 	}
-	outb_p(ENISR_RDC, base + EN0_ISR);	/* ACK Remote DMA interrupt */
+#ifdef OAKNET_HEADCHECK
+	/*
+	 * This was for the ALPHA version only, but enough people have
+	 * been encountering problems so it is still here.  If you see
+	 * this message you either 1) have a slightly incompatible clone
+	 * or 2) have noise/speed problems with your bus.
+	 */
+
+	/* DMA termination address check... */
+	{
+		int addr, tries = 20;
+		do {
+			/* DON'T check for 'ei_ibp(EN0_ISR) & ENISR_RDC' here
+			   -- it's broken for Rx on some cards! */
+			int high = ei_ibp(base + EN0_RSARHI);
+			int low = ei_ibp(base + EN0_RSARLO);
+			addr = (high << 8) + low;
+			if (((ring_offset + bytes) & 0xff) == low)
+				break;
+		} while (--tries > 0);
+	 	if (tries <= 0)
+			printk("%s: RX transfer address mismatch,"
+			       "%#4.4x (expected) vs. %#4.4x (actual).\n",
+			       dev->name, ring_offset + bytes, addr);
+	}
+#endif
+	ei_obp(ENISR_RDC, base + EN0_ISR);	/* ACK Remote DMA interrupt */
 	ei_status.dmaing &= ~0x01;
 
+#ifdef OAKNET_DISINT
+	restore_flags(flags);
+#endif
+
 	return;
 }
 
 /*
- * XXX - Document me.
+ * static void oaknet_block_output()
+ *
+ * Description:
+ *   This routine...
+ *
+ * Input(s):
+ *  *dev        - Pointer to the device structure for this driver.
+ *   count      - Number of bytes to be transferred.
+ *  *buf        - 
+ *   start_page - 
+ *
+ * Output(s):
+ *   N/A
+ *
+ * Returns:
+ *   N/A
+ *
  */
 static void
 oaknet_block_output(struct net_device *dev, int count,
 		    const unsigned char *buf, int start_page)
 {
-	int base = OAKNET_BASE;
+	int base = E8390_BASE;
+#if 0
 	int bug;
+#endif
 	unsigned long start;
-	unsigned char lobyte;
+#ifdef OAKNET_DISINT
+	unsigned long flags;
+#endif
+#ifdef OAKNET_HEADCHECK
+	int retries = 0;
+#endif
 
 	/* Round the count up for word writes. */
 
@@ -415,12 +505,22 @@
 		return;
 	}
 
+#ifdef OAKNET_DISINT
+	save_flags(flags);
+	cli();
+#endif
+
 	ei_status.dmaing |= 0x01;
 
 	/* Make sure we are in page 0. */
 
-	outb_p(E8390_PAGE0 + E8390_START + E8390_NODMA, base + OAKNET_CMD);
+	ei_obp(E8390_PAGE0 + E8390_START + E8390_NODMA, base + E8390_CMD);
 
+#ifdef OAKNET_HEADCHECK
+retry:
+#endif
+
+#if 0
 	/*
 	 * The 83902 documentation states that the processor needs to
 	 * do a "dummy read" before doing the remote write to work
@@ -433,60 +533,131 @@
 		unsigned int rdlo;
 
 		/* Now the normal output. */
-		outb_p(ENISR_RDC, base + EN0_ISR);
-		outb_p(count & 0xff, base + EN0_RCNTLO);
-		outb_p(count >> 8,   base + EN0_RCNTHI);
-		outb_p(0x00, base + EN0_RSARLO);
-		outb_p(start_page, base + EN0_RSARHI);
+		ei_obp(ENISR_RDC, base + EN0_ISR);
+		ei_obp(count & 0xff, base + EN0_RCNTLO);
+		ei_obp(count >> 8,   base + EN0_RCNTHI);
+		ei_obp(0x00, base + EN0_RSARLO);
+		ei_obp(start_page, base + EN0_RSARHI);
 
 		if (bug++)
 			break;
 
 		/* Perform the dummy read */
-		rdhi = inb_p(base + EN0_CRDAHI);
-		rdlo = inb_p(base + EN0_CRDALO);
-		outb_p(E8390_RREAD + E8390_START, base + OAKNET_CMD);
+		rdhi = ei_ibp(base + EN0_CRDAHI);
+		rdlo = ei_ibp(base + EN0_CRDALO);
+		ei_obp(E8390_RREAD + E8390_START, base + E8390_CMD);
 
 		while (1) {
 			unsigned int nrdhi;
 			unsigned int nrdlo;
-			nrdhi = inb_p(base + EN0_CRDAHI);
-			nrdlo = inb_p(base + EN0_CRDALO);
+			nrdhi = ei_ibp(base + EN0_CRDAHI);
+			nrdlo = ei_ibp(base + EN0_CRDALO);
 			if ((rdhi != nrdhi) || (rdlo != nrdlo))
 				break;
 		}
 	}
+#else
+#ifdef OAKNET_RWFIX
+	/*
+	 * Handle the read-before-write bug the same way as the
+	 * Crynwr packet driver -- the Nat'l Semi. method doesn't work.
+	 * Actually this doesn't always work either, but if you have
+	 * problems with your 83902 this is better than nothing!
+	 */
+
+	ei_obp(0x42, base + EN0_RCNTLO);
+	ei_obp(0x00, base + EN0_RCNTHI);
+	ei_obp(0x42, base + EN0_RSARLO);
+	ei_obp(0x00, base + EN0_RSARHI);
+	ei_obp(E8390_RREAD + E8390_START, base + E8390_CMD);
+	/* Make certain that the dummy read has occurred. */
+	udelay(6);
+#endif
+
+	ei_obp(ENISR_RDC, base + EN0_ISR);
 
-	outb_p(E8390_RWRITE+E8390_START, base + OAKNET_CMD);
+	/* Now the normal output. */
+	ei_obp(count & 0xff, base + EN0_RCNTLO);
+	ei_obp(count >> 8,   base + EN0_RCNTHI);
+	ei_obp(0x00, base + EN0_RSARLO);
+	ei_obp(start_page, base + EN0_RSARHI);
+#endif /* 0/1 */
+
+	ei_obp(E8390_RWRITE + E8390_START, base + E8390_CMD);
 	if (ei_status.word16) {
-		outsw(OAKNET_BASE + OAKNET_DATA, buf, count >> 1);
+		ei_osw(E8390_BASE + E8390_DATA, buf, count >> 1);
 	} else {
-		outsb(OAKNET_BASE + OAKNET_DATA, buf, count);
+		ei_osb(E8390_BASE + E8390_DATA, buf, count);
 	}
 
+#ifdef OAKNET_DISINT
+	restore_flags(flags);
+#endif
+
 	start = jiffies;
 
-	while (((lobyte = inb_p(base + EN0_ISR)) & ENISR_RDC) == 0) {
+#ifdef OAKNET_HEADCHECK
+	/*
+	 * This was for the ALPHA version only, but enough people have
+	 * been encountering problems so it is still here.
+	 */
+	
+	{
+		/* DMA termination address check... */
+		int addr, tries = 20;
+		do {
+			int high = ei_ibp(base + EN0_RSARHI);
+			int low = ei_ibp(base + EN0_RSARLO);
+			addr = (high << 8) + low;
+			if ((start_page << 8) + count == addr)
+				break;
+		} while (--tries > 0);
+
+		if (tries <= 0) {
+			printk("%s: Tx packet transfer address mismatch,"
+			       "%#4.4x (expected) vs. %#4.4x (actual).\n",
+			       dev->name, (start_page << 8) + count, addr);
+			if (retries++ == 0)
+				goto retry;
+		}
+	}
+#endif
+
+	while ((ei_ibp(base + EN0_ISR) & ENISR_RDC) == 0) {
 		if (jiffies - start > OAKNET_WAIT) {
-			unsigned char hicnt, locnt;
-			hicnt = inb_p(base + EN0_CRDAHI);
-			locnt = inb_p(base + EN0_CRDALO);
-			printk("%s: timeout waiting for Tx RDC, stat = 0x%x\n",
-			       dev->name, lobyte);
-			printk("\tstart address 0x%x, current address 0x%x, count %d\n",
-			       (start_page << 8), (hicnt << 8) | locnt, count);
+			printk("%s: timeout waiting for Tx RDC.\n", dev->name);
 			oaknet_reset_8390(dev);
 			NS8390_init(dev, TRUE);
 			break;
 		}
 	}
 	
-	outb_p(ENISR_RDC, base + EN0_ISR);	/* Ack intr. */
+	ei_obp(ENISR_RDC, base + EN0_ISR);	/* Ack intr. */
 	ei_status.dmaing &= ~0x01;
 
 	return;
 }
 
+/*
+ * static void oaknet_dma_error()
+ *
+ * Description:
+ *   This routine prints out a last-ditch informative message to the console
+ *   indicating that a DMA error occured. If you see this, it's the last
+ *   thing you'll see.
+ *
+ * Input(s):
+ *  *dev  - Pointer to the device structure for this driver.
+ *  *name - Informative text (e.g. function name) indicating where the
+ *          DMA error occurred.
+ *
+ * Output(s):
+ *   N/A
+ *
+ * Returns:
+ *   N/A
+ *
+ */
 static void
 oaknet_dma_error(struct net_device *dev, const char *name)
 {
@@ -498,12 +669,10 @@
 	return;
 }
 
-#if defined(MODULE)
 /*
  * Oak Ethernet module load interface.
  */
-int
-init_module(void)
+static int __init oaknet_init_module (void)
 {
 	int status;
 
@@ -512,7 +681,8 @@
 
 	status = oaknet_init()
 
-	lock_8390_module();
+	if (status == 0)
+		lock_8390_module();
 
 	return (status);
 }
@@ -520,8 +690,7 @@
 /*
  * Oak Ethernet module unload interface.
  */
-void
-cleanup_module(void)
+static void __exit oaknet_cleanup_module (void)
 {
 	if (oaknet_devs == NULL)
 		return;
@@ -538,7 +707,7 @@
 	oaknet_devs = NULL;
 
 	unlock_8390_module();
-
-	return;
 }
-#endif /* MODULE */
+
+module_init(oaknet_init_module);
+module_exit(oaknet_cleanup_module);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)