public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] winbond update
@ 2001-05-25 21:43 Manfred Spraul
  0 siblings, 0 replies; only message in thread
From: Manfred Spraul @ 2001-05-25 21:43 UTC (permalink / raw)
  To: linux-kernel

[-- Attachment #1: Type: text/plain, Size: 487 bytes --]

I'll send the attached patch to Alan soon, please test it.

Main changes:
* powerpc support added. Tested with ppc 603e.
* memory leak in _close fixed.
* initial power management support
* ethtool support merged
* SMP synchronization updated.
* improved link detection. This change could cause problems, but it's
required for proper switching between nway and pre-nway link partners,
at least with my PHY.

Tested with -ac11, patch also applies against 2.4.4 and 2.4.4-ac17.

--
	Manfred

[-- Attachment #2: patch-winbond --]
[-- Type: text/plain, Size: 32681 bytes --]

--- 2.4/drivers/net/winbond-840.c	Fri May 25 22:36:10 2001
+++ build-2.4/drivers/net/winbond-840.c	Fri May 25 23:23:07 2001
@@ -32,12 +32,22 @@
 		synchronize tx_q_bytes
 		software reset in tx_timeout
 			Copyright (C) 2000 Manfred Spraul
+	* further cleanups
+		power management.
+		support for big endian descriptors
+		ethtool ioctl merged from gkernel.
+			Copyright (c) 2001 Manfred Spraul
 
 	TODO:
-	* according to the documentation, the chip supports big endian
-		descriptors. Remove cpu_to_le32, enable BE descriptors.
+	* enable pci_power_off
+	* Wake-On-LAN
 */
 
+#define DRV_NAME	"winbond-840"
+#define DRV_VERSION	"1.01-b"
+#define DRV_RELDATE	"5/15/2000"
+
+
 /* Automatically extracted configuration info:
 probe-func: winbond840_probe
 config-in: tristate 'Winbond W89c840 Ethernet support' CONFIG_WINBOND_840
@@ -79,10 +89,13 @@
    Making the Tx ring too large decreases the effectiveness of channel
    bonding and packet priority.
    There are no ill effects from too-large receive rings. */
-#define TX_RING_SIZE	16
+#define TX_RING_SIZE	16	
 #define TX_QUEUE_LEN	10		/* Limit ring entries actually used.  */
+#define TX_QUEUE_LEN_RESTART	5
 #define RX_RING_SIZE	32
 
+#define TX_BUFLIMIT	(1024-128)
+
 /* The presumed FIFO size for working around the Tx-FIFO-overflow bug.
    To avoid overflowing we don't queue again until we have room for a
    full-size packet.
@@ -90,7 +103,6 @@
 #define TX_FIFO_SIZE (2048)
 #define TX_BUG_FIFO_LIMIT (TX_FIFO_SIZE-1514-16)
 
-#define TX_BUFLIMIT	(1024-128)
 
 /* Operational parameters that usually are not changed. */
 /* Time in jiffies before concluding the transmitter is hung. */
@@ -122,13 +134,17 @@
 #include <linux/skbuff.h>
 #include <linux/init.h>
 #include <linux/delay.h>
+#include <linux/rtnetlink.h>
+#include <linux/ethtool.h>
+#include <asm/uaccess.h>
 #include <asm/processor.h>		/* Processor type for cache alignment. */
 #include <asm/bitops.h>
 #include <asm/io.h>
+#include <asm/irq.h>
 
 /* These identify the driver base version and may not be removed. */
 static char version[] __devinitdata =
-KERN_INFO "winbond-840.c:v1.01 (2.4 port) 5/15/2000  Donald Becker <becker@scyld.com>\n"
+KERN_INFO DRV_NAME ".c:v" DRV_VERSION DRV_RELDATE "  Donald Becker <becker@scyld.com>\n"
 KERN_INFO "  http://www.scyld.com/network/drivers.html\n";
 
 MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
@@ -183,6 +199,8 @@
 correctly detect a full FIFO, and queuing more than 2048 bytes may result in
 silent data corruption.
 
+Test with 'ping -s 10000' on a fast computer.
+
 */
 
 \f
@@ -198,7 +216,7 @@
         PCI_ADDR_64BITS=0x100, PCI_NO_ACPI_WAKE=0x200, PCI_NO_MIN_LATENCY=0x400,
 };
 enum chip_capability_flags {
-	CanHaveMII=1, HasBrokenTx=2, AlwaysFDX=4, FDXOnNoMII=8,};
+	CanHaveMII=1, AlwaysFDX=2, FDXOnNoMII=4};
 #ifdef USE_IO_OPS
 #define W840_FLAGS (PCI_USES_IO | PCI_ADDR0 | PCI_USES_MASTER)
 #else
@@ -226,11 +244,11 @@
 static struct pci_id_info pci_id_tbl[] = {
 	{"Winbond W89c840",			/* Sometime a Level-One switch card. */
 	 { 0x08401050, 0xffffffff, 0x81530000, 0xffff0000 },
-	 W840_FLAGS, 128, CanHaveMII | HasBrokenTx | FDXOnNoMII},
+	 W840_FLAGS, 128, CanHaveMII | FDXOnNoMII},
 	{"Winbond W89c840", { 0x08401050, 0xffffffff, },
-	 W840_FLAGS, 128, CanHaveMII | HasBrokenTx},
+	 W840_FLAGS, 128, CanHaveMII },
 	{"Compex RL100-ATX", { 0x201111F6, 0xffffffff,},
-	 W840_FLAGS, 128, CanHaveMII | HasBrokenTx},
+	 W840_FLAGS, 128, CanHaveMII },
 	{0,},						/* 0 terminated list. */
 };
 
@@ -302,7 +320,7 @@
 struct w840_tx_desc {
 	s32 status;
 	s32 length;
-	u32 buffer1, buffer2;				/* We use only buffer 1.  */
+	u32 buffer1, buffer2;
 };
 
 /* Bits in network_desc.status */
@@ -335,30 +353,31 @@
 	unsigned int cur_rx, dirty_rx;		/* Producer/consumer ring indices */
 	unsigned int rx_buf_sz;				/* Based on MTU+slack. */
 	unsigned int cur_tx, dirty_tx;
-	int tx_q_bytes;
-	unsigned int tx_full:1;				/* The Tx queue is full. */
+	unsigned int tx_q_bytes;
+	unsigned int tx_full;				/* The Tx queue is full. */
 	/* These values are keep track of the transceiver/media in use. */
 	unsigned int full_duplex:1;			/* Full-duplex operation requested. */
 	unsigned int duplex_lock:1;
-	unsigned int medialock:1;			/* Do not sense media. */
-	unsigned int default_port:4;		/* Last dev->if_port value. */
+	int dev_suspended;
 	/* MII transceiver section. */
 	int mii_cnt;						/* MII device addresses. */
 	u16 advertising;					/* NWay media advertisement */
 	unsigned char phys[MII_CNT];		/* MII device addresses, but only the first is used */
+	u32 mii;
 };
 
 static int  eeprom_read(long ioaddr, int location);
 static int  mdio_read(struct net_device *dev, int phy_id, int location);
 static void mdio_write(struct net_device *dev, int phy_id, int location, int value);
 static int  netdev_open(struct net_device *dev);
-static void check_duplex(struct net_device *dev);
+static int  update_link(struct net_device *dev);
 static void netdev_timer(unsigned long data);
 static void init_rxtx_rings(struct net_device *dev);
 static void free_rxtx_rings(struct netdev_private *np);
 static void init_registers(struct net_device *dev);
 static void tx_timeout(struct net_device *dev);
-static int alloc_ring(struct net_device *dev);
+static int alloc_ringdesc(struct net_device *dev);
+static void free_ringdesc(struct netdev_private *np);
 static int  start_tx(struct sk_buff *skb, struct net_device *dev);
 static void intr_handler(int irq, void *dev_instance, struct pt_regs *regs);
 static void netdev_error(struct net_device *dev, int intr_status);
@@ -366,7 +385,7 @@
 static inline unsigned ether_crc(int length, unsigned char *data);
 static void set_rx_mode(struct net_device *dev);
 static struct net_device_stats *get_stats(struct net_device *dev);
-static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
 static int  netdev_close(struct net_device *dev);
 
 \f
@@ -399,7 +418,7 @@
 		return -ENOMEM;
 	SET_MODULE_OWNER(dev);
 
-	if (pci_request_regions(pdev, "winbond-840"))
+	if (pci_request_regions(pdev, DRV_NAME))
 		goto err_out_netdev;
 
 #ifdef USE_IO_OPS
@@ -411,7 +430,6 @@
 		goto err_out_free_res;
 #endif
 
-	/* Warning: broken for big-endian machines. */
 	for (i = 0; i < 3; i++)
 		((u16 *)dev->dev_addr)[i] = le16_to_cpu(eeprom_read(ioaddr, i));
 
@@ -427,6 +445,7 @@
 	np->chip_id = chip_idx;
 	np->drv_flags = pci_id_tbl[chip_idx].drv_flags;
 	spin_lock_init(&np->lock);
+	np->dev_suspended = 0;
 	
 	pci_set_drvdata(pdev, dev);
 
@@ -437,9 +456,9 @@
 	if (option > 0) {
 		if (option & 0x200)
 			np->full_duplex = 1;
-		np->default_port = option & 15;
-		if (np->default_port)
-			np->medialock = 1;
+		if (option & 15)
+			printk(KERN_INFO "%s: ignoring user supplied media type %d",
+				dev->name, option & 15);
 	}
 	if (find_cnt < MAX_UNITS  &&  full_duplex[find_cnt] > 0)
 		np->full_duplex = 1;
@@ -453,7 +472,7 @@
 	dev->stop = &netdev_close;
 	dev->get_stats = &get_stats;
 	dev->set_multicast_list = &set_rx_mode;
-	dev->do_ioctl = &mii_ioctl;
+	dev->do_ioctl = &netdev_ioctl;
 	dev->tx_timeout = &tx_timeout;
 	dev->watchdog_timeo = TX_TIMEOUT;
 
@@ -474,9 +493,11 @@
 			if (mii_status != 0xffff  &&  mii_status != 0x0000) {
 				np->phys[phy_idx++] = phy;
 				np->advertising = mdio_read(dev, phy, 4);
-				printk(KERN_INFO "%s: MII PHY found at address %d, status "
+				np->mii = (mdio_read(dev, phy, 2) << 16)+
+						mdio_read(dev, phy, 3);
+				printk(KERN_INFO "%s: MII PHY %8.8xh found at address %d, status "
 					   "0x%4.4x advertising %4.4x.\n",
-					   dev->name, phy, mii_status, np->advertising);
+					   dev->name, np->mii, phy, mii_status, np->advertising);
 			}
 		}
 		np->mii_cnt = phy_idx;
@@ -667,7 +688,7 @@
 		printk(KERN_DEBUG "%s: w89c840_open() irq %d.\n",
 			   dev->name, dev->irq);
 
-	if((i=alloc_ring(dev)))
+	if((i=alloc_ringdesc(dev)))
 		return i;
 
 	init_registers(dev);
@@ -678,7 +699,7 @@
 
 	/* Set the timer to check for link beat. */
 	init_timer(&np->timer);
-	np->timer.expires = jiffies + 3*HZ;
+	np->timer.expires = jiffies + 1*HZ;
 	np->timer.data = (unsigned long)dev;
 	np->timer.function = &netdev_timer;				/* timer handler */
 	add_timer(&np->timer);
@@ -686,25 +707,107 @@
 	return 0;
 }
 
-static void check_duplex(struct net_device *dev)
+#define MII_DAVICOM_DM9101	0x0181b800
+
+static int update_link(struct net_device *dev)
 {
 	struct netdev_private *np = dev->priv;
-	int mii_reg5 = mdio_read(dev, np->phys[0], 5);
-	int negotiated =  mii_reg5 & np->advertising;
-	int duplex;
+	int duplex, fasteth, result, mii_reg;
 
-	if (np->duplex_lock  ||  mii_reg5 == 0xffff)
-		return;
-	duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040;
-	if (np->full_duplex != duplex) {
-		np->full_duplex = duplex;
+	/* BSMR */
+	mii_reg = mdio_read(dev, np->phys[0], 1);
+
+	if (mii_reg == 0xffff)
+		return np->csr6;
+	/* reread: the link status bit is sticky */
+	mii_reg = mdio_read(dev, np->phys[0], 1);
+	if (!(mii_reg & 0x4)) {
+		if (netif_carrier_ok(dev)) {
+			if (debug)
+				printk(KERN_INFO "%s: MII #%d reports no link. Disabling watchdog.\n",
+					dev->name, np->phys[0]);
+			netif_carrier_off(dev);
+		}
+		return np->csr6;
+	}
+	if (!netif_carrier_ok(dev)) {
 		if (debug)
-			printk(KERN_INFO "%s: Setting %s-duplex based on MII #%d "
-				   "negotiated capability %4.4x.\n", dev->name,
-				   duplex ? "full" : "half", np->phys[0], negotiated);
-		np->csr6 &= ~0x200;
-		np->csr6 |= duplex ? 0x200 : 0;
+			printk(KERN_INFO "%s: MII #%d link is back. Enabling watchdog.\n",
+				dev->name, np->phys[0]);
+		netif_carrier_on(dev);
 	}
+	
+	if ((np->mii & ~0xf) == MII_DAVICOM_DM9101) {
+		/* If the link partner doesn't support autonegotiation
+		 * the MII detects it's abilities with the "parallel detection".
+		 * Some MIIs update the LPA register to the result of the parallel
+		 * detection, some don't.
+		 * The Davicom PHY [at least 0181b800] doesn't.
+		 * Instead bit 9 and 13 of the BMCR are updated to the result
+		 * of the negotiation..
+		 */
+		mii_reg = mdio_read(dev, np->phys[0], 0);
+		duplex = mii_reg & 0x100;
+		fasteth = mii_reg & 0x2000;
+	} else {
+		int negotiated;
+		mii_reg	= mdio_read(dev, np->phys[0], 5);
+		negotiated = mii_reg & np->advertising;
+
+		duplex = (negotiated & 0x0100) || ((negotiated & 0x02C0) == 0x0040);
+		fasteth = negotiated & 0x380;
+	}
+	duplex |= np->duplex_lock;
+	/* remove fastether and fullduplex */
+	result = np->csr6 & ~0x20000200;
+	if (duplex)
+		result |= 0x200;
+	if (fasteth)
+		result |= 0x20000000;
+	if (result != np->csr6 && debug)
+		printk(KERN_INFO "%s: Setting %dMBit-%s-duplex based on MII#%d\n",
+				 dev->name, fasteth ? 100 : 10, 
+			   	duplex ? "full" : "half", np->phys[0]);
+	return result;
+}
+
+#define RXTX_TIMEOUT	2000
+static inline void update_csr6(struct net_device *dev, int new)
+{
+	struct netdev_private *np = dev->priv;
+	long ioaddr = dev->base_addr;
+	int limit = RXTX_TIMEOUT;
+
+	if (new==np->csr6)
+		return;
+	/* stop both Tx and Rx processes */
+	writel(np->csr6 & ~0x2002, ioaddr + NetworkConfig);
+	/* wait until they have really stopped */
+	for (;;) {
+		int csr5 = readl(ioaddr + IntrStatus);
+		int t;
+
+		t = (csr5 >> 17) & 0x07;
+		if (t==0||t==1) {
+			/* rx stopped */
+			t = (csr5 >> 20) & 0x07;
+			if (t==0||t==1)
+				break;
+		}
+
+		limit--;
+		if(!limit) {
+			printk(KERN_INFO "%s: couldn't stop rxtx, IntrStatus %xh.\n",
+					dev->name, csr5);
+			break;
+		}
+		udelay(1);
+	}
+	np->csr6 = new;
+	/* and restart them with the new configuration */
+	writel(np->csr6, ioaddr + NetworkConfig);
+	if (new & 0x200)
+		np->full_duplex = 1;
 }
 
 static void netdev_timer(unsigned long data)
@@ -712,8 +815,6 @@
 	struct net_device *dev = (struct net_device *)data;
 	struct netdev_private *np = dev->priv;
 	long ioaddr = dev->base_addr;
-	int next_tick = 10*HZ;
-	int old_csr6 = np->csr6;
 
 	if (debug > 2)
 		printk(KERN_DEBUG "%s: Media selection timer tick, status %8.8x "
@@ -721,13 +822,9 @@
 			   dev->name, (int)readl(ioaddr + IntrStatus),
 			   (int)readl(ioaddr + NetworkConfig));
 	spin_lock_irq(&np->lock);
-	check_duplex(dev);
-	if (np->csr6 != old_csr6) {
-		writel(np->csr6 & ~0x0002, ioaddr + NetworkConfig);
-		writel(np->csr6 | 0x2002, ioaddr + NetworkConfig);
-	}
+	update_csr6(dev, update_link(dev));
 	spin_unlock_irq(&np->lock);
-	np->timer.expires = jiffies + next_tick;
+	np->timer.expires = jiffies + 10*HZ;
 	add_timer(&np->timer);
 }
 
@@ -741,12 +838,12 @@
 
 	/* Initial all Rx descriptors. */
 	for (i = 0; i < RX_RING_SIZE; i++) {
-		np->rx_ring[i].length = cpu_to_le32(np->rx_buf_sz);
+		np->rx_ring[i].length = np->rx_buf_sz;
 		np->rx_ring[i].status = 0;
 		np->rx_skbuff[i] = 0;
 	}
 	/* Mark the last entry as wrapping the ring. */
-	np->rx_ring[i-1].length |= cpu_to_le32(DescEndRing);
+	np->rx_ring[i-1].length |= DescEndRing;
 
 	/* Fill in the Rx buffers.  Handle allocation failure gracefully. */
 	for (i = 0; i < RX_RING_SIZE; i++) {
@@ -758,8 +855,8 @@
 		np->rx_addr[i] = pci_map_single(np->pci_dev,skb->tail,
 					skb->len,PCI_DMA_FROMDEVICE);
 
-		np->rx_ring[i].buffer1 = cpu_to_le32(np->rx_addr[i]);
-		np->rx_ring[i].status = cpu_to_le32(DescOwn);
+		np->rx_ring[i].buffer1 = np->rx_addr[i];
+		np->rx_ring[i].status = DescOwn;
 	}
 
 	np->cur_rx = 0;
@@ -816,6 +913,14 @@
 		writeb(dev->dev_addr[i], ioaddr + StationAddr + i);
 
 	/* Initialize other registers. */
+#ifdef __BIG_ENDIAN
+	i = (1<<20);	/* Big-endian descriptors */
+#else
+	i = 0;
+#endif
+	i |= (0x04<<2);		/* skip length 4 u32 */
+	i |= 0x02;		/* give Rx priority */
+
 	/* Configure the PCI bus bursts and FIFO thresholds.
 	   486: Set 8 longword cache alignment, 8 longword burst.
 	   586: Set 16 longword cache alignment, no burst limit.
@@ -823,43 +928,46 @@
 		0000	<not allowed> 		0000 align to cache	0800 8 longwords
 		4000	8  longwords		0100 1 longword		1000 16 longwords
 		8000	16 longwords		0200 2 longwords	2000 32 longwords
-		C000	32  longwords		0400 4 longwords
-	   Wait the specified 50 PCI cycles after a reset by initializing
-	   Tx and Rx queues and the address filter list. */
-#if defined(__powerpc__)		/* Big-endian */
-	writel(0x00100080 | 0xE010, ioaddr + PCIBusCfg);
-#elif defined(__alpha__)
-	writel(0xE010, ioaddr + PCIBusCfg);
-#elif defined(__i386__)
-#if defined(MODULE)
-	writel(0xE010, ioaddr + PCIBusCfg);
+		C000	32  longwords		0400 4 longwords */
+
+#if 1
+	/* certain burst/cache alignment settings cause
+	 * data corruptions.
+	 */
+	i |= 0x1000;
 #else
+#if defined (__i386__) && !defined(MODULE)
 	/* When not a module we can work around broken '486 PCI boards. */
-#define x86 boot_cpu_data.x86
-	writel((x86 <= 4 ? 0x4810 : 0xE010), ioaddr + PCIBusCfg);
-	if (x86 <= 4)
+	if (boot_cpu_data.x86 <= 4) {
+		i |= 0x4800;
 		printk(KERN_INFO "%s: This is a 386/486 PCI system, setting cache "
-			   "alignment to %x.\n", dev->name,
-			   (x86 <= 4 ? 0x4810 : 0xE010));
-#endif
+			   "alignment to 8 longwords.\n", dev->name);
+	} else {
+		i |= 0xE000;
+	}
+#elif defined(__powerpc__) || defined(__i386__) || defined(__alpha) || defined(__ia64__)
+	i |= 0xE000;
 #else
-	writel(0xE010, ioaddr + PCIBusCfg);
-#warning Processor architecture undefined!
+#warning Processor architecture undefined
+	i |= 0x4800;
 #endif
+#endif
+	writel(i, ioaddr + PCIBusCfg);
 
-	if (dev->if_port == 0)
-		dev->if_port = np->default_port;
-
-	/* Fast Ethernet; 128 byte Tx threshold; 
+	/* 128 byte Tx threshold; 
 		Transmit on; Receive on; */
-	np->csr6 = 0x20022002;
-	check_duplex(dev);
-	set_rx_mode(dev);
-	writel(0, ioaddr + RxStartDemand);
+	np->csr6 = 0x00002002;
+	np->csr6 = update_link(dev);
 
 	/* Clear and Enable interrupts by setting the interrupt mask. */
+	/* But defer interrupt processing until everything is set up */
+	disable_irq(dev->irq);
 	writel(0x1A0F5, ioaddr + IntrStatus);
 	writel(0x1A0F5, ioaddr + IntrEnable);
+	set_rx_mode(dev);
+
+	enable_irq(dev->irq);
+	writel(0, ioaddr + RxStartDemand);
 
 }
 
@@ -883,35 +991,40 @@
 		printk("\n");
 	}
 	printk(KERN_DEBUG "Tx cur %d Tx dirty %d Tx Full %d, q bytes %d.\n",
-				np->cur_tx, np->dirty_tx, np->tx_full,np->tx_q_bytes);
+				np->cur_tx, np->dirty_tx, np->tx_full, np->tx_q_bytes);
 	printk(KERN_DEBUG "Tx Descriptor addr %xh.\n",readl(ioaddr+0x4C));
 
 #endif
-	spin_lock_irq(&np->lock);
 	/*
 	 * Under high load dirty_tx and the internal tx descriptor pointer
 	 * come out of sync, thus perform a software reset and reinitialize
 	 * everything.
 	 */
+	disable_irq(dev->irq);
+	del_timer_sync(&np->timer);
+	spin_lock(&np->lock);
 
 	writel(1, dev->base_addr+PCIBusCfg);
+	readl(dev->base_addr+PCIBusCfg);
 	udelay(1);
 
 	free_rxtx_rings(np);
 	init_rxtx_rings(dev);
 	init_registers(dev);
-	set_rx_mode(dev);
 
-	spin_unlock_irq(&np->lock);
+	spin_unlock(&np->lock);
+	enable_irq(dev->irq);
 
 	netif_wake_queue(dev);
 	dev->trans_start = jiffies;
 	np->stats.tx_errors++;
-	return;
+
+	np->timer.expires = jiffies + 1*HZ;
+	add_timer(&np->timer);
 }
 
 /* Initialize the Rx and Tx rings, along with various 'dev' bits. */
-static int alloc_ring(struct net_device *dev)
+static int alloc_ringdesc(struct net_device *dev)
 {
 	struct netdev_private *np = dev->priv;
 
@@ -927,12 +1040,19 @@
 	return 0;
 }
 
+static void free_ringdesc(struct netdev_private *np)
+{
+	pci_free_consistent(np->pci_dev,
+			sizeof(struct w840_rx_desc)*RX_RING_SIZE +
+			sizeof(struct w840_tx_desc)*TX_RING_SIZE,
+			np->rx_ring, np->ring_dma_addr);
+
+}
 
 static int start_tx(struct sk_buff *skb, struct net_device *dev)
 {
 	struct netdev_private *np = dev->priv;
 	unsigned entry;
-	int len1, len2;
 
 	/* Caution: the write order is important here, set the field
 	   with the "ownership" bits last. */
@@ -940,48 +1060,49 @@
 	/* Calculate the next Tx descriptor entry. */
 	entry = np->cur_tx % TX_RING_SIZE;
 
-	np->tx_skbuff[entry] = skb;
 	np->tx_addr[entry] = pci_map_single(np->pci_dev,
 				skb->data,skb->len, PCI_DMA_TODEVICE);
-	np->tx_ring[entry].buffer1 = cpu_to_le32(np->tx_addr[entry]);
-	len2 = 0;
-	len1 = skb->len;
-	if(len1 > TX_BUFLIMIT) {
-		len1 = TX_BUFLIMIT;
-		len2 = skb->len-len1;
-		np->tx_ring[entry].buffer2 = cpu_to_le32(np->tx_addr[entry]+TX_BUFLIMIT);
-	}
-	np->tx_ring[entry].length = cpu_to_le32(DescWholePkt | (len2 << 11) | len1);
-	if (entry >= TX_RING_SIZE-1)		 /* Wrap ring */
-		np->tx_ring[entry].length |= cpu_to_le32(DescIntr | DescEndRing);
-	np->cur_tx++;
+	np->tx_skbuff[entry] = skb;
+
+	np->tx_ring[entry].buffer1 = np->tx_addr[entry];
+	if (skb->len < TX_BUFLIMIT) {
+		np->tx_ring[entry].length = DescWholePkt | skb->len;
+	} else {
+		int len = skb->len - TX_BUFLIMIT;
+
+		np->tx_ring[entry].buffer2 = np->tx_addr[entry]+TX_BUFLIMIT;
+		np->tx_ring[entry].length = DescWholePkt | (len << 11) | TX_BUFLIMIT;
+	}
+	if(entry == TX_RING_SIZE-1)
+		np->tx_ring[entry].length |= DescEndRing;
 
-	/* The spinlock protects against 2 races:
-	 * - tx_q_bytes is updated by this function and intr_handler
-	 * - our hardware is extremely fast and finishes the packet between
-	 *	our check for "queue full" and netif_stop_queue.
-	 *	Thus setting DescOwn and netif_stop_queue must be atomic.
+	/* Now acquire the irq spinlock.
+	 * The difficult race is the the ordering between
+	 * increasing np->cur_tx and setting DescOwn:
+	 * - if np->cur_tx is increased first the interrupt
+	 *   handler could consider the packet as transmitted
+	 *   since DescOwn is cleared.
+	 * - If DescOwn is set first the NIC could report the
+	 *   packet as sent, but the interrupt handler would ignore it
+	 *   since the np->cur_tx was not yet increased.
 	 */
 	spin_lock_irq(&np->lock);
+	np->cur_tx++;
 
 	wmb(); /* flush length, buffer1, buffer2 */
-	np->tx_ring[entry].status = cpu_to_le32(DescOwn);
+	np->tx_ring[entry].status = DescOwn;
 	wmb(); /* flush status and kick the hardware */
 	writel(0, dev->base_addr + TxStartDemand);
-
 	np->tx_q_bytes += skb->len;
-	/* Work around horrible bug in the chip by marking the queue as full
-	   when we do not have FIFO room for a maximum sized packet. */
-	if (np->cur_tx - np->dirty_tx > TX_QUEUE_LEN)
-		np->tx_full = 1;
-	else if ((np->drv_flags & HasBrokenTx)
-			 && np->tx_q_bytes > TX_BUG_FIFO_LIMIT)
-		np->tx_full = 1;
-	if (np->tx_full)
+	if (np->cur_tx - np->dirty_tx > TX_QUEUE_LEN ||
+		np->tx_q_bytes > TX_BUG_FIFO_LIMIT) {
 		netif_stop_queue(dev);
+		wmb();
+		np->tx_full = 1;
+	}
+	spin_unlock_irq(&np->lock);
 
 	dev->trans_start = jiffies;
-	spin_unlock_irq(&np->lock);
 
 	if (debug > 4) {
 		printk(KERN_DEBUG "%s: Transmit frame #%d queued in slot %d.\n",
@@ -990,6 +1111,62 @@
 	return 0;
 }
 
+static void netdev_tx_done(struct net_device *dev)
+{
+	struct netdev_private *np = dev->priv;
+	for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) {
+		int entry = np->dirty_tx % TX_RING_SIZE;
+		int tx_status = np->tx_ring[entry].status;
+
+		if (tx_status < 0)
+			break;
+		if (tx_status & 0x8000) { 	/* There was an error, log it. */
+#ifndef final_version
+			if (debug > 1)
+				printk(KERN_DEBUG "%s: Transmit error, Tx status %8.8x.\n",
+					   dev->name, tx_status);
+#endif
+			np->stats.tx_errors++;
+			if (tx_status & 0x0104) np->stats.tx_aborted_errors++;
+			if (tx_status & 0x0C80) np->stats.tx_carrier_errors++;
+			if (tx_status & 0x0200) np->stats.tx_window_errors++;
+			if (tx_status & 0x0002) np->stats.tx_fifo_errors++;
+			if ((tx_status & 0x0080) && np->full_duplex == 0)
+				np->stats.tx_heartbeat_errors++;
+#ifdef ETHER_STATS
+			if (tx_status & 0x0100) np->stats.collisions16++;
+#endif
+		} else {
+#ifdef ETHER_STATS
+			if (tx_status & 0x0001) np->stats.tx_deferred++;
+#endif
+#ifndef final_version
+			if (debug > 3)
+				printk(KERN_DEBUG "%s: Transmit slot %d ok, Tx status %8.8x.\n",
+					   dev->name, entry, tx_status);
+#endif
+			np->stats.tx_bytes += np->tx_skbuff[entry]->len;
+			np->stats.collisions += (tx_status >> 3) & 15;
+			np->stats.tx_packets++;
+		}
+		/* Free the original skb. */
+		pci_unmap_single(np->pci_dev,np->tx_addr[entry],
+					np->tx_skbuff[entry]->len,
+					PCI_DMA_TODEVICE);
+		np->tx_q_bytes -= np->tx_skbuff[entry]->len;
+		dev_kfree_skb_irq(np->tx_skbuff[entry]);
+		np->tx_skbuff[entry] = 0;
+	}
+	if (np->tx_full &&
+		np->cur_tx - np->dirty_tx < TX_QUEUE_LEN_RESTART &&
+		np->tx_q_bytes < TX_BUG_FIFO_LIMIT) {
+		/* The ring is no longer full, clear tbusy. */
+		np->tx_full = 0;
+		wmb();
+		netif_wake_queue(dev);
+	}
+}
+
 /* The interrupt handler does all of the Rx thread work and cleans up
    after the Tx thread. */
 static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs)
@@ -999,8 +1176,8 @@
 	long ioaddr = dev->base_addr;
 	int work_limit = max_interrupt_work;
 
-	spin_lock(&np->lock);
-
+	if (np->dev_suspended)
+		return;
 	do {
 		u32 intr_status = readl(ioaddr + IntrStatus);
 
@@ -1016,51 +1193,14 @@
 
 		if (intr_status & (IntrRxDone | RxNoBuf))
 			netdev_rx(dev);
+		if (intr_status & RxNoBuf)
+			writel(0, ioaddr + RxStartDemand);
 
-		for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) {
-			int entry = np->dirty_tx % TX_RING_SIZE;
-			int tx_status = le32_to_cpu(np->tx_ring[entry].status);
-
-			if (tx_status < 0)
-				break;
-			if (tx_status & 0x8000) { 		/* There was an error, log it. */
-#ifndef final_version
-				if (debug > 1)
-					printk(KERN_DEBUG "%s: Transmit error, Tx status %8.8x.\n",
-						   dev->name, tx_status);
-#endif
-				np->stats.tx_errors++;
-				if (tx_status & 0x0104) np->stats.tx_aborted_errors++;
-				if (tx_status & 0x0C80) np->stats.tx_carrier_errors++;
-				if (tx_status & 0x0200) np->stats.tx_window_errors++;
-				if (tx_status & 0x0002) np->stats.tx_fifo_errors++;
-				if ((tx_status & 0x0080) && np->full_duplex == 0)
-					np->stats.tx_heartbeat_errors++;
-#ifdef ETHER_STATS
-				if (tx_status & 0x0100) np->stats.collisions16++;
-#endif
-			} else {
-#ifdef ETHER_STATS
-				if (tx_status & 0x0001) np->stats.tx_deferred++;
-#endif
-				np->stats.tx_bytes += np->tx_skbuff[entry]->len;
-				np->stats.collisions += (tx_status >> 3) & 15;
-				np->stats.tx_packets++;
-			}
-			/* Free the original skb. */
-			pci_unmap_single(np->pci_dev,np->tx_addr[entry],
-						np->tx_skbuff[entry]->len,
-						PCI_DMA_TODEVICE);
-			np->tx_q_bytes -= np->tx_skbuff[entry]->len;
-			dev_kfree_skb_irq(np->tx_skbuff[entry]);
-			np->tx_skbuff[entry] = 0;
-		}
-		if (np->tx_full &&
-			np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 4
-			&&  np->tx_q_bytes < TX_BUG_FIFO_LIMIT) {
-			/* The ring is no longer full, clear tbusy. */
-			np->tx_full = 0;
-			netif_wake_queue(dev);
+		if (intr_status & (TxIdle | IntrTxDone) &&
+			np->cur_tx != np->dirty_tx) {
+			spin_lock(&np->lock);
+			netdev_tx_done(dev);
+			spin_unlock(&np->lock);
 		}
 
 		/* Abnormal error summary/uncommon events handlers. */
@@ -1073,8 +1213,12 @@
 				   "status=0x%4.4x.\n", dev->name, intr_status);
 			/* Set the timer to re-enable the other interrupts after
 			   10*82usec ticks. */
-			writel(AbnormalIntr | TimerInt, ioaddr + IntrEnable);
-			writel(10, ioaddr + GPTimer);
+			spin_lock(&np->lock);
+			if (!np->dev_suspended) {
+				writel(AbnormalIntr | TimerInt, ioaddr + IntrEnable);
+				writel(10, ioaddr + GPTimer);
+			}
+			spin_unlock(&np->lock);
 			break;
 		}
 	} while (1);
@@ -1082,8 +1226,6 @@
 	if (debug > 3)
 		printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n",
 			   dev->name, (int)readl(ioaddr + IntrStatus));
-
-	spin_unlock(&np->lock);
 }
 
 /* This routine is logically part of the interrupt handler, but separated
@@ -1102,7 +1244,7 @@
 	/* If EOP is set on the next entry, it's a new packet. Send it up. */
 	while (--work_limit >= 0) {
 		struct w840_rx_desc *desc = np->rx_head_desc;
-		s32 status = le32_to_cpu(desc->status);
+		s32 status = desc->status;
 
 		if (debug > 4)
 			printk(KERN_DEBUG "  netdev_rx() status was %8.8x.\n",
@@ -1198,10 +1340,10 @@
 			np->rx_addr[entry] = pci_map_single(np->pci_dev,
 							skb->tail,
 							skb->len, PCI_DMA_FROMDEVICE);
-			np->rx_ring[entry].buffer1 = cpu_to_le32(np->rx_addr[entry]);
+			np->rx_ring[entry].buffer1 = np->rx_addr[entry];
 		}
 		wmb();
-		np->rx_ring[entry].status = cpu_to_le32(DescOwn);
+		np->rx_ring[entry].status = DescOwn;
 	}
 
 	return 0;
@@ -1218,31 +1360,36 @@
 	if (intr_status == 0xffffffff)
 		return;
 	if (intr_status & TxFIFOUnderflow) {
+		int new;
 		/* Bump up the Tx threshold */
+		spin_lock(&np->lock);
 #if 0
 		/* This causes lots of dropped packets,
 		 * and under high load even tx_timeouts
 		 */
-		np->csr6 += 0x4000;
+		new = np->csr6 + 0x4000;
 #else
-		int cur = (np->csr6 >> 14)&0x7f;
-		if (cur < 64)
-			cur *= 2;
+		new = (np->csr6 >> 14)&0x7f;
+		if (new < 64)
+			new *= 2;
 		 else
-		 	cur = 0; /* load full packet before starting */
-		np->csr6 &= ~(0x7F << 14);
-		np->csr6 |= cur<<14;
+		 	new = 0; /* load full packet before starting */
+		new = (np->csr6 & ~(0x7F << 14)) | (new<<14);
 #endif
-		printk(KERN_DEBUG "%s: Tx underflow, increasing threshold to %8.8x.\n",
-			   dev->name, np->csr6);
-		writel(np->csr6, ioaddr + NetworkConfig);
+		printk(KERN_DEBUG "%s: Tx underflow, new csr6 %8.8x.\n",
+			   dev->name, new);
+		update_csr6(dev, new);
+		spin_unlock(&np->lock);
 	}
 	if (intr_status & IntrRxDied) {		/* Missed a Rx frame. */
 		np->stats.rx_errors++;
 	}
 	if (intr_status & TimerInt) {
 		/* Re-enable other interrupts. */
-		writel(0x1A0F5, ioaddr + IntrEnable);
+		spin_lock(&np->lock);
+		if (!np->dev_suspended)
+			writel(0x1A0F5, ioaddr + IntrEnable);
+		spin_unlock(&np->lock);
 	}
 	np->stats.rx_missed_errors += readl(ioaddr + RxMissed) & 0xffff;
 	writel(0, ioaddr + RxStartDemand);
@@ -1307,26 +1454,57 @@
 	}
 	writel(mc_filter[0], ioaddr + MulticastFilter0);
 	writel(mc_filter[1], ioaddr + MulticastFilter1);
-	np->csr6 &= ~0x00F8;
-	np->csr6 |= rx_mode;
-	writel(np->csr6, ioaddr + NetworkConfig);
+	spin_lock_irq(&np->lock);
+	update_csr6(dev, (np->csr6 & ~0x00F8) | rx_mode);
+	spin_unlock_irq(&np->lock);
+}
+
+static int netdev_ethtool_ioctl(struct net_device *dev, void *useraddr)
+{
+	struct netdev_private *np = dev->priv;
+	u32 ethcmd;
+		
+	if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
+		return -EFAULT;
+
+        switch (ethcmd) {
+        case ETHTOOL_GDRVINFO: {
+		struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO};
+		strcpy(info.driver, DRV_NAME);
+		strcpy(info.version, DRV_VERSION);
+		strcpy(info.bus_info, np->pci_dev->slot_name);
+		if (copy_to_user(useraddr, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+
+        }
+	
+	return -EOPNOTSUPP;
 }
 
-static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
 {
+	struct netdev_private *np = dev->priv;
 	u16 *data = (u16 *)&rq->ifr_data;
 
 	switch(cmd) {
+	case SIOCETHTOOL:
+		return netdev_ethtool_ioctl(dev, (void *) rq->ifr_data);
 	case SIOCDEVPRIVATE:		/* Get the address of the PHY in use. */
 		data[0] = ((struct netdev_private *)dev->priv)->phys[0] & 0x1f;
 		/* Fall Through */
 	case SIOCDEVPRIVATE+1:		/* Read the specified MII register. */
+		spin_lock_irq(&np->lock);
 		data[3] = mdio_read(dev, data[0] & 0x1f, data[1] & 0x1f);
+		spin_unlock_irq(&np->lock);
 		return 0;
 	case SIOCDEVPRIVATE+2:		/* Write the specified MII register */
 		if (!capable(CAP_NET_ADMIN))
 			return -EPERM;
+		spin_lock_irq(&np->lock);
 		mdio_write(dev, data[0] & 0x1f, data[1] & 0x1f, data[2]);
+		spin_unlock_irq(&np->lock);
 		return 0;
 	default:
 		return -EOPNOTSUPP;
@@ -1337,7 +1515,6 @@
 {
 	long ioaddr = dev->base_addr;
 	struct netdev_private *np = dev->priv;
-	int i;
 
 	netif_stop_queue(dev);
 
@@ -1348,18 +1525,29 @@
 		printk(KERN_DEBUG "%s: Queue pointers were Tx %d / %d,  Rx %d / %d.\n",
 			   dev->name, np->cur_tx, np->dirty_tx, np->cur_rx, np->dirty_rx);
 	}
+	/* Stop the chip's Tx and Rx processes. */
+	spin_lock_irq(&np->lock);
+	update_csr6(dev, np->csr6 & ~0x20FA);
 
-	/* Disable interrupts by clearing the interrupt mask. */
+	/* and now disable the interrupt handler handler
+	 * dev_suspended is required to prevent a TimerInt
+	 * from reenabling the interrupts.
+	 * The rntl_lock prevents concurrent _suspend calls.
+	 */
+	np->dev_suspended = 1;
 	writel(0x0000, ioaddr + IntrEnable);
+	spin_unlock_irq(&np->lock);
 
-	/* Stop the chip's Tx and Rx processes. */
-	writel(np->csr6 &= ~0x20FA, ioaddr + NetworkConfig);
+	free_irq(dev->irq, dev);
+	wmb();
+	np->dev_suspended = 0;
 
 	if (readl(ioaddr + NetworkConfig) != 0xffffffff)
 		np->stats.rx_missed_errors += readl(ioaddr + RxMissed) & 0xffff;
 
 #ifdef __i386__
 	if (debug > 2) {
+		int i;
 		printk("\n"KERN_DEBUG"  Tx ring at %8.8x:\n",
 			   (int)np->tx_ring);
 		for (i = 0; i < TX_RING_SIZE; i++)
@@ -1376,11 +1564,10 @@
 	}
 #endif /* __i386__ debugging only */
 
-	free_irq(dev->irq, dev);
-
 	del_timer_sync(&np->timer);
 
 	free_rxtx_rings(np);
+	free_ringdesc(np);
 
 	return 0;
 }
@@ -1402,11 +1589,70 @@
 	pci_set_drvdata(pdev, NULL);
 }
 
+static void w840_suspend (struct pci_dev *pdev)
+{
+	struct net_device *dev = pci_get_drvdata (pdev);
+	struct netdev_private *np = dev->priv;
+	long ioaddr = dev->base_addr;
+
+	netif_device_detach(dev);
+	/* no more calls to tx_timeout, hard_start_xmit, set_rx_mode */
+	rtnl_lock();
+	rtnl_unlock();
+	/* noone within ->open */
+	if (netif_running (dev)) {
+		del_timer_sync(&np->timer);
+		/* no more link beat timer calls */
+		spin_lock_irq(&np->lock);
+		update_csr6(dev, np->csr6 & ~0x2002);
+		/* Tx and Rx processes stopped */
+
+		np->dev_suspended = 1;
+		writel(0, ioaddr + IntrEnable);
+		/* all irq events disabled. */
+		spin_unlock_irq(&np->lock);
+
+		synchronize_irq();
+
+		/* Update the error counts. */
+		np->stats.rx_missed_errors += readl(ioaddr + RxMissed) & 0xffff;
+
+		/* pci_power_off(pdev, -1); */
+
+		free_rxtx_rings(np);
+	}
+}
+
+
+static void w840_resume (struct pci_dev *pdev)
+{
+	struct net_device *dev = pci_get_drvdata (pdev);
+	struct netdev_private *np = dev->priv;
+
+	if (netif_running (dev)) {
+		pci_enable_device(pdev);
+	/*	pci_power_on(pdev); */
+		np->dev_suspended = 0;
+
+		writel(1, dev->base_addr+PCIBusCfg);
+		readl(dev->base_addr+PCIBusCfg);
+		udelay(1);
+		init_rxtx_rings(dev);
+		init_registers(dev);
+
+		np->timer.expires = jiffies + 1*HZ;
+		add_timer(&np->timer);
+	}
+	netif_device_attach(dev);
+}
+
 static struct pci_driver w840_driver = {
-	name:		"winbond-840",
+	name:		DRV_NAME,
 	id_table:	w840_pci_tbl,
 	probe:		w840_probe1,
 	remove:		w840_remove1,
+	suspend:	w840_suspend,
+	resume:		w840_resume,
 };
 
 static int __init w840_init(void)

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2001-05-25 21:45 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2001-05-25 21:43 [PATCH] winbond update Manfred Spraul

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox