netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-07-28 21:38 Francois Romieu
@ 2006-08-01 15:12 ` Steve Glendinning
  2006-08-01 15:33   ` John W. Linville
                     ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Steve Glendinning @ 2006-08-01 15:12 UTC (permalink / raw)
  To: netdev
  Cc: Ian Saturley, Bahadir Balban, Stephen Hemminger, Francois Romieu,
	Steve Glendinning

> > Attached is a driver patch for SMSC911x family of ethernet chips,
> > generated against 2.6.18-rc1 sources. There's a similar driver in the
> > tree; this one has been tested by SMSC on all flavors of the chip and
> > claimed to be efficient.
>
> Updated after feedback from Stephen Hemminger.
>
> Driver updated to also support LAN921x family.  Workarounds added for
> known hardware issues.

Many improvements following feedback from Stephen Hemminger and
Francois Romieu:
 - Tasklet removed, NAPI poll used instead
 - Multiple device support
 - style fixes & minor improvements

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
---
 drivers/net/Kconfig    |   12 
 drivers/net/Makefile   |    1 
 drivers/net/smsc911x.c | 1931 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h |  442 +++++++++++
 4 files changed, 2386 insertions(+), 0 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 3918990..bf84e2a 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -865,6 +865,18 @@ config NET_NETX
 	  <file:Documentation/networking/net-modules.txt>. The module
 	  will be called netx-eth.
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config DM9000
 	tristate "DM9000 support"
 	depends on (ARM || MIPS) && NET_ETHERNET
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index c91e951..51f680b 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -196,6 +196,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
 
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..053863a
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,1931 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2005  SMSC
+ * Copyright (C) 2005 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *
+ * History:
+ *   05/05/2005 bahadir.balban@arm.com
+ *     - Transition to linux coding style
+ *     - Platform driver and module interface
+ *
+ *   17/07/2006 steve.glendinning@smsc.com
+ *     - Added support for LAN921x family
+ *     - Added workaround for multicast filters
+ *
+ *   31/07/2006 steve.glendinning@smsc.com
+ *     - Removed tasklet, using NAPI poll instead
+ *     - Multiple device support
+ *     - Large tidy-up following feedback from netdev list
+ */
+
+#include <linux/config.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <linux/crc32.h>
+#include <asm/bug.h>
+#include <asm/bitops.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		 "smsc911x"
+
+MODULE_LICENSE("GPL");
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--) {
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+	}
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--) {
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* waits for MAC not busy, with timeout.  Assumes MacPhyAccessLock has
+ * already been acquired */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+
+	for (i = 0; i < 40; i++) {
+		if ((smsc911x_reg_read(pdata, MAC_CSR_CMD)
+		     & MAC_CSR_CMD_CSR_BUSY_) == 0) {
+			return 1;
+		}
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", smsc911x_reg_read(pdata,
+							      MAC_CSR_CMD));
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static unsigned int
+smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int result = 0xFFFFFFFF;
+	unsigned int temp;
+
+	/* Wait until not busy */
+	if (unlikely
+	    (smsc911x_reg_read(pdata, MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, "
+			     "MAC already busy at entry");
+		return result;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0x000000FF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (unlikely(!smsc911x_mac_notbusy(pdata)))
+		SMSC_WARNING("smsc911x_mac_read failed, "
+			     "waiting for MAC not busy after read");
+	else
+		result = smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	return result;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, unsigned int val)
+{
+	unsigned int temp;
+
+	if (unlikely
+	    (smsc911x_reg_read(pdata, MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, "
+			     "MAC already busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0x000000FF) | MAC_CSR_CMD_CSR_BUSY_),
+			   pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (unlikely(!smsc911x_mac_notbusy(pdata))) {
+		SMSC_WARNING("smsc911x_mac_write failed, "
+			     "waiting for MAC not busy after write");
+	}
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static unsigned int
+smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	unsigned int result = 0xFFFF;
+	int i;
+
+	/* Confirm MII not busy */
+	if (unlikely
+	    ((smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_) != 0)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->phy_address) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if ((smsc911x_mac_read(pdata, MII_ACC)
+		     & MII_ACC_MII_BUSY_) == 0) {
+			result = smsc911x_mac_read(pdata, MII_DATA);
+			return result;
+		}
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+
+	return result;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, unsigned int val)
+{
+	unsigned int addr;
+	int i;
+
+	/* Confirm MII not busy */
+	if (unlikely
+	    ((smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_) != 0)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->phy_address) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if ((smsc911x_mac_read(pdata, MII_ACC)
+		     & MII_ACC_MII_BUSY_) == 0)
+			return;
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, phy_lock already acquired. */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped because
+		 * smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		for (address = 0; address <= 31; address++) {
+			pdata->phy_address = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to interal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+#ifdef USE_LED1_WORK_AROUND
+			pdata->not_using_extphy = 0;
+#endif
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int lcount = 100000;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+		lcount--;
+	} while ((lcount > 0) && (temp & BMCR_RESET));
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but using 500 here to be safe
+	 */
+	udelay(500);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries = 0;
+	unsigned int lcount = 0;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a = 0;
+		unsigned int txcmd_b = 0;
+		unsigned int status = 0;
+		unsigned int pktlength = 0;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		lcount = 60;
+		do {
+			udelay(5);
+			lcount--;
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((lcount > 0) && (status == 0));
+
+		if (status == 0) {
+			SMSC_WARNING("Failed to transmit during "
+				     "loopback test");
+			continue;
+		}
+		if (status & 0x00008000) {
+			SMSC_WARNING("Transmit encountered errors "
+				     "during loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		lcount = 60;
+		do {
+			udelay(5);
+			lcount--;
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((lcount > 0) && (status == 0));
+
+		if (status == 0) {
+			SMSC_WARNING("Failed to receive during "
+				     "loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors "
+				     "during loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int index = 0;
+			int mismatch = 0;
+			for (index = 0; index < MIN_PACKET_SIZE; index++) {
+				if (pdata->loopback_tx_pkt[index]
+				    != pdata->loopback_rx_pkt[index]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int index = 0;
+	unsigned int tries = 0;
+	unsigned int val;
+
+	/* Initialise tx packet */
+	for (index = 0; index < 6; index++) {
+		/* Use broadcast destination address */
+		pdata->loopback_tx_pkt[index] = (char)0xFF;
+	}
+
+	for (index = 6; index < 12; index++) {
+		/* Use incrementing source address */
+		pdata->loopback_tx_pkt[index] = (char)index;
+	}
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (index = 14; index < MIN_PACKET_SIZE; index++) {
+		pdata->loopback_tx_pkt[index] = (char)index;
+	}
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (tries = 0; tries < 10; tries++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* Gets the current link mode, assumes phy_lock has already been acquired */
+static void smsc911x_phy_getlinkmode(struct smsc911x_data *pdata)
+{
+	unsigned int result = LINK_OFF;
+	unsigned int phy_reg = 0;
+	unsigned int phy_bsr = 0;
+
+	phy_bsr = smsc911x_phy_read(pdata, MII_BMSR);
+
+	pdata->link_settings = LINK_OFF;
+	if (phy_bsr & BMSR_LSTATUS) {
+		phy_reg = smsc911x_phy_read(pdata, MII_BMCR);
+		if (phy_reg & BMCR_ANENABLE) {
+			unsigned int linksettings = LINK_AUTO_NEGOTIATE;
+			unsigned int phy_adv =
+			    smsc911x_phy_read(pdata, MII_ADVERTISE);
+			unsigned int phy_lpa =
+			    smsc911x_phy_read(pdata, MII_LPA);
+			if (phy_adv & ADVERTISE_PAUSE_ASYM)
+				linksettings |= LINK_ASYMMETRIC_PAUSE;
+			if (phy_adv & ADVERTISE_PAUSE_CAP)
+				linksettings |= LINK_SYMMETRIC_PAUSE;
+			if (phy_adv & ADVERTISE_100FULL)
+				linksettings |= LINK_SPEED_100FD;
+			if (phy_adv & ADVERTISE_100HALF)
+				linksettings |= LINK_SPEED_100HD;
+			if (phy_adv & ADVERTISE_10FULL)
+				linksettings |= LINK_SPEED_10FD;
+			if (phy_adv & ADVERTISE_10HALF)
+				linksettings |= LINK_SPEED_10HD;
+
+			pdata->link_settings = linksettings;
+			phy_lpa &= phy_adv;
+
+			if (phy_lpa & LPA_100FULL)
+				result = LINK_SPEED_100FD;
+			else if (phy_lpa & LPA_100HALF)
+				result = LINK_SPEED_100HD;
+			else if (phy_lpa & LPA_10FULL)
+				result = LINK_SPEED_10FD;
+			else if (phy_lpa & LPA_10HALF)
+				result = LINK_SPEED_10HD;
+		} else {
+			if (phy_reg & BMCR_SPEED100) {
+				if (phy_reg & BMCR_FULLDPLX) {
+					result = LINK_SPEED_100FD;
+					pdata->link_settings = result;
+				} else {
+					result = LINK_SPEED_100HD;
+					pdata->link_settings = result;
+				}
+			} else {
+				if (phy_reg & BMCR_FULLDPLX) {
+					result = LINK_SPEED_10FD;
+					pdata->link_settings = result;
+				} else {
+					result = LINK_SPEED_10HD;
+					pdata->link_settings = result;
+				}
+			}
+		}
+	}
+	pdata->link_speed = result;
+}
+
+static void smsc911x_phy_print_linkmode(unsigned int locallink,
+					unsigned int linkpartner)
+{
+	SMSC_TRACE("LAN911x: %s,%s,%s,%s,%s,%s",
+		   (locallink & ADVERTISE_PAUSE_ASYM) ? "ASYMP" : "     ",
+		   (locallink & ADVERTISE_PAUSE_CAP) ? "SYMP " : "     ",
+		   (locallink & ADVERTISE_100FULL) ? "100FD" : "     ",
+		   (locallink & ADVERTISE_100HALF) ? "100HD" : "     ",
+		   (locallink & ADVERTISE_10FULL) ? "10FD " : "     ",
+		   (locallink & ADVERTISE_10HALF) ? "10HD " : "     ");
+
+	SMSC_TRACE("Partner: %s,%s,%s,%s,%s,%s",
+		   (linkpartner & LPA_PAUSE_ASYM) ? "ASYMP" : "     ",
+		   (linkpartner & LPA_PAUSE_CAP) ? "SYMP " : "     ",
+		   (linkpartner & LPA_100FULL) ? "100FD" : "     ",
+		   (linkpartner & LPA_100HALF) ? "100HD" : "     ",
+		   (linkpartner & LPA_10FULL) ? "10FD " : "     ",
+		   (linkpartner & LPA_10HALF) ? "10HD " : "     ");
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int old_link_speed = pdata->link_speed;
+	unsigned int temp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_getlinkmode(pdata);
+
+	if (old_link_speed != pdata->link_speed) {
+		if (pdata->link_speed != LINK_OFF) {
+			unsigned int phy_reg = 0;
+			switch (pdata->link_speed) {
+			case LINK_SPEED_10HD:
+				SMSC_TRACE("Link is now UP at 10Mbps HD");
+				break;
+			case LINK_SPEED_10FD:
+				SMSC_TRACE("Link is now UP at 10Mbps FD");
+				break;
+			case LINK_SPEED_100HD:
+				SMSC_TRACE("Link is now UP at 100Mbps HD");
+				break;
+			case LINK_SPEED_100FD:
+				SMSC_TRACE("Link is now UP at 100Mbps FD");
+				break;
+			default:
+				SMSC_WARNING("Link is now UP at unknown link "
+					     "speed: 0x%08X",
+					     pdata->link_speed);
+				break;
+			}
+			phy_reg = smsc911x_mac_read(pdata, MAC_CR);
+			phy_reg &= ~(MAC_CR_FDPX_ | MAC_CR_RCVOWN_);
+
+			switch (pdata->link_speed) {
+			case LINK_SPEED_10HD:
+			case LINK_SPEED_100HD:
+				phy_reg |= MAC_CR_RCVOWN_;
+				break;
+
+			case LINK_SPEED_10FD:
+			case LINK_SPEED_100FD:
+				phy_reg |= MAC_CR_FDPX_;
+				break;
+
+			default:
+				SMSC_WARNING("Unknown link speed: 0x%08X",
+					     pdata->link_speed);
+				break;
+			}
+
+			smsc911x_mac_write(pdata, MAC_CR, phy_reg);
+
+			if (pdata->link_settings & LINK_AUTO_NEGOTIATE) {
+				unsigned int linkpartner = 0;
+				unsigned int locallink = 0;
+				locallink = smsc911x_phy_read(pdata, 4);
+				linkpartner = smsc911x_phy_read(pdata, 5);
+				switch (pdata->link_speed) {
+				case LINK_SPEED_10FD:
+				case LINK_SPEED_100FD:
+					if (((locallink & linkpartner) &
+					     LPA_PAUSE_CAP) != 0) {
+						/* Enable PAUSE receive and transmit */
+						smsc911x_mac_write(pdata, FLOW,
+								   0xFFFF0002);
+						temp =
+						    smsc911x_reg_read(pdata,
+								      AFC_CFG);
+						temp |= 0xF;
+						smsc911x_reg_write(temp, pdata,
+								   AFC_CFG);
+					} else if (((locallink &
+						     (ADVERTISE_PAUSE_CAP |
+						      ADVERTISE_PAUSE_ASYM)) ==
+						    (ADVERTISE_PAUSE_CAP |
+						     ADVERTISE_PAUSE_ASYM)) &&
+						   ((linkpartner &
+						     (LPA_PAUSE_CAP |
+						      LPA_PAUSE_ASYM)) ==
+						    LPA_PAUSE_ASYM)) {
+						/* Enable PAUSE receive, disable PAUSE transmit */
+						smsc911x_mac_write(pdata, FLOW,
+								   0xFFFF0002);
+						temp =
+						    smsc911x_reg_read(pdata,
+								      AFC_CFG);
+						temp &= ~0xF;
+						smsc911x_reg_write(temp, pdata,
+								   AFC_CFG);
+					} else {
+						/* Disable PAUSE receive and transmit */
+						smsc911x_mac_write(pdata, FLOW,
+								   0);
+						temp =
+						    smsc911x_reg_read(pdata,
+								      AFC_CFG);
+						temp &= ~0xF;
+						smsc911x_reg_write(temp, pdata,
+								   AFC_CFG);
+					}
+					break;
+
+				case LINK_SPEED_10HD:
+				case LINK_SPEED_100HD:
+					smsc911x_mac_write(pdata, FLOW, 0);
+					temp =
+					    smsc911x_reg_read(pdata, AFC_CFG);
+					temp |= 0xF;
+					smsc911x_reg_write(temp, pdata,
+							   AFC_CFG);
+					break;
+
+				default:
+					SMSC_WARNING("Unknown link speed: "
+						     "0x%08X\n",
+						     pdata->link_speed);
+					break;
+				}
+				smsc911x_phy_print_linkmode(locallink,
+							    linkpartner);
+			} else {
+				switch (pdata->link_speed) {
+				case LINK_SPEED_10HD:
+				case LINK_SPEED_100HD:
+					smsc911x_mac_write(pdata, FLOW, 0);
+					smsc911x_reg_write(0x0000000F,
+							   pdata, AFC_CFG);
+					break;
+				default:
+					smsc911x_mac_write(pdata, FLOW, 0);
+					temp =
+					    smsc911x_reg_read(pdata, AFC_CFG);
+					temp &= ~0xF;
+					smsc911x_reg_write(temp, pdata,
+							   AFC_CFG);
+					break;
+				}
+			}
+			netif_carrier_on(dev);
+#ifdef USE_LED1_WORK_AROUND
+			if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+			    pdata->not_using_extphy) {
+				/* Restore orginal GPIO configuration */
+				pdata->gpio_setting = pdata->gpio_orig_setting;
+				smsc911x_reg_write(pdata->gpio_setting, pdata,
+						   GPIO_CFG);
+			}
+#endif				/* USE_LED1_WORK_AROUND */
+		} else {
+			SMSC_TRACE("Link is now down");
+			netif_carrier_off(dev);
+			smsc911x_mac_write(pdata, FLOW, 0);
+
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+
+#ifdef USE_LED1_WORK_AROUND
+			/* Check global setting that LED1
+			 * usage is 10/100 indicator */
+			pdata->gpio_setting =
+			    smsc911x_reg_read(pdata, GPIO_CFG);
+			if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+			    && pdata->not_using_extphy) {
+				/* Force 10/100 LED off, after saving
+				 * orginal GPIO configuration */
+				pdata->gpio_orig_setting = pdata->gpio_setting;
+
+				pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+				pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+							| GPIO_CFG_GPIODIR0_
+							| GPIO_CFG_GPIOD0_);
+				smsc911x_reg_write(pdata->gpio_setting, pdata,
+						   GPIO_CFG);
+			}
+#endif				/* USE_LED1_WORK_AROUND */
+		}
+	}
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	/* Must call this twice */
+	smsc911x_phy_update_linkmode(dev);
+	smsc911x_phy_update_linkmode(dev);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + HZ;
+		add_timer(&(pdata->link_poll_timer));
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation, phy_lock already
+ * acquired. */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->phy_address = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->phy_address = 1;
+		break;
+	}
+
+#ifdef USE_LED1_WORK_AROUND
+	pdata->not_using_extphy = 1;
+#endif
+
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	pdata->link_speed = LINK_OFF;
+	pdata->link_settings = LINK_OFF;
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+
+	init_timer(&pdata->link_poll_timer);
+	pdata->link_poll_timer.function = smsc911x_phy_checklink;
+	pdata->link_poll_timer.data = (unsigned long)dev;
+	pdata->link_poll_timer.expires = jiffies + HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata)
+{
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet length.
+			 * Since a packet length can never reach the size of 0x8000,
+			 * this bit is reserved so that if packet tracking tags were
+			 * ever used, then those tracking tags would set the reserved bit.
+			 * Accordingly, this control path would be used to look up the
+			 * packet and perhaps free it. It is worth noting that the
+			 * "reserved bit" in the warning above does not reference a
+			 * hardware defined reserved bit but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				pdata->stats.tx_errors++;
+			} else {
+				pdata->stats.tx_packets++;
+				pdata->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				pdata->stats.collisions += 16;
+				pdata->stats.tx_aborted_errors += 1;
+			} else {
+				pdata->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800)) {
+				pdata->stats.tx_carrier_errors += 1;
+			}
+			if (unlikely(tx_stat & 0x00000200)) {
+				pdata->stats.collisions++;
+				pdata->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat)
+{
+	int crc_err;
+
+	crc_err = 0;
+	if (unlikely(rxstat & 0x00008000)) {
+		pdata->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			pdata->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			pdata->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			pdata->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int count)
+{
+	if (likely(count >= 4)) {
+		unsigned int timeout = 500;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		while (timeout && (smsc911x_reg_read(pdata, RX_DP_CTRL)
+				   & RX_DP_CTRL_RX_FFWD_)) {
+			udelay(1);
+			timeout--;
+		}
+		if (unlikely(timeout == 0)) {
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X",
+				     smsc911x_reg_read(pdata, RX_DP_CTRL));
+		}
+	} else {
+		while (count) {
+			volatile unsigned int temp;
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+			count--;
+		}
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct net_device *dev, int *budget)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	int npackets = 0;
+	int quota = min(dev->quota, *budget);
+
+	while (npackets < quota) {
+		unsigned int pktlength;
+		unsigned int rxstat;
+		rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		/* break out of while loop if there are no more packets waiting */
+		if (rxstat == 0)
+			break;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		smsc911x_rx_counterrors(pdata, rxstat);
+
+		if (likely((rxstat & RX_STS_ES_) == 0)) {
+			struct sk_buff *skb = NULL;
+			skb = dev_alloc_skb(pktlength + 2);
+			if (likely(skb)) {
+				skb->data = skb->head;
+				skb->tail = skb->head;
+				/* Align IP on 16B boundary */
+				skb_reserve(skb, 2);
+				skb_put(skb, pktlength - 4);
+				smsc911x_rx_readfifo(pdata,
+						     (unsigned int *)skb->head,
+						     (pktlength + 2 + 3) >> 2);
+				skb->dev = dev;
+				skb->protocol = eth_type_trans(skb, dev);
+				skb->ip_summed = CHECKSUM_NONE;
+				netif_receive_skb(skb);
+
+				/* Update counters */
+				pdata->stats.rx_packets++;
+				pdata->stats.rx_bytes += (pktlength - 4);
+				dev->last_rx = jiffies;
+				npackets++;
+				continue;
+			} else {
+				SMSC_WARNING("Unable to allocate sk_buff "
+					     "for rx packet, in PIO path");
+				pdata->stats.rx_dropped++;
+			}
+		}
+		/* At this point, the packet is to be read out
+		 * of the fifo and discarded */
+		pktlength += 2 + 3;
+		pktlength >>= 2;
+		smsc911x_rx_fastforward(pdata, pktlength);
+	}
+
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+
+	*budget -= npackets;
+	dev->quota -= npackets;
+
+	if (npackets < quota) {
+		unsigned int temp;
+		/* We processed all packets available.  Tell NAPI it can
+		 * stop polling then re-enable rx interrupts */
+		netif_rx_complete(dev);
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp |= INT_EN_RSFL_EN_;
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		return 0;
+	}
+
+	/* There are still packets waiting */
+	return 1;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+	unsigned long flags;
+
+	/* This function is only called for older LAN911x devices 
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers. */
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int mac_high16;
+	unsigned int mac_low32;
+	unsigned int timeout;
+	unsigned int temp;
+	unsigned long flags;
+
+	spin_lock_init(&pdata->phy_lock);
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear\n");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+	/* Set interrupt deassertion to 100uS */
+	smsc911x_reg_write(((10 << 24) | INT_CFG_IRQ_EN_), pdata, INT_CFG);
+
+	/*
+	 * intcfg |= INT_CFG_IRQ_POL_;  use this to set IRQ_POL bit
+	 * intcfg |= INT_CFG_IRQ_TYPE_;  use this to set IRQ_TYPE bit
+	 */
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->request_irq_disable = 0;
+	pdata->software_irq_signal = 0;
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+	timeout = 1000;
+	do {
+		udelay(10);
+	} while ((--timeout) && (!pdata->software_irq_signal));
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	/* Read mac address from EEPROM */
+	mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+	mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+
+	/* Generate random MAC address if eeprom values are invalid */
+	if ((mac_high16 == 0x0000FFFF) && (mac_low32 == 0xFFFFFFFF)) {
+		u8 random_mac[6];
+		random_ether_addr(random_mac);
+		mac_high16 = (random_mac[5] << 8) | random_mac[4];
+		mac_low32 = (random_mac[3] << 24) | (random_mac[2] << 16) |
+		    (random_mac[1] << 8) | random_mac[0];
+
+		smsc911x_mac_write(pdata, ADDRH, mac_high16);
+		smsc911x_mac_write(pdata, ADDRL, mac_low32);
+		SMSC_TRACE("MAC Address is set to random_ether_addr");
+	} else {
+		SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+	}
+
+	dev->dev_addr[0] = (u8)(mac_low32);
+	dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+	dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+	dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+	dev->dev_addr[4] = (u8)(mac_high16);
+	dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	netif_carrier_off(dev);
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	temp &= HW_CFG_TX_FIF_SZ_;
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	smsc911x_reg_write((smsc911x_reg_read(pdata, INT_CFG) &
+			    (~INT_CFG_IRQ_EN_)), pdata, INT_CFG);
+	netif_stop_queue(dev);
+
+	/* At this point all Rx and Tx activity is stopped */
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(pdata);
+
+	SMSC_TRACE("<--Simp911x_stop");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	dev_kfree_skb(skb);
+	freespace -= (skb->len + 32);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(pdata);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return &pdata->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (pdata->multicast_update_pending == 0) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t
+smsc911x_irqhandler(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		serviced = IRQ_HANDLED;
+		if (pdata->request_irq_disable) {
+			temp = smsc911x_reg_read(pdata, INT_CFG);
+			temp &= (~INT_CFG_IRQ_EN_);
+			smsc911x_reg_write(temp, pdata, INT_CFG);
+			/* Prevent irqs from being handled */
+			intsts = 0;
+		}
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		/* Disable Rx interrupts and schedule NAPI poll */
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RSFL_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		netif_rx_schedule(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, (void *)dev, NULL);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *const data =
+	    (struct mii_ioctl_data *)&ifr->ifr_data;
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+	case SIOCDEVPRIVATE:
+		data->phy_id = pdata->phy_address;
+		return 0;
+	case SIOCGMIIREG:
+	case SIOCDEVPRIVATE + 1:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+	case SIOCDEVPRIVATE + 2:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+/* Initializing private device structures */
+static int smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	dev->poll = smsc911x_poll;
+	dev->weight = 64;
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	return 0;
+}
+
+static int smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	int res_size;
+	int retval;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		retval = -ENODEV;
+		goto out;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io;
+	}
+	SET_MODULE_OWNER(dev);
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+	memset(pdata, 0, sizeof(struct smsc911x_data));
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev;
+	}
+
+	if ((retval = smsc911x_init(dev)) < 0)
+		goto out_unmap_io;
+
+	if (request_irq(dev->irq, smsc911x_irqhandler,
+			SA_INTERRUPT, SMSC_CHIPNAME, dev) != 0) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		retval = -ENODEV;
+		goto out_unmap_io;
+	}
+
+	platform_set_drvdata(pdev, dev);
+	retval = register_netdev(dev);
+
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		retval = -ENODEV;
+		goto out_unset_drvdata;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	return 0;
+
+out_unset_drvdata:
+	platform_set_drvdata(pdev, NULL);
+out_unmap_io:
+	iounmap(pdata->ioaddr);
+out_free_netdev:
+	free_netdev(dev);
+out_release_io:
+	release_mem_region(res->start, res->end - res->start);
+out:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.suspend = 0,		/* TODO: Add suspend routine */
+	.resume = 0,		/* TODO: Add resume routine */
+	.driver = {
+		   .name = SMSC_CHIPNAME,
+		   },
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	platform_driver_register(&smsc911x_driver);
+	return 0;
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..c9134ac
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,442 @@
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define USE_PHY_WORK_AROUND
+#define USE_LED1_WORK_AROUND	/* 10/100 LED link-state inversion */
+
+/* Debugging */
+#define USE_DEBUG	0
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt,args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_TRACE(msg,args...)
+#endif				/* USE_DEBUG >= 2 */
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+	unsigned int idrev;
+	unsigned int generation;	/* used to decide which workarounds apply */
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+	struct net_device_stats stats;
+	unsigned int phy_address;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+	unsigned int not_using_extphy;
+#endif
+	unsigned int link_speed;
+	unsigned int link_settings;
+	struct timer_list link_poll_timer;
+	int stop_link_poll;
+
+	int request_irq_disable;
+	int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#define TX_FIFO_LOW_THRESHOLD            (1600)
+
+/* IO macros for portability */
+
+#define SMSC_CAN_USE_16BIT	0
+#define SMSC_CAN_USE_32BIT	1
+
+#if SMSC_CAN_USE_16BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	u32 reg_val;
+	unsigned long flags;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	reg_val =
+	    ((readw((u16 *)(((unsigned int)pdata->ioaddr) + reg)) & 0xFFFF) |
+	     ((readw((u16 *)(((unsigned int)pdata->ioaddr) + reg + 2)) &
+	       0xFFFF) << 16));
+	local_irq_restore(flags);
+
+	return reg_val;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	writew(val & 0xFFFF, (u16 *)(((unsigned int)pdata->ioaddr) + reg));
+	writew(((val >> 16) & 0xFFFF),
+	       (u16 *)(((unsigned int)pdata->ioaddr) + reg + 2));
+	local_irq_restore(flags);
+}
+
+#elif SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl((u32 *)(((unsigned int)pdata->ioaddr) + reg));
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, (u32 *)(((unsigned int)pdata->ioaddr) + reg));
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO					0x00
+
+#define TX_DATA_FIFO					0x20
+#define TX_CMD_A_ON_COMP_				0x80000000
+#define TX_CMD_A_BUF_END_ALGN_				0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_				0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_				0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_				0x02000000
+#define TX_CMD_A_DATA_OFFSET_				0x001F0000
+#define TX_CMD_A_FIRST_SEG_				0x00002000
+#define TX_CMD_A_LAST_SEG_				0x00001000
+#define TX_CMD_A_BUF_SIZE_				0x000007FF
+#define TX_CMD_B_PKT_TAG_				0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_			0x00002000
+#define TX_CMD_B_DISABLE_PADDING_			0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_			0x000007FF
+
+#define RX_STATUS_FIFO					0x40
+#define RX_STS_ES_					0x00008000
+#define RX_STS_MCAST_					0x00000400
+#define RX_STATUS_FIFO_PEEK				0x44
+#define TX_STATUS_FIFO					0x48
+#define TX_STATUS_FIFO_PEEK				0x4C
+#define ID_REV						0x50
+#define ID_REV_CHIP_ID_					0xFFFF0000 /* RO */
+#define ID_REV_REV_ID_					0x0000FFFF /* RO */
+
+#define INT_CFG						0x54
+#define INT_CFG_INT_DEAS_				0xFF000000 /* R/W */
+#define INT_CFG_INT_DEAS_CLR_				0x00004000 /* SC */
+#define INT_CFG_INT_DEAS_STS_				0x00002000 /* SC */
+#define INT_CFG_IRQ_INT_				0x00001000 /* RO */
+#define INT_CFG_IRQ_EN_					0x00000100 /* R/W */
+#define INT_CFG_IRQ_POL_				0x00000010 /* R/W Not Affected by SW Reset */
+#define INT_CFG_IRQ_TYPE_				0x00000001 /* R/W Not Affected by SW Reset */
+
+#define INT_STS						0x58
+#define INT_STS_SW_INT_					0x80000000 /* R/WC */
+#define INT_STS_TXSTOP_INT_				0x02000000 /* R/WC */
+#define INT_STS_RXSTOP_INT_				0x01000000 /* R/WC */
+#define INT_STS_RXDFH_INT_				0x00800000 /* R/WC */
+#define INT_STS_RXDF_INT_				0x00400000 /* R/WC */
+#define INT_STS_TX_IOC_					0x00200000 /* R/WC */
+#define INT_STS_RXD_INT_				0x00100000 /* R/WC */
+#define INT_STS_GPT_INT_				0x00080000 /* R/WC */
+#define INT_STS_PHY_INT_				0x00040000 /* RO */
+#define INT_STS_PME_INT_				0x00020000 /* R/WC */
+#define INT_STS_TXSO_					0x00010000 /* R/WC */
+#define INT_STS_RWT_					0x00008000 /* R/WC */
+#define INT_STS_RXE_					0x00004000 /* R/WC */
+#define INT_STS_TXE_					0x00002000 /* R/WC */
+#define INT_STS_TDFU_					0x00000800 /* R/WC */
+#define INT_STS_TDFO_					0x00000400 /* R/WC */
+#define INT_STS_TDFA_					0x00000200 /* R/WC */
+#define INT_STS_TSFF_					0x00000100 /* R/WC */
+#define INT_STS_TSFL_					0x00000080 /* R/WC */
+#define INT_STS_RXDF_					0x00000040 /* R/WC */
+#define INT_STS_RDFL_					0x00000020 /* R/WC */
+#define INT_STS_RSFF_					0x00000010 /* R/WC */
+#define INT_STS_RSFL_					0x00000008 /* R/WC */
+#define INT_STS_GPIO2_INT_				0x00000004 /* R/WC */
+#define INT_STS_GPIO1_INT_				0x00000002 /* R/WC */
+#define INT_STS_GPIO0_INT_				0x00000001 /* R/WC */
+#define INT_EN						0x5C
+#define INT_EN_SW_INT_EN_				0x80000000 /* R/W */
+#define INT_EN_TXSTOP_INT_EN_				0x02000000 /* R/W */
+#define INT_EN_RXSTOP_INT_EN_				0x01000000 /* R/W */
+#define INT_EN_RXDFH_INT_EN_				0x00800000 /* R/W */
+#define INT_EN_TIOC_INT_EN_				0x00200000 /* R/W */
+#define INT_EN_RXD_INT_EN_				0x00100000 /* R/W */
+#define INT_EN_GPT_INT_EN_				0x00080000 /* R/W */
+#define INT_EN_PHY_INT_EN_				0x00040000 /* R/W */
+#define INT_EN_PME_INT_EN_				0x00020000 /* R/W */
+#define INT_EN_TXSO_EN_					0x00010000 /* R/W */
+#define INT_EN_RWT_EN_					0x00008000 /* R/W */
+#define INT_EN_RXE_EN_					0x00004000 /* R/W */
+#define INT_EN_TXE_EN_					0x00002000 /* R/W */
+#define INT_EN_TDFU_EN_					0x00000800 /* R/W */
+#define INT_EN_TDFO_EN_					0x00000400 /* R/W */
+#define INT_EN_TDFA_EN_					0x00000200 /* R/W */
+#define INT_EN_TSFF_EN_					0x00000100 /* R/W */
+#define INT_EN_TSFL_EN_					0x00000080 /* R/W */
+#define INT_EN_RXDF_EN_					0x00000040 /* R/W */
+#define INT_EN_RDFL_EN_					0x00000020 /* R/W */
+#define INT_EN_RSFF_EN_					0x00000010 /* R/W */
+#define INT_EN_RSFL_EN_					0x00000008 /* R/W */
+#define INT_EN_GPIO2_INT_				0x00000004 /* R/W */
+#define INT_EN_GPIO1_INT_				0x00000002 /* R/W */
+#define INT_EN_GPIO0_INT_				0x00000001 /* R/W */
+
+#define BYTE_TEST					0x64
+#define FIFO_INT					0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_			0xFF000000/* R/W */
+#define FIFO_INT_TX_STS_LEVEL_				0x00FF0000/* R/W */
+#define FIFO_INT_RX_AVAIL_LEVEL_			0x0000FF00/* R/W */
+#define FIFO_INT_RX_STS_LEVEL_				0x000000FF/* R/W */
+
+#define RX_CFG						0x6C
+#define RX_CFG_RX_END_ALGN_				0xC0000000 /* R/W */
+#define RX_CFG_RX_END_ALGN4_				0x00000000 /* R/W */
+#define RX_CFG_RX_END_ALGN16_				0x40000000 /* R/W */
+#define RX_CFG_RX_END_ALGN32_				0x80000000 /* R/W */
+#define RX_CFG_RX_DMA_CNT_				0x0FFF0000 /* R/W */
+#define RX_CFG_RX_DUMP_					0x00008000 /* R/W */
+#define RX_CFG_RXDOFF_					0x00001F00 /* R/W */
+
+#define TX_CFG						0x70
+#define TX_CFG_TXS_DUMP_				0x00008000 /* Self Clearing */
+#define TX_CFG_TXD_DUMP_				0x00004000 /* Self Clearing */
+#define TX_CFG_TXSAO_					0x00000004 /* R/W */
+#define TX_CFG_TX_ON_					0x00000002 /* R/W */
+#define TX_CFG_STOP_TX_					0x00000001 /* Self Clearing */
+
+#define HW_CFG						0x74
+#define HW_CFG_TTM_					0x00200000 /* R/W */
+#define HW_CFG_SF_					0x00100000 /* R/W */
+#define HW_CFG_TX_FIF_SZ_				0x000F0000 /* R/W */
+#define HW_CFG_TR_					0x00003000 /* R/W */
+
+/* R/W only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_				0x00000060
+
+/* R/W only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_			0x00000000
+
+/* R/W only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_			0x00000020
+
+/* R/W only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_			0x00000040
+
+/* R/W only available on 115/117 */
+#define HW_CFG_SMI_SEL_		 			0x00000010
+
+/* RO only available  on 115/117 */
+#define HW_CFG_EXT_PHY_DET_				0x00000008
+
+/* R/W only available on 115/117 */
+#define HW_CFG_EXT_PHY_EN_				0x00000004
+
+/* RO only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_				0x00000004
+
+/* RO only available  on 115/117 */
+#define HW_CFG_SRST_TO_					0x00000002
+#define HW_CFG_SRST_					0x00000001 /* Self Clearing */
+
+#define RX_DP_CTRL					0x78
+#define RX_DP_CTRL_RX_FFWD_				0x80000000 /* RO */
+
+#define RX_FIFO_INF					0x7C
+#define RX_FIFO_INF_RXSUSED_				0x00FF0000 /* RO */
+#define RX_FIFO_INF_RXDUSED_				0x0000FFFF /* RO */
+
+#define TX_FIFO_INF					0x80
+#define TX_FIFO_INF_TSUSED_				0x00FF0000 /* RO */
+#define TX_FIFO_INF_TDFREE_				0x0000FFFF /* RO */
+
+#define PMT_CTRL					0x84
+#define PMT_CTRL_PM_MODE_				0x00003000 /* Self Clearing */
+#define PMT_CTRL_PM_MODE_D0_				0x00000000 /* Self Clearing */
+#define PMT_CTRL_PM_MODE_D1_				0x00001000 /* Self Clearing */
+#define PMT_CTRL_PM_MODE_D2_				0x00002000 /* Self Clearing */
+#define PMT_CTRL_PM_MODE_D3_				0x00003000 /* Self Clearing */
+#define PMT_CTRL_PHY_RST_				0x00000400 /* Self Clearing */
+#define PMT_CTRL_WOL_EN_				0x00000200 /* R/W */
+#define PMT_CTRL_ED_EN_					0x00000100 /* R/W */
+#define PMT_CTRL_PME_TYPE_				0x00000040 /* R/W
+								    * Not Affected by
+								    * SW Reset */
+#define PMT_CTRL_WUPS_					0x00000030 /* R/WC */
+#define PMT_CTRL_WUPS_NOWAKE_				0x00000000 /* R/WC */
+#define PMT_CTRL_WUPS_ED_				0x00000010 /* R/WC */
+#define PMT_CTRL_WUPS_WOL_				0x00000020 /* R/WC */
+#define PMT_CTRL_WUPS_MULTI_				0x00000030 /* R/WC */
+#define PMT_CTRL_PME_IND_				0x00000008 /* R/W */
+#define PMT_CTRL_PME_POL_				0x00000004 /* R/W */
+#define PMT_CTRL_PME_EN_				0x00000002 /* R/W
+								    * Not Affected by
+								    * SW Reset */
+#define PMT_CTRL_READY_					0x00000001 /* RO */
+
+#define GPIO_CFG					0x88
+#define GPIO_CFG_LED3_EN_				0x40000000 /* R/W */
+#define GPIO_CFG_LED2_EN_				0x20000000 /* R/W */
+#define GPIO_CFG_LED1_EN_				0x10000000 /* R/W */
+#define GPIO_CFG_GPIO2_INT_POL_				0x04000000 /* R/W */
+#define GPIO_CFG_GPIO1_INT_POL_				0x02000000 /* R/W */
+#define GPIO_CFG_GPIO0_INT_POL_				0x01000000 /* R/W */
+#define GPIO_CFG_EEPR_EN_				0x00700000 /* R/W */
+#define GPIO_CFG_GPIOBUF2_				0x00040000 /* R/W */
+#define GPIO_CFG_GPIOBUF1_				0x00020000 /* R/W */
+#define GPIO_CFG_GPIOBUF0_				0x00010000 /* R/W */
+#define GPIO_CFG_GPIODIR2_				0x00000400 /* R/W */
+#define GPIO_CFG_GPIODIR1_				0x00000200 /* R/W */
+#define GPIO_CFG_GPIODIR0_				0x00000100 /* R/W */
+#define GPIO_CFG_GPIOD4_				0x00000020 /* R/W */
+#define GPIO_CFG_GPIOD3_				0x00000010 /* R/W */
+#define GPIO_CFG_GPIOD2_				0x00000004 /* R/W */
+#define GPIO_CFG_GPIOD1_				0x00000002 /* R/W */
+#define GPIO_CFG_GPIOD0_				0x00000001 /* R/W */
+
+#define GPT_CFG						0x8C
+#define GPT_CFG_TIMER_EN_				0x20000000 /* R/W */
+#define GPT_CFG_GPT_LOAD_				0x0000FFFF /* R/W */
+
+#define GPT_CNT						0x90
+#define GPT_CNT_GPT_CNT_				0x0000FFFF /* RO */
+
+#define ENDIAN						0x98
+#define FREE_RUN					0x9C
+#define RX_DROP						0xA0
+#define MAC_CSR_CMD					0xA4
+#define MAC_CSR_CMD_CSR_BUSY_				0x80000000 /* Self Clearing */
+#define MAC_CSR_CMD_R_NOT_W_				0x40000000 /* R/W */
+#define MAC_CSR_CMD_CSR_ADDR_				0x000000FF /* R/W */
+
+#define MAC_CSR_DATA					0xA8
+#define AFC_CFG						0xAC
+#define AFC_CFG_AFC_HI_					0x00FF0000 /* R/W */
+#define AFC_CFG_AFC_LO_					0x0000FF00 /* R/W */
+#define AFC_CFG_BACK_DUR_				0x000000F0 /* R/W */
+#define AFC_CFG_FCMULT_					0x00000008 /* R/W */
+#define AFC_CFG_FCBRD_					0x00000004 /* R/W */
+#define AFC_CFG_FCADD_					0x00000002 /* R/W */
+#define AFC_CFG_FCANY_					0x00000001 /* R/W */
+
+#define E2P_CMD						0xB0
+#define E2P_CMD_EPC_BUSY_				0x80000000 /* Self Clearing */
+#define E2P_CMD_EPC_CMD_				0x70000000 /* R/W */
+#define E2P_CMD_EPC_CMD_READ_				0x00000000 /* R/W */
+#define E2P_CMD_EPC_CMD_EWDS_				0x10000000 /* R/W */
+#define E2P_CMD_EPC_CMD_EWEN_				0x20000000 /* R/W */
+#define E2P_CMD_EPC_CMD_WRITE_				0x30000000 /* R/W */
+#define E2P_CMD_EPC_CMD_WRAL_				0x40000000 /* R/W */
+#define E2P_CMD_EPC_CMD_ERASE_				0x50000000 /* R/W */
+#define E2P_CMD_EPC_CMD_ERAL_				0x60000000 /* R/W */
+#define E2P_CMD_EPC_CMD_RELOAD_				0x70000000 /* R/W */
+#define E2P_CMD_EPC_TIMEOUT_				0x00000200 /* R */
+#define E2P_CMD_MAC_ADDR_LOADED_			0x00000100 /* RO */
+#define E2P_CMD_EPC_ADDR_				0x000000FF /* R/W */
+
+#define E2P_DATA					0xB4
+#define E2P_DATA_EEPROM_DATA_				0x000000FF /* R/W */
+#define LAN_REGISTER_EXTENT				0x00000100
+
+#define LINK_OFF					0x00
+#define LINK_SPEED_10HD					0x01
+#define LINK_SPEED_10FD					0x02
+#define LINK_SPEED_100HD				0x04
+#define LINK_SPEED_100FD				0x08
+#define LINK_SYMMETRIC_PAUSE				0x10
+#define LINK_ASYMMETRIC_PAUSE				0x20
+#define LINK_AUTO_NEGOTIATE				0x40
+
+/*
+ *		 MAC Control and Status Register (Indirect Address)
+ *		 Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR						0x01	/* R/W */
+
+/* MAC_CR - MAC Control Register */
+#define MAC_CR_RXALL_					0x80000000
+#define MAC_CR_HBDIS_					0x10000000
+#define MAC_CR_RCVOWN_					0x00800000
+#define MAC_CR_LOOPBK_					0x00200000
+#define MAC_CR_FDPX_					0x00100000
+#define MAC_CR_MCPAS_					0x00080000
+#define MAC_CR_PRMS_					0x00040000
+#define MAC_CR_INVFILT_					0x00020000
+#define MAC_CR_PASSBAD_					0x00010000
+#define MAC_CR_HFILT_					0x00008000
+#define MAC_CR_HPFILT_					0x00002000
+#define MAC_CR_LCOLL_					0x00001000
+#define MAC_CR_BCAST_					0x00000800
+#define MAC_CR_DISRTY_					0x00000400
+#define MAC_CR_PADSTR_					0x00000100
+#define MAC_CR_BOLMT_MASK_				0x000000C0
+#define MAC_CR_DFCHK_					0x00000020
+#define MAC_CR_TXEN_					0x00000008
+#define MAC_CR_RXEN_					0x00000004
+
+#define ADDRH						0x02 /* R/W mask 0x0000FFFFUL */
+#define ADDRL						0x03 /* R/W mask 0xFFFFFFFFUL */
+#define HASHH						0x04 /* R/W */
+#define HASHL						0x05 /* R/W */
+
+#define MII_ACC						0x06 /* R/W */
+#define MII_ACC_PHY_ADDR_				0x0000F800
+#define MII_ACC_MIIRINDA_				0x000007C0
+#define MII_ACC_MII_WRITE_				0x00000002
+#define MII_ACC_MII_BUSY_				0x00000001
+
+#define MII_DATA					0x07	/* R/W mask 0x0000FFFFUL */
+
+#define FLOW						0x08	/* R/W */
+#define FLOW_FCPT_					0xFFFF0000
+#define FLOW_FCPASS_					0x00000004
+#define FLOW_FCEN_					0x00000002
+#define FLOW_FCBSY_					0x00000001
+
+#define VLAN1						0x09	/* R/W mask 0x0000FFFFUL */
+#define VLAN2						0x0A	/* R/W mask 0x0000FFFFUL */
+
+#define WUFF						0x0B	/* WO */
+
+#define WUCSR						0x0C	/* R/W */
+#define WUCSR_GUE_					0x00000200
+#define WUCSR_WUFR_					0x00000040
+#define WUCSR_MPR_					0x00000020
+#define WUCSR_WAKE_EN_					0x00000004
+#define WUCSR_MPEN_					0x00000002
+
+/*
+ * Phy definitions
+ */
+#define LAN9118_PHY_ID					0x00C0001C
+
+#endif				/* __SMSC911X_H__ */
-- 
1.4.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 15:12 ` [PATCH] " Steve Glendinning
@ 2006-08-01 15:33   ` John W. Linville
  2006-08-02 19:23     ` Steve.Glendinning
  2006-08-01 18:28   ` Scott Murray
  2006-08-01 21:40   ` Francois Romieu
  2 siblings, 1 reply; 35+ messages in thread
From: John W. Linville @ 2006-08-01 15:33 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: netdev, Ian Saturley, Bahadir Balban, Stephen Hemminger,
	Francois Romieu

On Tue, Aug 01, 2006 at 04:12:15PM +0100, Steve Glendinning wrote:
> > > Attached is a driver patch for SMSC911x family of ethernet chips,
> > > generated against 2.6.18-rc1 sources. There's a similar driver in the
> > > tree; this one has been tested by SMSC on all flavors of the chip and
> > > claimed to be efficient.
> >
> > Updated after feedback from Stephen Hemminger.
> >
> > Driver updated to also support LAN921x family.  Workarounds added for
> > known hardware issues.
> 
> Many improvements following feedback from Stephen Hemminger and
> Francois Romieu:
>  - Tasklet removed, NAPI poll used instead
>  - Multiple device support
>  - style fixes & minor improvements

<snip>

> +/* waits for MAC not busy, with timeout.  Assumes MacPhyAccessLock has
> + * already been acquired */
> +static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
> +{
> +	int i;
> +
> +	for (i = 0; i < 40; i++) {
> +		if ((smsc911x_reg_read(pdata, MAC_CSR_CMD)
> +		     & MAC_CSR_CMD_CSR_BUSY_) == 0) {
> +			return 1;
> +		}
> +	}
> +	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
> +		     "MAC_CSR_CMD: 0x%08X", smsc911x_reg_read(pdata,
> +							      MAC_CSR_CMD));
> +	return 0;
> +}

How is the length of this timeout controlled?  IOW, what prevents
it from being too short when the Omegatron 128 running at 10GHz hits
the market?  Are you relying on the MII clock rate?

<snip>

> +/* Gets a phy register, phy_lock must be acquired before calling */
> +static unsigned int
> +smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
> +{
> +	unsigned int addr;
> +	unsigned int result = 0xFFFF;
> +	int i;
> +
> +	/* Confirm MII not busy */
> +	if (unlikely
> +	    ((smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_) != 0)) {
> +		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
> +		return 0;
> +	}
> +
> +	/* Set the address, index & direction (read from PHY) */
> +	addr = (((pdata->phy_address) & 0x1F) << 11)
> +	    | ((index & 0x1F) << 6);
> +	smsc911x_mac_write(pdata, MII_ACC, addr);
> +
> +	/* Wait for read to complete w/ timeout */
> +	for (i = 0; i < 100; i++) {
> +		/* See if MII is finished yet */
> +		if ((smsc911x_mac_read(pdata, MII_ACC)
> +		     & MII_ACC_MII_BUSY_) == 0) {
> +			result = smsc911x_mac_read(pdata, MII_DATA);
> +			return result;
> +		}
> +	}
> +	SMSC_WARNING("Timed out waiting for MII write to finish");

Ditto?

<snip>

> +/* Sets a phy register, phy_lock must be acquired before calling */
> +static void smsc911x_phy_write(struct smsc911x_data *pdata,
> +			       unsigned int index, unsigned int val)
> +{
> +	unsigned int addr;
> +	int i;
> +
> +	/* Confirm MII not busy */
> +	if (unlikely
> +	    ((smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_) != 0)) {
> +		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
> +		return;
> +	}
> +
> +	/* Put the data to write in the MAC */
> +	smsc911x_mac_write(pdata, MII_DATA, val);
> +
> +	/* Set the address, index & direction (write to PHY) */
> +	addr = (((pdata->phy_address) & 0x1F) << 11) |
> +	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
> +	smsc911x_mac_write(pdata, MII_ACC, addr);
> +
> +	/* Wait for write to complete w/ timeout */
> +	for (i = 0; i < 100; i++) {
> +		/* See if MII is finished yet */
> +		if ((smsc911x_mac_read(pdata, MII_ACC)
> +		     & MII_ACC_MII_BUSY_) == 0)
> +			return;
> +	}
> +	SMSC_WARNING("Timed out waiting for MII write to finish");
> +}

Ditto?

> +/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
> + * If something goes wrong, returns -ENODEV to revert back to internal phy.
> + * Performed at initialisation only, phy_lock already acquired. */
> +static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
> +{
> +	unsigned int address;
> +	unsigned int hwcfg;
> +	unsigned int phyid1;
> +	unsigned int phyid2;
> +
> +	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
> +
> +	/* External phy is requested, supported, and detected */
> +	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
> +
> +		/* Attempt to switch to external phy for auto-detecting
> +		 * its address. Assuming tx and rx are stopped because
> +		 * smsc911x_phy_initialise is called before
> +		 * smsc911x_rx_initialise and tx_initialise.
> +		 */
> +
> +		/* Disable phy clocks to the MAC */
> +		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
> +		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
> +		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> +		udelay(10);	/* Enough time for clocks to stop */
> +
> +		/* Switch to external phy */
> +		hwcfg |= HW_CFG_EXT_PHY_EN_;
> +		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> +
> +		/* Enable phy clocks to the MAC */
> +		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
> +		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
> +		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> +		udelay(10);	/* Enough time for clocks to restart */
> +
> +		hwcfg |= HW_CFG_SMI_SEL_;
> +		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> +
> +		/* Auto-detect PHY */
> +		for (address = 0; address <= 31; address++) {
> +			pdata->phy_address = address;
> +			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
> +			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
> +			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
> +				SMSC_TRACE("Detected PHY at address = "
> +					   "0x%02X = %d", address, address);
> +				break;
> +			}
> +		}

Does this need the magic "for (addr=1; addr <=32; addr++)" trick that
has become idiomatic for PHY discovery in our drivers?

<snip>

> --- /dev/null
> +++ b/drivers/net/smsc911x.h

<snip>

> +/* SMSC911x registers and bitfields */
> +#define RX_DATA_FIFO					0x00
> +
> +#define TX_DATA_FIFO					0x20
> +#define TX_CMD_A_ON_COMP_				0x80000000
> +#define TX_CMD_A_BUF_END_ALGN_				0x03000000
> +#define TX_CMD_A_4_BYTE_ALGN_				0x00000000
> +#define TX_CMD_A_16_BYTE_ALGN_				0x01000000
> +#define TX_CMD_A_32_BYTE_ALGN_				0x02000000
> +#define TX_CMD_A_DATA_OFFSET_				0x001F0000
> +#define TX_CMD_A_FIRST_SEG_				0x00002000
> +#define TX_CMD_A_LAST_SEG_				0x00001000
> +#define TX_CMD_A_BUF_SIZE_				0x000007FF
> +#define TX_CMD_B_PKT_TAG_				0xFFFF0000
> +#define TX_CMD_B_ADD_CRC_DISABLE_			0x00002000
> +#define TX_CMD_B_DISABLE_PADDING_			0x00001000
> +#define TX_CMD_B_PKT_BYTE_LENGTH_			0x000007FF

Looks like something went haywire w/ your tabbing in this file...?

Your style overall is very nice and easy to read.

Thanks,

John
-- 
John W. Linville
linville@tuxdriver.com

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 15:12 ` [PATCH] " Steve Glendinning
  2006-08-01 15:33   ` John W. Linville
@ 2006-08-01 18:28   ` Scott Murray
  2006-08-01 19:27     ` Steve.Glendinning
  2006-08-01 21:40   ` Francois Romieu
  2 siblings, 1 reply; 35+ messages in thread
From: Scott Murray @ 2006-08-01 18:28 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: netdev, Ian Saturley, Bahadir Balban, Stephen Hemminger,
	Francois Romieu

On Tue, 1 Aug 2006, Steve Glendinning wrote:

>>> Attached is a driver patch for SMSC911x family of ethernet chips,
>>> generated against 2.6.18-rc1 sources. There's a similar driver in the
>>> tree; this one has been tested by SMSC on all flavors of the chip and
>>> claimed to be efficient.
>>
>> Updated after feedback from Stephen Hemminger.
>>
>> Driver updated to also support LAN921x family.  Workarounds added for
>> known hardware issues.
>
> Many improvements following feedback from Stephen Hemminger and
> Francois Romieu:
> - Tasklet removed, NAPI poll used instead
> - Multiple device support
> - style fixes & minor improvements

Sorry to be coming in late, but I'm curious about why this work is being
submitted as a separate driver, rather than as patches against the driver
from Dustin McIntire that was added a few months ago.  Is the intention to 
go forward with two different drivers for these chips?

Scott


-- 
==============================================================================
Scott Murray, scott@spiteful.org

      "Good, bad ... I'm the guy with the gun." - Ash, "Army of Darkness"

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 18:28   ` Scott Murray
@ 2006-08-01 19:27     ` Steve.Glendinning
  2006-08-01 23:51       ` Scott Murray
  0 siblings, 1 reply; 35+ messages in thread
From: Steve.Glendinning @ 2006-08-01 19:27 UTC (permalink / raw)
  To: Scott Murray
  Cc: Bahadir Balban, ian.saturley, netdev, Francois Romieu,
	Stephen Hemminger

Hi Scott

> Sorry to be coming in late, but I'm curious about why this work is being
> submitted as a separate driver, rather than as patches against the 
driver
> from Dustin McIntire that was added a few months ago.  Is the intention 
to 
> go forward with two different drivers for these chips?

I was waiting for someone to ask this!

This driver has been developed by SMSC & ARM, and has several advantages 
over the already merged smc911x:

- The current driver is arm specific, our smsc911x driver is tested and 
supported on arm, sh, i386
- smsc911x contains support for the new LAN921x family, as well as LAN911x
- smsc911x contains important workarounds for currently known hardware 
issues
- It's shorted, and I believe the coding style to be cleaner and easier to 
follow.

so I'm presenting this as an alternative.  Thoughts?

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 15:12 ` [PATCH] " Steve Glendinning
  2006-08-01 15:33   ` John W. Linville
  2006-08-01 18:28   ` Scott Murray
@ 2006-08-01 21:40   ` Francois Romieu
  2006-08-02 19:39     ` Steve.Glendinning
  2 siblings, 1 reply; 35+ messages in thread
From: Francois Romieu @ 2006-08-01 21:40 UTC (permalink / raw)
  To: Steve Glendinning; +Cc: netdev, Ian Saturley, Bahadir Balban, Stephen Hemminger


[...]
> /* Writes a packet to the TX_DATA_FIFO */
> static inline void
> smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
> 		      unsigned int wordcount)
> {
> 	while (wordcount--) {
> 		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
> 	}

Curly braces are not needed around single line blocks.

[...]
> /* waits for MAC not busy, with timeout.  Assumes MacPhyAccessLock has
>  * already been acquired */
> static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
> {
> 	int i;
> 
> 	for (i = 0; i < 40; i++) {
> 		if ((smsc911x_reg_read(pdata, MAC_CSR_CMD)
> 		     & MAC_CSR_CMD_CSR_BUSY_) == 0) {
> 			return 1;
> 		}
> 	}
> 	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
> 		     "MAC_CSR_CMD: 0x%08X", smsc911x_reg_read(pdata,
> 							      MAC_CSR_CMD));
> 	return 0;

	u32 val;

 	for (i = 0; i < 40; i++) {
 		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
 			return 1;
 	}
 	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
 		     "MAC_CSR_CMD: 0x%08X", val);

-> no painful line-breaking (s/val/{reg/cmd}/ at will).

> }
> 
> /* Fetches a MAC register value. Assumes phy_lock is acquired */
> static unsigned int
> smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
> {
> 	unsigned int result = 0xFFFFFFFF;
> 	unsigned int temp;
> 
> 	/* Wait until not busy */
> 	if (unlikely
> 	    (smsc911x_reg_read(pdata, MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY_)) {
> 		SMSC_WARNING("smsc911x_mac_read failed, "
> 			     "MAC already busy at entry");
> 		return result;
> 	}
> 
> 	/* Send the MAC cmd */
> 	smsc911x_reg_write(((offset & 0x000000FF) | MAC_CSR_CMD_CSR_BUSY_
> 			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
> 
> 	/* Workaround for hardware read-after-write restriction */
> 	temp = smsc911x_reg_read(pdata, BYTE_TEST);
> 
> 	/* Wait for the read to happen */
> 	if (unlikely(!smsc911x_mac_notbusy(pdata)))

Double negation (at least :o) ).

[...]
> /* Gets a phy register, phy_lock must be acquired before calling */
> static unsigned int
> smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
> {
> 	unsigned int addr;
> 	unsigned int result = 0xFFFF;
> 	int i;
> 
> 	/* Confirm MII not busy */
> 	if (unlikely
> 	    ((smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_) != 0)) {
> 		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
> 		return 0;
> 	}
> 
> 	/* Set the address, index & direction (read from PHY) */
> 	addr = (((pdata->phy_address) & 0x1F) << 11)
> 	    | ((index & 0x1F) << 6);
> 	smsc911x_mac_write(pdata, MII_ACC, addr);
> 
> 	/* Wait for read to complete w/ timeout */
> 	for (i = 0; i < 100; i++) {
> 		/* See if MII is finished yet */
> 		if ((smsc911x_mac_read(pdata, MII_ACC)
> 		     & MII_ACC_MII_BUSY_) == 0) {
> 			result = smsc911x_mac_read(pdata, MII_DATA);
> 			return result;
> 		}

 		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
 			return smsc911x_mac_read(pdata, MII_DATA);

> 	}
> 	SMSC_WARNING("Timed out waiting for MII write to finish");
> 
> 	return result;

I'd go with return 0xffff; here and remove 'result' altogether.

> }
> 
> /* Sets a phy register, phy_lock must be acquired before calling */
> static void smsc911x_phy_write(struct smsc911x_data *pdata,
> 			       unsigned int index, unsigned int val)
> {
> 	unsigned int addr;
> 	int i;
> 
> 	/* Confirm MII not busy */
> 	if (unlikely
> 	    ((smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_) != 0)) {

Factor through a smsc911x_mac_busy(pdata) helper ?

[...]
> static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
> {
> 	unsigned int address;
> 	unsigned int hwcfg;
> 	unsigned int phyid1;
> 	unsigned int phyid2;
> 
> 	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
> 
> 	/* External phy is requested, supported, and detected */
> 	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
> 
> 		/* Attempt to switch to external phy for auto-detecting
> 		 * its address. Assuming tx and rx are stopped because
> 		 * smsc911x_phy_initialise is called before
> 		 * smsc911x_rx_initialise and tx_initialise.
> 		 */
> 
> 		/* Disable phy clocks to the MAC */
> 		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
> 		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
> 		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> 		udelay(10);	/* Enough time for clocks to stop */
> 
> 		/* Switch to external phy */
> 		hwcfg |= HW_CFG_EXT_PHY_EN_;
> 		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> 
> 		/* Enable phy clocks to the MAC */
> 		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
> 		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
> 		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> 		udelay(10);	/* Enough time for clocks to restart */

(back to my original question that I should have reworded in a different
thread)

Does the platform guarantees that the register write has actually reached
the real register when the udelay is issued ?

> 
> 		hwcfg |= HW_CFG_SMI_SEL_;
> 		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> 
> 		/* Auto-detect PHY */
> 		for (address = 0; address <= 31; address++) {
> 			pdata->phy_address = address;
> 			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
> 			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
> 			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
> 				SMSC_TRACE("Detected PHY at address = "
> 					   "0x%02X = %d", address, address);
> 				break;
> 			}
> 		}
> 
> 		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
> 			SMSC_WARNING("External PHY is not accessable, "
> 				     "using internal PHY instead");
> 			/* Revert back to interal phy settings. */

s/interal/internal/

[...]
> static int smsc911x_phy_reset(struct smsc911x_data *pdata)
> {
> 	unsigned int temp;
> 	unsigned int lcount = 100000;
> 
> 	SMSC_TRACE("Performing PHY BCR Reset");
> 	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
> 	do {
> 		udelay(10);
> 		temp = smsc911x_phy_read(pdata, MII_BMCR);
> 		lcount--;
> 	} while ((lcount > 0) && (temp & BMCR_RESET));
> 
> 	if (temp & BMCR_RESET) {
> 		SMSC_WARNING("PHY reset failed to complete.");
> 		return 0;
> 	}
> 	/* Extra delay required because the phy may not be completed with
> 	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
> 	 * enough delay but using 500 here to be safe
> 	 */
> 	udelay(500);

It would be nice to turn the udelay() into msleep() but this code is issued
from at least one spnlock-protected section.

[...]
> #ifdef USE_PHY_WORK_AROUND
> static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
> {
> 	unsigned int tries = 0;
> 	unsigned int lcount = 0;
> 	u32 wrsz;
> 	u32 rdsz;
> 	u32 bufp;
> 
> 	for (tries = 0; tries < 10; tries++) {

Wrong visibility whence an useless initialization of 'lcount'.

[...]
> /* Update link mode if any thing has changed */
> static void smsc911x_phy_update_linkmode(struct net_device *dev)
> {
> 	struct smsc911x_data *pdata = netdev_priv(dev);
> 	unsigned int old_link_speed = pdata->link_speed;
> 	unsigned int temp;
> 	unsigned long flags;
> 
> 	spin_lock_irqsave(&pdata->phy_lock, flags);
> 	smsc911x_phy_getlinkmode(pdata);
> 
> 	if (old_link_speed != pdata->link_speed) {
> 		if (pdata->link_speed != LINK_OFF) {
> 			unsigned int phy_reg = 0;
> 			switch (pdata->link_speed) {
> 			case LINK_SPEED_10HD:
> 				SMSC_TRACE("Link is now UP at 10Mbps HD");
> 				break;
> 			case LINK_SPEED_10FD:
> 				SMSC_TRACE("Link is now UP at 10Mbps FD");
> 				break;
> 			case LINK_SPEED_100HD:
> 				SMSC_TRACE("Link is now UP at 100Mbps HD");
> 				break;
> 			case LINK_SPEED_100FD:
> 				SMSC_TRACE("Link is now UP at 100Mbps FD");
> 				break;
> 			default:
> 				SMSC_WARNING("Link is now UP at unknown link "
> 					     "speed: 0x%08X",
> 					     pdata->link_speed);
> 				break;
> 			}
> 			phy_reg = smsc911x_mac_read(pdata, MAC_CR);
> 			phy_reg &= ~(MAC_CR_FDPX_ | MAC_CR_RCVOWN_);
> 
> 			switch (pdata->link_speed) {
> 			case LINK_SPEED_10HD:
> 			case LINK_SPEED_100HD:
> 				phy_reg |= MAC_CR_RCVOWN_;
> 				break;
> 
> 			case LINK_SPEED_10FD:
> 			case LINK_SPEED_100FD:
> 				phy_reg |= MAC_CR_FDPX_;
> 				break;
> 
> 			default:
> 				SMSC_WARNING("Unknown link speed: 0x%08X",
> 					     pdata->link_speed);
> 				break;
> 			}
> 
> 			smsc911x_mac_write(pdata, MAC_CR, phy_reg);
> 
> 			if (pdata->link_settings & LINK_AUTO_NEGOTIATE) {
> 				unsigned int linkpartner = 0;
> 				unsigned int locallink = 0;
> 				locallink = smsc911x_phy_read(pdata, 4);
> 				linkpartner = smsc911x_phy_read(pdata, 5);
> 				switch (pdata->link_speed) {
> 				case LINK_SPEED_10FD:
> 				case LINK_SPEED_100FD:
> 					if (((locallink & linkpartner) &
> 					     LPA_PAUSE_CAP) != 0) {
> 						/* Enable PAUSE receive and transmit */
> 						smsc911x_mac_write(pdata, FLOW,
> 								   0xFFFF0002);
> 						temp =
> 						    smsc911x_reg_read(pdata,
> 								      AFC_CFG);
> 						temp |= 0xF;
> 						smsc911x_reg_write(temp, pdata,
> 								   AFC_CFG);
> 					} else if (((locallink &
> 						     (ADVERTISE_PAUSE_CAP |
> 						      ADVERTISE_PAUSE_ASYM)) ==
> 						    (ADVERTISE_PAUSE_CAP |
> 						     ADVERTISE_PAUSE_ASYM)) &&
> 						   ((linkpartner &
> 						     (LPA_PAUSE_CAP |
> 						      LPA_PAUSE_ASYM)) ==
> 						    LPA_PAUSE_ASYM)) {

Wow...

[...]
> /* Increments the Rx error counters */
> static void
> smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat)
> {
> 	int crc_err;
> 
> 	crc_err = 0;

Merge declaration/initialization.

[...]
> /* Quickly dumps bad packets */
> static void
> smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int count)
> {
> 	if (likely(count >= 4)) {
> 		unsigned int timeout = 500;
> 		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
> 		while (timeout && (smsc911x_reg_read(pdata, RX_DP_CTRL)
> 				   & RX_DP_CTRL_RX_FFWD_)) {
> 			udelay(1);
> 			timeout--;
> 		}
> 		if (unlikely(timeout == 0)) {
> 			SMSC_WARNING("Timed out waiting for RX FFWD "
> 				     "to finish, RX_DP_CTRL: 0x%08X",
> 				     smsc911x_reg_read(pdata, RX_DP_CTRL));
> 		}
> 	} else {
> 		while (count) {
> 			volatile unsigned int temp;
> 			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);

Kill the volatile: there is an implicit readl().

> /* NAPI poll function */
> static int smsc911x_poll(struct net_device *dev, int *budget)
> {
> 	struct smsc911x_data *pdata = netdev_priv(dev);
> 	int npackets = 0;
> 	int quota = min(dev->quota, *budget);
> 
> 	while (npackets < quota) {
> 		unsigned int pktlength;
> 		unsigned int rxstat;
> 		rxstat = smsc911x_rx_get_rxstatus(pdata);
> 
> 		/* break out of while loop if there are no more packets waiting */
> 		if (rxstat == 0)
> 			break;
> 
> 		pktlength = ((rxstat & 0x3FFF0000) >> 16);
> 		smsc911x_rx_counterrors(pdata, rxstat);
> 
> 		if (likely((rxstat & RX_STS_ES_) == 0)) {
> 			struct sk_buff *skb = NULL;

Useless initialization.

> 			skb = dev_alloc_skb(pktlength + 2);

s/2/NET_IP_ALIGN/

> 			if (likely(skb)) {
> 				skb->data = skb->head;
> 				skb->tail = skb->head;
> 				/* Align IP on 16B boundary */
> 				skb_reserve(skb, 2);

s/2/NET_IP_ALIGN/

[...]
> static int smsc911x_open(struct net_device *dev)
> {
> 	struct smsc911x_data *pdata = netdev_priv(dev);
> 	unsigned int mac_high16;
> 	unsigned int mac_low32;
> 	unsigned int timeout;
> 	unsigned int temp;
> 	unsigned long flags;
> 
> 	spin_lock_init(&pdata->phy_lock);
> 
> 	/* Reset the LAN911x */
> 	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
> 	timeout = 10;
> 	do {
> 		udelay(10);
> 		temp = smsc911x_reg_read(pdata, HW_CFG);
> 	} while ((--timeout) && (temp & HW_CFG_SRST_));
> 
> 	if (unlikely(temp & HW_CFG_SRST_)) {
> 		SMSC_WARNING("Failed to complete reset");
> 		return -ENODEV;
> 	}
> 
> 	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
> 	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
> 
> 	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
> 	timeout = 50;
> 	while ((timeout--) &&
> 	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
> 		udelay(10);
> 	}
> 
> 	if (unlikely(timeout == 0)) {
> 		SMSC_WARNING("Timed out waiting for EEPROM "
> 			     "busy bit to clear\n");
> 	}
> #if USE_DEBUG >= 1
> 	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
> #else
> 	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
> #endif
> 
> 	/* Initialise irqs, but leave all sources disabled */
> 	smsc911x_reg_write(0, pdata, INT_EN);
> 	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
> 	/* Set interrupt deassertion to 100uS */
> 	smsc911x_reg_write(((10 << 24) | INT_CFG_IRQ_EN_), pdata, INT_CFG);
> 
> 	/*
> 	 * intcfg |= INT_CFG_IRQ_POL_;  use this to set IRQ_POL bit
> 	 * intcfg |= INT_CFG_IRQ_TYPE_;  use this to set IRQ_TYPE bit
> 	 */
> 
> 	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
> 	pdata->request_irq_disable = 0;
> 	pdata->software_irq_signal = 0;
> 	temp = smsc911x_reg_read(pdata, INT_EN);
> 	temp |= INT_EN_SW_INT_EN_;
> 	smsc911x_reg_write(temp, pdata, INT_EN);
> 	timeout = 1000;
> 	do {
> 		udelay(10);
> 	} while ((--timeout) && (!pdata->software_irq_signal));

	while (timeout--) {
		smp_rmb();
		if (pdata->software_irq_signal)
			break;
		msleep(1);
	}

	+ smp_wmb() in the irq handler.

> 
> 	if (!pdata->software_irq_signal) {
> 		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
> 		       dev->name, dev->irq);
> 		return -ENODEV;
> 	}
> 	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
> 
> 	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
> 	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
> 
> 	spin_lock_irqsave(&pdata->phy_lock, flags);

flags useless: ->open() is issued in irq-enabled context.

> 
> 	/* Read mac address from EEPROM */
> 	mac_high16 = smsc911x_mac_read(pdata, ADDRH);
> 	mac_low32 = smsc911x_mac_read(pdata, ADDRL);
> 
> 	/* Generate random MAC address if eeprom values are invalid */
> 	if ((mac_high16 == 0x0000FFFF) && (mac_low32 == 0xFFFFFFFF)) {
> 		u8 random_mac[6];
> 		random_ether_addr(random_mac);
> 		mac_high16 = (random_mac[5] << 8) | random_mac[4];
> 		mac_low32 = (random_mac[3] << 24) | (random_mac[2] << 16) |
> 		    (random_mac[1] << 8) | random_mac[0];
> 
> 		smsc911x_mac_write(pdata, ADDRH, mac_high16);
> 		smsc911x_mac_write(pdata, ADDRL, mac_low32);
> 		SMSC_TRACE("MAC Address is set to random_ether_addr");
> 	} else {
> 		SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
> 	}
> 
> 	dev->dev_addr[0] = (u8)(mac_low32);
> 	dev->dev_addr[1] = (u8)(mac_low32 >> 8);
> 	dev->dev_addr[2] = (u8)(mac_low32 >> 16);
> 	dev->dev_addr[3] = (u8)(mac_low32 >> 24);
> 	dev->dev_addr[4] = (u8)(mac_high16);
> 	dev->dev_addr[5] = (u8)(mac_high16 >> 8);
> 	printk(KERN_INFO
> 	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
> 	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
> 	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
> 
> 	netif_carrier_off(dev);
> 	if (!smsc911x_phy_initialise(dev)) {
> 		SMSC_WARNING("Failed to initialize PHY");
> 		return -ENODEV;

return with spinlock held (mantra: use goto).

Any chance you could avoid the spinlock ? It would help turning udelay
into sleep (namely smsc911x_phy_reset).

[...]
> #ifdef CONFIG_NET_POLL_CONTROLLER
> void smsc911x_poll_controller(struct net_device *dev)
> {
> 	disable_irq(dev->irq);
> 	smsc911x_irqhandler(0, (void *)dev, NULL);

No need to cast to (void *)

> 	enable_irq(dev->irq);
> }
> #endif				/* CONFIG_NET_POLL_CONTROLLER */
> 
> /* Standard ioctls for mii-tool */
> int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)

static int

> {
> 	struct smsc911x_data *pdata = netdev_priv(dev);
> 	struct mii_ioctl_data *const data =
> 	    (struct mii_ioctl_data *)&ifr->ifr_data;

Use if_mii()

> 	unsigned long flags;
> 
> 	SMSC_TRACE("ioctl cmd 0x%x", cmd);
> 	switch (cmd) {
> 	case SIOCGMIIPHY:
> 	case SIOCDEVPRIVATE:

The SIOCDEVPRIVATE can/should be removed.

[...]
> static int smsc911x_drv_probe(struct platform_device *pdev)
> {
> 	struct net_device *dev;
> 	struct smsc911x_data *pdata;
> 	struct resource *res;
> 	int res_size;
> 	int retval;
> 
> 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> 					   "smsc911x-memory");
> 	if (!res)
> 		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> 	if (!res) {
> 		retval = -ENODEV;
> 		goto out;
> 	}
> 	res_size = res->end - res->start;
> 
> 	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
> 		retval = -EBUSY;
> 		goto out;
> 	}
> 
> 	dev = alloc_etherdev(sizeof(struct smsc911x_data));
> 	if (!dev) {
> 		printk(KERN_WARNING "%s: Could not allocate device.\n",
> 		       SMSC_CHIPNAME);
> 		retval = -ENOMEM;
> 		goto out_release_io;
> 	}
> 	SET_MODULE_OWNER(dev);
> 	SET_NETDEV_DEV(dev, &pdev->dev);
> 
> 	pdata = netdev_priv(dev);
> 	memset(pdata, 0, sizeof(struct smsc911x_data));
> 
> 	dev->irq = platform_get_irq(pdev, 0);
> 	pdata->ioaddr = ioremap_nocache(res->start, res_size);
> 
> 	if (pdata->ioaddr == NULL) {
> 		SMSC_WARNING("Error smsc911x base address invalid");
> 		retval = -ENOMEM;
> 		goto out_free_netdev;
> 	}
> 
> 	if ((retval = smsc911x_init(dev)) < 0)
> 		goto out_unmap_io;
> 
> 	if (request_irq(dev->irq, smsc911x_irqhandler,
> 			SA_INTERRUPT, SMSC_CHIPNAME, dev) != 0) {
> 		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
> 		retval = -ENODEV;

Should be retval = request_irq(...)

> 		goto out_unmap_io;
> 	}
> 
> 	platform_set_drvdata(pdev, dev);
> 	retval = register_netdev(dev);
> 
> 	if (retval) {
> 		SMSC_WARNING("Error %i registering device", retval);
> 		retval = -ENODEV;

No need to overwrite retval.

> 		goto out_unset_drvdata;
> 	} else {
> 		SMSC_TRACE("Network interface: \"%s\"", dev->name);
> 	}
> 
> 	return 0;
> 
> out_unset_drvdata:
> 	platform_set_drvdata(pdev, NULL);

Missing free_irq().

Please number the label: out_0, out_release_io_1, etc... It's easier to check.

> out_unmap_io:
> 	iounmap(pdata->ioaddr);
> out_free_netdev:
> 	free_netdev(dev);
> out_release_io:
> 	release_mem_region(res->start, res->end - res->start);
> out:
> 	return retval;
> }

[...]
> /* Entry point for loading the module */
> static int __init smsc911x_init_module(void)
> {
> 	platform_driver_register(&smsc911x_driver);
> 	return 0;

return platform_driver_register(...);

> --- /dev/null
> ++ b/drivers/net/smsc911x.h
|...]
> static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
> {
> 	u32 reg_val;
> 	unsigned long flags;
> 
> 	/* these two 16-bit reads must be performed consecutively, so must
> 	 * not be interrupted by our own ISR (which would start another
> 	 * read operation) */
> 	local_irq_save(flags);
> 	reg_val =
> 	    ((readw((u16 *)(((unsigned int)pdata->ioaddr) + reg)) & 0xFFFF) |

pdata->ioaddr is void * now. Please simplify.


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 19:27     ` Steve.Glendinning
@ 2006-08-01 23:51       ` Scott Murray
  2006-08-03 15:26         ` Steve.Glendinning
  0 siblings, 1 reply; 35+ messages in thread
From: Scott Murray @ 2006-08-01 23:51 UTC (permalink / raw)
  To: Steve.Glendinning
  Cc: Bahadir Balban, ian.saturley, netdev, Francois Romieu,
	Stephen Hemminger

On Tue, 1 Aug 2006, Steve.Glendinning@smsc.com wrote:

> Hi Scott
>
>> Sorry to be coming in late, but I'm curious about why this work is being
>> submitted as a separate driver, rather than as patches against the
> driver
>> from Dustin McIntire that was added a few months ago.  Is the intention
> to
>> go forward with two different drivers for these chips?
>
> I was waiting for someone to ask this!
>
> This driver has been developed by SMSC & ARM, and has several advantages
> over the already merged smc911x:
>
> - The current driver is arm specific, our smsc911x driver is tested and
> supported on arm, sh, i386
> - smsc911x contains support for the new LAN921x family, as well as LAN911x
> - smsc911x contains important workarounds for currently known hardware
> issues
> - It's shorted, and I believe the coding style to be cleaner and easier to
> follow.
>
> so I'm presenting this as an alternative.  Thoughts?

I think the main concern that my co-workers here at SOMA Networks and I 
have is that having two drivers dilutes the community effort.  There's
also the fact that smc911x seems to have DMA support, which smsc911x does 
not.  Unless there are plans to enhance smsc911x with DMA support, it 
would seem to me to make more sense to apply the LAN921x support and 
hardware workaround changes to smc911x.

Scott


-- 
==============================================================================
Scott Murray, scott@spiteful.org

      "Good, bad ... I'm the guy with the gun." - Ash, "Army of Darkness"

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 15:33   ` John W. Linville
@ 2006-08-02 19:23     ` Steve.Glendinning
  2006-08-02 19:51       ` John W. Linville
  0 siblings, 1 reply; 35+ messages in thread
From: Steve.Glendinning @ 2006-08-02 19:23 UTC (permalink / raw)
  To: John W. Linville
  Cc: Bahadir Balban, ian.saturley, netdev, Francois Romieu,
	Stephen Hemminger

Hi John,

Thanks for all your feedback.

> > +/* waits for MAC not busy, with timeout.  Assumes MacPhyAccessLock 
has
> > + * already been acquired */
> > +static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
> > +{
> > +            int i;
> > +
> > +            for (i = 0; i < 40; i++) {
> > +                            if ((smsc911x_reg_read(pdata, 
MAC_CSR_CMD)
> > +                                 & MAC_CSR_CMD_CSR_BUSY_) == 0) {
> > +                                            return 1;
> > +                            }
> > +            }
> > +            SMSC_WARNING("Timed out waiting for MAC not BUSY. "
> > +                                 "MAC_CSR_CMD: 0x%08X", 
smsc911x_reg_read(pdata,
> > +                                  MAC_CSR_CMD));
> > +            return 0;
> > +}
> 
> How is the length of this timeout controlled?  IOW, what prevents
> it from being too short when the Omegatron 128 running at 10GHz hits
> the market?  Are you relying on the MII clock rate?

The LAN911x and LAN921x devices uses an SRAM-like bus interface with a 
minimum cycle time of 45ns, so smsc_reg_read() and smsc_reg_write() are 
guaranteed to take at least 45ns.  The MAC operates a little slower, but 
the operation shouldn't take longer than 225ns (5 read cycles).

The PHY is accessed via slave registers in the MAC (which then relays the 
command over mii), so its timeout works in the same way.

The timeouts are only there to prevent total lockup if the hardware fails, 
if the part is working it should take nowhere near 40 iterations.


> > +                            /* Auto-detect PHY */
> > +                            for (address = 0; address <= 31; 
address++) {
> > +                                            pdata->phy_address = 
address;
> > +                                            phyid1 = 
smsc911x_phy_read(pdata, MII_PHYSID1);
> > +                                            phyid2 = 
smsc911x_phy_read(pdata, MII_PHYSID2);
> > +                                            if ((phyid1 != 0xFFFFU) 
|| (phyid2 != 0xFFFFU)) {
> > + SMSC_TRACE("Detected PHY at address = "
> > +     "0x%02X = %d", address, address);
> > +                                                            break;
> > +                                            }
> > +                            }
> 
> Does this need the magic "for (addr=1; addr <=32; addr++)" trick that
> has become idiomatic for PHY discovery in our drivers?

I don't understand the question - surely 32 is not a valid PHY address?


> > +/* SMSC911x registers and bitfields */
> > +#define RX_DATA_FIFO                        0x00
> > +
> > +#define TX_DATA_FIFO                        0x20
> > +#define TX_CMD_A_ON_COMP_   0x80000000
> > +#define TX_CMD_A_BUF_END_ALGN_              0x03000000
> > +#define TX_CMD_A_4_BYTE_ALGN_               0x00000000
> > +#define TX_CMD_A_16_BYTE_ALGN_              0x01000000
> > +#define TX_CMD_A_32_BYTE_ALGN_              0x02000000
> > +#define TX_CMD_A_DATA_OFFSET_               0x001F0000
> > +#define TX_CMD_A_FIRST_SEG_                 0x00002000
> > +#define TX_CMD_A_LAST_SEG_          0x00001000
> > +#define TX_CMD_A_BUF_SIZE_          0x000007FF
> > +#define TX_CMD_B_PKT_TAG_   0xFFFF0000
> > +#define TX_CMD_B_ADD_CRC_DISABLE_  0x00002000
> > +#define TX_CMD_B_DISABLE_PADDING_  0x00001000
> > +#define TX_CMD_B_PKT_BYTE_LENGTH_  0x000007FF
> 
> Looks like something went haywire w/ your tabbing in this file...?

Its just the "+ " in the patch, once applied it looks quite pretty!

Best Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 21:40   ` Francois Romieu
@ 2006-08-02 19:39     ` Steve.Glendinning
  2006-08-02 21:07       ` Francois Romieu
  0 siblings, 1 reply; 35+ messages in thread
From: Steve.Glendinning @ 2006-08-02 19:39 UTC (permalink / raw)
  To: Francois Romieu; +Cc: Bahadir Balban, ian.saturley, netdev, Stephen Hemminger

Hi Francois,

Thanks again for all your feedback.  I have implemented most of your 
suggestions, 

> >                              /* Enable phy clocks to the MAC */
> >                              hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
> >                              hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
> >                              smsc911x_reg_write(hwcfg, pdata, HW_CFG);
> >                              udelay(10);             /* Enough time 
for clocks to restart */
> 
> (back to my original question that I should have reworded in a different
> thread)
> 
> Does the platform guarantees that the register write has actually 
reached
> the real register when the udelay is issued ?

I think so, but maybe you can help me check.  The LAN911x device is always 
directly connected to a simple SRAM-like host bus, and smsc911x_reg_write 
is implemented using readl.  Does this implicitly guarantee it to be 
volatile?


> > 
> >              if (!pdata->software_irq_signal) {
> >                              printk(KERN_WARNING "%s: ISR failed 
signaling test (IRQ %d)\n",
> >                                     dev->name, dev->irq);
> >                              return -ENODEV;
> >              }
> >              SMSC_TRACE("IRQ handler passed test using IRQ %d", 
dev->irq);
> > 
> >              printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, 
IRQ: %d\n",
> >                     dev->name, (unsigned long)pdata->ioaddr, 
dev->irq);
> > 
> >              spin_lock_irqsave(&pdata->phy_lock, flags);
> 
> flags useless: ->open() is issued in irq-enabled context.

How do you mean? I thought an irq-enabled context meant i DO have to 
disable irqs?


> >              unsigned long flags;
> > 
> >              SMSC_TRACE("ioctl cmd 0x%x", cmd);
> >              switch (cmd) {
> >              case SIOCGMIIPHY:
> >              case SIOCDEVPRIVATE:
> 
> The SIOCDEVPRIVATE can/should be removed.

I have removed these, they were only in as a quick fix because mii-tool 
here sends SIOCDEVPRIVATE instead of SIOCGMIIPHY.  I fixed my copy of 
mii-tool instead :o)

Best Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-02 19:23     ` Steve.Glendinning
@ 2006-08-02 19:51       ` John W. Linville
  0 siblings, 0 replies; 35+ messages in thread
From: John W. Linville @ 2006-08-02 19:51 UTC (permalink / raw)
  To: Steve.Glendinning
  Cc: Bahadir Balban, ian.saturley, netdev, Francois Romieu,
	Stephen Hemminger

On Wed, Aug 02, 2006 at 08:23:40PM +0100, Steve.Glendinning@smsc.com wrote:

> > Does this need the magic "for (addr=1; addr <=32; addr++)" trick that
> > has become idiomatic for PHY discovery in our drivers?
> 
> I don't understand the question - surely 32 is not a valid PHY address?

That's why it is magic! :-)

The idea is to probe PHY addr 0 last in the series.  Apparently some
PHYs don't like seeing addr 0 or somesuch, so you try it last to avoid
screwing them up.  It may well be folklore and legend at this point.
Still, you will find several examples in the various drivers.
The sundance driver is one example.

John
-- 
John W. Linville
linville@tuxdriver.com

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-02 19:39     ` Steve.Glendinning
@ 2006-08-02 21:07       ` Francois Romieu
  2006-08-04 11:29         ` Steve Glendinning
  0 siblings, 1 reply; 35+ messages in thread
From: Francois Romieu @ 2006-08-02 21:07 UTC (permalink / raw)
  To: Steve.Glendinning; +Cc: Bahadir Balban, ian.saturley, netdev, Stephen Hemminger

Steve.Glendinning@smsc.com <Steve.Glendinning@smsc.com> :
> Mezigues :
[...]
> > Does the platform guarantees that the register write has actually 
> reached
> > the real register when the udelay is issued ?
> 
> I think so, but maybe you can help me check.  The LAN911x device is always 
> directly connected to a simple SRAM-like host bus, and smsc911x_reg_write 
> is implemented using readl.  Does this implicitly guarantee it to be 
> volatile?

(s/readl/writel/)

It's probably safe if it's non-cached SRAM like but I strongly suggest to
read Documentation/DocBook/deviceiobook.tmpl. It explains better than me.

[...]
> > >              spin_lock_irqsave(&pdata->phy_lock, flags);
> > 
> > flags useless: ->open() is issued in irq-enabled context.
> 
> How do you mean? I thought an irq-enabled context meant i DO have to 
> disable irqs?

Yes but you can disable unconditionally and later enable unconditionnally
because you know that the irq are _always_ enabled before the lock (in
->open()).

'flags' saves the state. If the state is constant, you can either:
- s/spin_{lock_irqsave/unlock_irqrestore}/spin_{lock/unlock}_irq/
  (irq always on before the lock)
or:
- s/spin_{lock_irqsave/unlock_irqrestore}/spin_{lock/unlock}/
  (irq always off before the lock)

-- 
Ueimor

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-01 23:51       ` Scott Murray
@ 2006-08-03 15:26         ` Steve.Glendinning
  2006-08-03 21:07           ` Scott Murray
  0 siblings, 1 reply; 35+ messages in thread
From: Steve.Glendinning @ 2006-08-03 15:26 UTC (permalink / raw)
  To: Scott Murray
  Cc: Bahadir Balban, ian.saturley, netdev, Francois Romieu,
	Stephen Hemminger

Hi Scott,

> I think the main concern that my co-workers here at SOMA Networks
> and I have is that having two drivers dilutes the community effort.
> There's also the fact that smc911x seems to have DMA support, which
> smsc911x does not.  Unless there are plans to enhance smsc911x with
> DMA support, it would seem to me to make more sense to apply the
> LAN921x support and hardware workaround changes to smc911x.

I have kept this driver simple for now, focussing on stable support for 
multiple platforms.  While arm is certainly popular, we also have a large 
number of customers on sh, x86 and others, and currently there is no 
in-kernel support (DMA or not) for these people.

Many of our customers use our proprietary drivers (which support DMA on 
many platforms), and we would like to migrate them over to a standard 
in-kernel driver - it would make support easier and everyone can benefit 
from ongoing driver improvements.  The DMA code within these proprietary 
drivers is the next target for submission, although I would like to defer 
additional complexity until we are all happy with the current PIO-only 
implementation.

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-03 15:26         ` Steve.Glendinning
@ 2006-08-03 21:07           ` Scott Murray
  0 siblings, 0 replies; 35+ messages in thread
From: Scott Murray @ 2006-08-03 21:07 UTC (permalink / raw)
  To: Steve.Glendinning
  Cc: Scott Murray, Bahadir Balban, ian.saturley, netdev,
	Francois Romieu, Stephen Hemminger

On Thu, 3 Aug 2006, Steve.Glendinning@smsc.com wrote:

> Hi Scott,
>
>> I think the main concern that my co-workers here at SOMA Networks
>> and I have is that having two drivers dilutes the community effort.
>> There's also the fact that smc911x seems to have DMA support, which
>> smsc911x does not.  Unless there are plans to enhance smsc911x with
>> DMA support, it would seem to me to make more sense to apply the
>> LAN921x support and hardware workaround changes to smc911x.
>
> I have kept this driver simple for now, focussing on stable support for
> multiple platforms.  While arm is certainly popular, we also have a large
> number of customers on sh, x86 and others, and currently there is no
> in-kernel support (DMA or not) for these people.
>
> Many of our customers use our proprietary drivers (which support DMA on
> many platforms), and we would like to migrate them over to a standard
> in-kernel driver - it would make support easier and everyone can benefit
> from ongoing driver improvements.  The DMA code within these proprietary
> drivers is the next target for submission, although I would like to defer
> additional complexity until we are all happy with the current PIO-only
> implementation.

Okay, I guess I'll shut up.  We're going to stick with smc911x for now 
and will probably submit some patches to it in the near future.  If 
smsc911x gets accepted into mainline by Jeff and gains DMA support, we 
may reconsider.

Scott


-- 
==============================================================================
Scott Murray, scott@spiteful.org

      "Good, bad ... I'm the guy with the gun." - Ash, "Army of Darkness"

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-08-02 21:07       ` Francois Romieu
@ 2006-08-04 11:29         ` Steve Glendinning
  0 siblings, 0 replies; 35+ messages in thread
From: Steve Glendinning @ 2006-08-04 11:29 UTC (permalink / raw)
  To: netdev
  Cc: Ian Saturley, Bahadir Balban, Stephen Hemminger, Francois Romieu,
	bilgehan.balban, Steve Glendinning

> > > Attached is a driver patch for SMSC911x family of ethernet chips,
> > > generated against 2.6.18-rc1 sources. There's a similar driver in the
> > > tree; this one has been tested by SMSC on all flavors of the chip and
> > > claimed to be efficient.
> >
> > Updated after feedback from Stephen Hemminger.
>
> Improvements following feedback from Stephen Hemminger and Francois Romieu

Updated after additional feedback from netdev list:
 - added ethtool support
 - simplified phy code using generic mii library

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
---
 drivers/net/Kconfig    |   13 
 drivers/net/Makefile   |    1 
 drivers/net/smsc911x.c | 2035 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h |  445 ++++++++++
 4 files changed, 2494 insertions(+), 0 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 3918990..fe835e5 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -865,6 +865,19 @@ config NET_NETX
 	  <file:Documentation/networking/net-modules.txt>. The module
 	  will be called netx-eth.
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config DM9000
 	tristate "DM9000 support"
 	depends on (ARM || MIPS) && NET_ETHERNET
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index c91e951..51f680b 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -196,6 +196,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
 
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..c8db131
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2035 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2005  SMSC
+ * Copyright (C) 2005 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *
+ * History:
+ *   05/05/2005 bahadir.balban@arm.com
+ *     - Transition to linux coding style
+ *     - Platform driver and module interface
+ *
+ *   17/07/2006 steve.glendinning@smsc.com
+ *     - Added support for LAN921x family
+ *     - Added workaround for multicast filters
+ *
+ *   31/07/2006 steve.glendinning@smsc.com
+ *     - Removed tasklet, using NAPI poll instead
+ *     - Multiple device support
+ *     - Large tidy-up following feedback from netdev list
+ *
+ *   03/08/2006 steve.glendinning@smsc.com
+ *     - Added ethtool support
+ *     - Convert to use generic MII interface
+ *
+ *   04/08/2006 bahadir.balban@arm.com
+ *     - Added ethtool eeprom r/w support
+ */
+
+#include <linux/config.h>
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <asm/bug.h>
+#include <asm/bitops.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_DRV_VERSION	"2006-08-04"
+
+MODULE_LICENSE("GPL");
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes phy_lock is held */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 1;
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", val);
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata,
+			   MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return;
+
+	SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+			return smsc911x_mac_read(pdata, MII_DATA);
+		}
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+	return 0xFFFF;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, u16 val)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return;
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	int reg;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	reg = smsc911x_phy_read(pdata, location);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	return reg;
+}
+
+static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
+				int location, int val)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, location, val);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped because
+		 * smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		spin_lock_irq(&pdata->phy_lock);
+		for (address = 0; address <= 31; address++) {
+			pdata->mii.phy_id = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+		spin_unlock_irq(&pdata->phy_lock);
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to internal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+			pdata->using_extphy = 1;
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* called by phy_initialise and loopback test */
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int i = 100000;
+	unsigned long flags;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but using 500 here to be safe
+	 */
+	msleep(500);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to transmit during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING("Transmit encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int i;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Initialise tx packet */
+	for (i = 0; i < 6; i++) {
+		/* Use broadcast destination address */
+		pdata->loopback_tx_pkt[i] = (char)0xFF;
+	}
+
+	for (i = 6; i < 12; i++) {
+		/* Use incrementing source address */
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (i = 14; i < MIN_PACKET_SIZE; i++) {
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* assumes phy_lock is held */
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+
+	if (pdata->mii.full_duplex) {
+		unsigned int phy_adv;
+		unsigned int phy_lpa;
+		phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE);
+		phy_lpa = smsc911x_phy_read(pdata, MII_LPA);
+		if (phy_adv & phy_lpa & LPA_PAUSE_CAP) {
+			/* Both ends support symmetric pause, enable
+			 * PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp |= 0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else if (((phy_adv & ADVERTISE_PAUSE_ALL) ==
+			    ADVERTISE_PAUSE_ALL) &&
+			   ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) {
+			/* We support symmetric and asym pause, the
+			 * other end only supports asym, Enable PAUSE
+			 * receive, disable PAUSE transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else {
+			/* Disable PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		}
+	} else {
+		smsc911x_mac_write(pdata, FLOW, 0);
+		temp = smsc911x_reg_read(pdata, AFC_CFG);
+		temp |= 0xF;
+		smsc911x_reg_write(temp, pdata, AFC_CFG);
+	}
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) {
+		/* duplex state has changed */
+		unsigned int mac_cr;
+
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (pdata->mii.full_duplex) {
+			SMSC_TRACE("configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE("configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	}
+#ifdef USE_LED1_WORK_AROUND
+	if (netif_carrier_ok(dev)) {
+		if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+		    (!pdata->using_extphy)) {
+			/* Restore orginal GPIO configuration */
+			pdata->gpio_setting = pdata->gpio_orig_setting;
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	} else {
+		/* Check global setting that LED1
+		 * usage is 10/100 indicator */
+		pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG);
+		if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+		    && (!pdata->using_extphy)) {
+			/* Force 10/100 LED off, after saving
+			 * orginal GPIO configuration */
+			pdata->gpio_orig_setting = pdata->gpio_setting;
+
+			pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+			pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+						| GPIO_CFG_GPIODIR0_
+						| GPIO_CFG_GPIOD0_);
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	}
+#endif				/* USE_LED1_WORK_AROUND */
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_phy_update_linkmode(dev, 0);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + HZ;
+		add_timer(&(pdata->link_poll_timer));
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation by open() so
+ * interrupts are enabled */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->mii.phy_id = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->mii.phy_id = 1;
+		break;
+	}
+
+	spin_lock_irq(&pdata->phy_lock);
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+	pdata->mii.advertising = temp;
+
+	if (!pdata->using_extphy) {
+		/* using internal phy, enable PHY interrupts */
+		smsc911x_phy_read(pdata, MII_INTSTS);
+		smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_);
+	}
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_phy_update_linkmode(dev, 1);
+
+	init_timer(&pdata->link_poll_timer);
+	pdata->link_poll_timer.function = smsc911x_phy_checklink;
+	pdata->link_poll_timer.data = (unsigned long)dev;
+	pdata->link_poll_timer.expires = jiffies + HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata)
+{
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet length.
+			 * Since a packet length can never reach the size of 0x8000,
+			 * this bit is reserved so that if packet tracking tags were
+			 * ever used, then those tracking tags would set the reserved bit.
+			 * Accordingly, this control path would be used to look up the
+			 * packet and perhaps free it. It is worth noting that the
+			 * "reserved bit" in the warning above does not reference a
+			 * hardware defined reserved bit but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				pdata->stats.tx_errors++;
+			} else {
+				pdata->stats.tx_packets++;
+				pdata->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				pdata->stats.collisions += 16;
+				pdata->stats.tx_aborted_errors += 1;
+			} else {
+				pdata->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800)) {
+				pdata->stats.tx_carrier_errors += 1;
+			}
+			if (unlikely(tx_stat & 0x00000200)) {
+				pdata->stats.collisions++;
+				pdata->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		pdata->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			pdata->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			pdata->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			pdata->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int count)
+{
+	unsigned int temp;
+
+	if (likely(count >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		while (count--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct net_device *dev, int *budget)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	int npackets = 0;
+	int quota = min(dev->quota, *budget);
+
+	while (npackets < quota) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		/* break out of while loop if there are no more packets waiting */
+		if (!rxstat)
+			break;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(pdata, rxstat);
+
+		if (likely((rxstat & RX_STS_ES_) == 0)) {
+			struct sk_buff *skb;
+			skb = dev_alloc_skb(pktlength + NET_IP_ALIGN);
+			if (likely(skb)) {
+				skb->data = skb->head;
+				skb->tail = skb->head;
+				/* Align IP on 16B boundary */
+				skb_reserve(skb, NET_IP_ALIGN);
+				skb_put(skb, pktlength - 4);
+				smsc911x_rx_readfifo(pdata,
+						     (unsigned int *)skb->head,
+						     pktwords);
+				skb->dev = dev;
+				skb->protocol = eth_type_trans(skb, dev);
+				skb->ip_summed = CHECKSUM_NONE;
+				netif_receive_skb(skb);
+
+				/* Update counters */
+				pdata->stats.rx_packets++;
+				pdata->stats.rx_bytes += (pktlength - 4);
+				dev->last_rx = jiffies;
+				npackets++;
+				continue;
+			} else {
+				SMSC_WARNING("Unable to allocate sk_buff "
+					     "for rx packet, in PIO path");
+				pdata->stats.rx_dropped++;
+			}
+		}
+		/* At this point, the packet is to be read out
+		 * of the fifo and discarded */
+		smsc911x_rx_fastforward(pdata, pktwords);
+	}
+
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+
+	*budget -= npackets;
+	dev->quota -= npackets;
+
+	if (npackets < quota) {
+		unsigned int temp;
+		/* We processed all packets available.  Tell NAPI it can
+		 * stop polling then re-enable rx interrupts */
+		netif_rx_complete(dev);
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp |= INT_EN_RSFL_EN_;
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		return 0;
+	}
+
+	/* There are still packets waiting */
+	return 1;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the phy_lock held */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices 
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->phy_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->phy_lock);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int mac_high16;
+	unsigned int mac_low32;
+	unsigned int timeout;
+	unsigned int temp;
+
+	spin_lock_init(&pdata->phy_lock);
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear\n");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+	/* Set interrupt deassertion to 100uS */
+	smsc911x_reg_write(((10 << 24) | INT_CFG_IRQ_EN_), pdata, INT_CFG);
+
+	/*
+	 * intcfg |= INT_CFG_IRQ_POL_;  use this to set IRQ_POL bit
+	 * intcfg |= INT_CFG_IRQ_TYPE_;  use this to set IRQ_TYPE bit
+	 */
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->request_irq_disable = 0;
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	timeout = 1000;
+	while (timeout--) {
+		smp_rmb();
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	spin_lock_irq(&pdata->phy_lock);
+
+	/* Read mac address from EEPROM */
+	mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+	mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+
+	/* Generate random MAC address if eeprom values are invalid */
+	if ((mac_high16 == 0x0000FFFF) && (mac_low32 == 0xFFFFFFFF)) {
+		u8 random_mac[6];
+		random_ether_addr(random_mac);
+		mac_high16 = (random_mac[5] << 8) | random_mac[4];
+		mac_low32 = (random_mac[3] << 24) | (random_mac[2] << 16) |
+		    (random_mac[1] << 8) | random_mac[0];
+
+		smsc911x_mac_write(pdata, ADDRH, mac_high16);
+		smsc911x_mac_write(pdata, ADDRL, mac_low32);
+		SMSC_TRACE("MAC Address is set to random_ether_addr");
+	} else {
+		SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+	}
+
+	spin_unlock_irq(&pdata->phy_lock);
+
+	dev->dev_addr[0] = (u8)(mac_low32);
+	dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+	dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+	dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+	dev->dev_addr[4] = (u8)(mac_high16);
+	dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	netif_carrier_off(dev);
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	temp &= HW_CFG_TX_FIF_SZ_;
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	smsc911x_reg_write((smsc911x_reg_read(pdata, INT_CFG) &
+			    (~INT_CFG_IRQ_EN_)), pdata, INT_CFG);
+	netif_stop_queue(dev);
+
+	/* At this point all Rx and Tx activity is stopped */
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(pdata);
+
+	SMSC_TRACE("<--Simp911x_stop");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	dev_kfree_skb(skb);
+	freespace -= (skb->len + 32);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(pdata);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return &pdata->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (pdata->multicast_update_pending == 0) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t
+smsc911x_irqhandler(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+		if (pdata->request_irq_disable) {
+			temp = smsc911x_reg_read(pdata, INT_CFG);
+			temp &= (~INT_CFG_IRQ_EN_);
+			smsc911x_reg_write(temp, pdata, INT_CFG);
+			/* Prevent irqs from being handled */
+			intsts = 0;
+		}
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		/* Disable Rx interrupts and schedule NAPI poll */
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RSFL_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		netif_rx_schedule(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_PHY_INT_)) {
+		smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS);
+		spin_lock(&pdata->phy_lock);
+		temp = smsc911x_phy_read(pdata, MII_INTSTS);
+		spin_unlock(&pdata->phy_lock);
+		SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp);
+		smsc911x_phy_update_linkmode(dev, 0);
+		serviced = IRQ_HANDLED;
+	}
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev, NULL);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		data->phy_id = pdata->mii.phy_id;
+		return 0;
+	case SIOCGMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return mii_ethtool_gset(&pdata->mii, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_ethtool_sset(&pdata->mii, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strncpy(info->bus_info, dev->class_dev.dev->bus_id,
+		sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_nway_restart(&pdata->mii);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	for (i = MAC_CR; i <= WUCSR; i++)
+		data[j++] = smsc911x_mac_read(pdata, i);
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_phy_read(pdata, i);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(temp, pdata, GPIO_CFG);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE("op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING("Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(e2cmd, pdata, E2P_CMD);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE("TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE("Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write((u32)data, pdata, E2P_DATA);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures */
+static int smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	dev->poll = smsc911x_poll;
+	dev->weight = 64;
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	pdata->mii.phy_id_mask = 0x1f;
+	pdata->mii.reg_num_mask = 0x1f;
+	pdata->mii.force_media = 0;
+	pdata->mii.full_duplex = 0;
+	pdata->mii.dev = dev;
+	pdata->mii.mdio_read = smsc911x_mdio_read;
+	pdata->mii.mdio_write = smsc911x_mdio_write;
+
+	pdata->msg_enable = NETIF_MSG_LINK;
+
+	return 0;
+}
+
+static int smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	int res_size;
+	int retval;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+	SET_MODULE_OWNER(dev);
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+	memset(pdata, 0, sizeof(struct smsc911x_data));
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	if ((retval = smsc911x_init(dev)) < 0)
+		goto out_unmap_io_3;
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, SA_INTERRUPT,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	return 0;
+
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.suspend = 0,		/* TODO: Add suspend routine */
+	.resume = 0,		/* TODO: Add resume routine */
+	.driver = {
+		   .name = SMSC_CHIPNAME,
+		   },
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..d2f5b33
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,445 @@
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	(u32)1600
+#define SMSC911X_EEPROM_SIZE	(u32)7
+#define USE_DEBUG 		0
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+/* 10/100 LED link-state inversion when media is disconnected */
+#define USE_LED1_WORK_AROUND
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt,args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_TRACE(msg,args...)
+#endif				/* USE_DEBUG >= 2 */
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+	unsigned int idrev;
+	unsigned int generation;	/* used to decide which workarounds apply */
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+	struct net_device_stats stats;
+	struct mii_if_info mii;
+	unsigned int using_extphy;
+	u32 msg_enable;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+#endif
+	struct timer_list link_poll_timer;
+	int stop_link_poll;
+
+	int request_irq_disable;
+	int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	u32 reg_val;
+	unsigned long flags;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		   ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	local_irq_restore(flags);
+
+	return reg_val;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	local_irq_restore(flags);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define ENDIAN				0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
-- 
1.4.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
@ 2006-12-04 22:31 Steve Glendinning
  0 siblings, 0 replies; 35+ messages in thread
From: Steve Glendinning @ 2006-12-04 22:31 UTC (permalink / raw)
  To: netdev; +Cc: Ian Saturley, Bahadir Balban, Stephen Hemminger,
	Steve Glendinning

Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
ethernet controllers.  There is an existing smc911x driver in the tree;
this is intended to replace it.

This driver contains workarounds for all known hardware issues, and has
been tested on all flavours of the chip on multiple architectures.

Dustin McIntire (the author of the smc911x driver) has also expressed
his support for switching to this driver.

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
---
 drivers/net/Kconfig    |   13 
 drivers/net/Makefile   |    1 
 drivers/net/smsc911x.c | 2034 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h |  445 +++++++++++
 4 files changed, 2493 insertions(+), 0 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 9de0eed..07ae1c9 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -873,6 +873,19 @@ config NET_NETX
 	  <file:Documentation/networking/net-modules.txt>. The module
 	  will be called netx-eth.
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config DM9000
 	tristate "DM9000 support"
 	depends on (ARM || MIPS) && NET_ETHERNET
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 4c0d4e5..0b65c85 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -194,6 +194,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
 
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..c71eb30
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2034 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2005  SMSC
+ * Copyright (C) 2005 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *
+ * History:
+ *   05/05/2005 bahadir.balban@arm.com
+ *     - Transition to linux coding style
+ *     - Platform driver and module interface
+ *
+ *   17/07/2006 steve.glendinning@smsc.com
+ *     - Added support for LAN921x family
+ *     - Added workaround for multicast filters
+ *
+ *   31/07/2006 steve.glendinning@smsc.com
+ *     - Removed tasklet, using NAPI poll instead
+ *     - Multiple device support
+ *     - Large tidy-up following feedback from netdev list
+ *
+ *   03/08/2006 steve.glendinning@smsc.com
+ *     - Added ethtool support
+ *     - Convert to use generic MII interface
+ *
+ *   04/08/2006 bahadir.balban@arm.com
+ *     - Added ethtool eeprom r/w support
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <asm/bug.h>
+#include <asm/bitops.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_DRV_VERSION	"2006-08-04"
+
+MODULE_LICENSE("GPL");
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes phy_lock is held */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 1;
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", val);
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata,
+			   MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return;
+
+	SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+			return smsc911x_mac_read(pdata, MII_DATA);
+		}
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+	return 0xFFFF;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, u16 val)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return;
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	int reg;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	reg = smsc911x_phy_read(pdata, location);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	return reg;
+}
+
+static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
+				int location, int val)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, location, val);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped because
+		 * smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		spin_lock_irq(&pdata->phy_lock);
+		for (address = 0; address <= 31; address++) {
+			pdata->mii.phy_id = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+		spin_unlock_irq(&pdata->phy_lock);
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to internal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+			pdata->using_extphy = 1;
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* called by phy_initialise and loopback test */
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int i = 100000;
+	unsigned long flags;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but using 500 here to be safe
+	 */
+	msleep(500);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to transmit during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING("Transmit encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int i;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Initialise tx packet */
+	for (i = 0; i < 6; i++) {
+		/* Use broadcast destination address */
+		pdata->loopback_tx_pkt[i] = (char)0xFF;
+	}
+
+	for (i = 6; i < 12; i++) {
+		/* Use incrementing source address */
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (i = 14; i < MIN_PACKET_SIZE; i++) {
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* assumes phy_lock is held */
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+
+	if (pdata->mii.full_duplex) {
+		unsigned int phy_adv;
+		unsigned int phy_lpa;
+		phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE);
+		phy_lpa = smsc911x_phy_read(pdata, MII_LPA);
+		if (phy_adv & phy_lpa & LPA_PAUSE_CAP) {
+			/* Both ends support symmetric pause, enable
+			 * PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp |= 0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else if (((phy_adv & ADVERTISE_PAUSE_ALL) ==
+			    ADVERTISE_PAUSE_ALL) &&
+			   ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) {
+			/* We support symmetric and asym pause, the
+			 * other end only supports asym, Enable PAUSE
+			 * receive, disable PAUSE transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else {
+			/* Disable PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		}
+	} else {
+		smsc911x_mac_write(pdata, FLOW, 0);
+		temp = smsc911x_reg_read(pdata, AFC_CFG);
+		temp |= 0xF;
+		smsc911x_reg_write(temp, pdata, AFC_CFG);
+	}
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) {
+		/* duplex state has changed */
+		unsigned int mac_cr;
+
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (pdata->mii.full_duplex) {
+			SMSC_TRACE("configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE("configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	}
+#ifdef USE_LED1_WORK_AROUND
+	if (netif_carrier_ok(dev)) {
+		if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+		    (!pdata->using_extphy)) {
+			/* Restore orginal GPIO configuration */
+			pdata->gpio_setting = pdata->gpio_orig_setting;
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	} else {
+		/* Check global setting that LED1
+		 * usage is 10/100 indicator */
+		pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG);
+		if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+		    && (!pdata->using_extphy)) {
+			/* Force 10/100 LED off, after saving
+			 * orginal GPIO configuration */
+			pdata->gpio_orig_setting = pdata->gpio_setting;
+
+			pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+			pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+						| GPIO_CFG_GPIODIR0_
+						| GPIO_CFG_GPIOD0_);
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	}
+#endif				/* USE_LED1_WORK_AROUND */
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_phy_update_linkmode(dev, 0);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + HZ;
+		add_timer(&(pdata->link_poll_timer));
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation by open() so
+ * interrupts are enabled */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->mii.phy_id = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->mii.phy_id = 1;
+		break;
+	}
+
+	spin_lock_irq(&pdata->phy_lock);
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+	pdata->mii.advertising = temp;
+
+	if (!pdata->using_extphy) {
+		/* using internal phy, enable PHY interrupts */
+		smsc911x_phy_read(pdata, MII_INTSTS);
+		smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_);
+	}
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_phy_update_linkmode(dev, 1);
+
+	init_timer(&pdata->link_poll_timer);
+	pdata->link_poll_timer.function = smsc911x_phy_checklink;
+	pdata->link_poll_timer.data = (unsigned long)dev;
+	pdata->link_poll_timer.expires = jiffies + HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata)
+{
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet length.
+			 * Since a packet length can never reach the size of 0x8000,
+			 * this bit is reserved so that if packet tracking tags were
+			 * ever used, then those tracking tags would set the reserved bit.
+			 * Accordingly, this control path would be used to look up the
+			 * packet and perhaps free it. It is worth noting that the
+			 * "reserved bit" in the warning above does not reference a
+			 * hardware defined reserved bit but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				pdata->stats.tx_errors++;
+			} else {
+				pdata->stats.tx_packets++;
+				pdata->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				pdata->stats.collisions += 16;
+				pdata->stats.tx_aborted_errors += 1;
+			} else {
+				pdata->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800)) {
+				pdata->stats.tx_carrier_errors += 1;
+			}
+			if (unlikely(tx_stat & 0x00000200)) {
+				pdata->stats.collisions++;
+				pdata->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		pdata->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			pdata->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			pdata->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			pdata->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int count)
+{
+	unsigned int temp;
+
+	if (likely(count >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		while (count--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct net_device *dev, int *budget)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	int npackets = 0;
+	int quota = min(dev->quota, *budget);
+
+	while (npackets < quota) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		/* break out of while loop if there are no more packets waiting */
+		if (!rxstat)
+			break;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(pdata, rxstat);
+
+		if (likely((rxstat & RX_STS_ES_) == 0)) {
+			struct sk_buff *skb;
+			skb = dev_alloc_skb(pktlength + NET_IP_ALIGN);
+			if (likely(skb)) {
+				skb->data = skb->head;
+				skb->tail = skb->head;
+				/* Align IP on 16B boundary */
+				skb_reserve(skb, NET_IP_ALIGN);
+				skb_put(skb, pktlength - 4);
+				smsc911x_rx_readfifo(pdata,
+						     (unsigned int *)skb->head,
+						     pktwords);
+				skb->dev = dev;
+				skb->protocol = eth_type_trans(skb, dev);
+				skb->ip_summed = CHECKSUM_NONE;
+				netif_receive_skb(skb);
+
+				/* Update counters */
+				pdata->stats.rx_packets++;
+				pdata->stats.rx_bytes += (pktlength - 4);
+				dev->last_rx = jiffies;
+				npackets++;
+				continue;
+			} else {
+				SMSC_WARNING("Unable to allocate sk_buff "
+					     "for rx packet, in PIO path");
+				pdata->stats.rx_dropped++;
+			}
+		}
+		/* At this point, the packet is to be read out
+		 * of the fifo and discarded */
+		smsc911x_rx_fastforward(pdata, pktwords);
+	}
+
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+
+	*budget -= npackets;
+	dev->quota -= npackets;
+
+	if (npackets < quota) {
+		unsigned int temp;
+		/* We processed all packets available.  Tell NAPI it can
+		 * stop polling then re-enable rx interrupts */
+		netif_rx_complete(dev);
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp |= INT_EN_RSFL_EN_;
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		return 0;
+	}
+
+	/* There are still packets waiting */
+	return 1;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the phy_lock held */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices 
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->phy_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->phy_lock);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int mac_high16;
+	unsigned int mac_low32;
+	unsigned int timeout;
+	unsigned int temp;
+
+	spin_lock_init(&pdata->phy_lock);
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear\n");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+	/* Set interrupt deassertion to 100uS */
+	smsc911x_reg_write(((10 << 24) | INT_CFG_IRQ_EN_), pdata, INT_CFG);
+
+	/*
+	 * intcfg |= INT_CFG_IRQ_POL_;  use this to set IRQ_POL bit
+	 * intcfg |= INT_CFG_IRQ_TYPE_;  use this to set IRQ_TYPE bit
+	 */
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->request_irq_disable = 0;
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	timeout = 1000;
+	while (timeout--) {
+		smp_rmb();
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	spin_lock_irq(&pdata->phy_lock);
+
+	/* Read mac address from EEPROM */
+	mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+	mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+
+	/* Generate random MAC address if eeprom values are invalid */
+	if ((mac_high16 == 0x0000FFFF) && (mac_low32 == 0xFFFFFFFF)) {
+		u8 random_mac[6];
+		random_ether_addr(random_mac);
+		mac_high16 = (random_mac[5] << 8) | random_mac[4];
+		mac_low32 = (random_mac[3] << 24) | (random_mac[2] << 16) |
+		    (random_mac[1] << 8) | random_mac[0];
+
+		smsc911x_mac_write(pdata, ADDRH, mac_high16);
+		smsc911x_mac_write(pdata, ADDRL, mac_low32);
+		SMSC_TRACE("MAC Address is set to random_ether_addr");
+	} else {
+		SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+	}
+
+	spin_unlock_irq(&pdata->phy_lock);
+
+	dev->dev_addr[0] = (u8)(mac_low32);
+	dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+	dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+	dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+	dev->dev_addr[4] = (u8)(mac_high16);
+	dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	netif_carrier_off(dev);
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	temp &= HW_CFG_TX_FIF_SZ_;
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	smsc911x_reg_write((smsc911x_reg_read(pdata, INT_CFG) &
+			    (~INT_CFG_IRQ_EN_)), pdata, INT_CFG);
+	netif_stop_queue(dev);
+
+	/* At this point all Rx and Tx activity is stopped */
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(pdata);
+
+	SMSC_TRACE("<--Simp911x_stop");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	dev_kfree_skb(skb);
+	freespace -= (skb->len + 32);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(pdata);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	smsc911x_tx_update_txcounters(pdata);
+	return &pdata->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (pdata->multicast_update_pending == 0) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+		if (pdata->request_irq_disable) {
+			temp = smsc911x_reg_read(pdata, INT_CFG);
+			temp &= (~INT_CFG_IRQ_EN_);
+			smsc911x_reg_write(temp, pdata, INT_CFG);
+			/* Prevent irqs from being handled */
+			intsts = 0;
+		}
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		/* Disable Rx interrupts and schedule NAPI poll */
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RSFL_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		netif_rx_schedule(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_PHY_INT_)) {
+		smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS);
+		spin_lock(&pdata->phy_lock);
+		temp = smsc911x_phy_read(pdata, MII_INTSTS);
+		spin_unlock(&pdata->phy_lock);
+		SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp);
+		smsc911x_phy_update_linkmode(dev, 0);
+		serviced = IRQ_HANDLED;
+	}
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		data->phy_id = pdata->mii.phy_id;
+		return 0;
+	case SIOCGMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return mii_ethtool_gset(&pdata->mii, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_ethtool_sset(&pdata->mii, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strncpy(info->bus_info, dev->class_dev.dev->bus_id,
+		sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_nway_restart(&pdata->mii);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	for (i = MAC_CR; i <= WUCSR; i++)
+		data[j++] = smsc911x_mac_read(pdata, i);
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_phy_read(pdata, i);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(temp, pdata, GPIO_CFG);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE("op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING("Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(e2cmd, pdata, E2P_CMD);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE("TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE("Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write((u32)data, pdata, E2P_DATA);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures */
+static int smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	dev->poll = smsc911x_poll;
+	dev->weight = 64;
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	pdata->mii.phy_id_mask = 0x1f;
+	pdata->mii.reg_num_mask = 0x1f;
+	pdata->mii.force_media = 0;
+	pdata->mii.full_duplex = 0;
+	pdata->mii.dev = dev;
+	pdata->mii.mdio_read = smsc911x_mdio_read;
+	pdata->mii.mdio_write = smsc911x_mdio_write;
+
+	pdata->msg_enable = NETIF_MSG_LINK;
+
+	return 0;
+}
+
+static int smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	int res_size;
+	int retval;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+	SET_MODULE_OWNER(dev);
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+	memset(pdata, 0, sizeof(struct smsc911x_data));
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	if ((retval = smsc911x_init(dev)) < 0)
+		goto out_unmap_io_3;
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, SA_INTERRUPT,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	return 0;
+
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.suspend = 0,		/* TODO: Add suspend routine */
+	.resume = 0,		/* TODO: Add resume routine */
+	.driver = {
+		   .name = SMSC_CHIPNAME,
+		   },
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..d2f5b33
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,445 @@
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	(u32)1600
+#define SMSC911X_EEPROM_SIZE	(u32)7
+#define USE_DEBUG 		0
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+/* 10/100 LED link-state inversion when media is disconnected */
+#define USE_LED1_WORK_AROUND
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt,args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_TRACE(msg,args...)
+#endif				/* USE_DEBUG >= 2 */
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+	unsigned int idrev;
+	unsigned int generation;	/* used to decide which workarounds apply */
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+	struct net_device_stats stats;
+	struct mii_if_info mii;
+	unsigned int using_extphy;
+	u32 msg_enable;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+#endif
+	struct timer_list link_poll_timer;
+	int stop_link_poll;
+
+	int request_irq_disable;
+	int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	u32 reg_val;
+	unsigned long flags;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		   ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	local_irq_restore(flags);
+
+	return reg_val;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	local_irq_restore(flags);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define ENDIAN				0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
-- 
1.4.2.4


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
@ 2006-12-30 16:34 Steve Glendinning
  2007-01-04 14:42 ` Pierre TARDY
  0 siblings, 1 reply; 35+ messages in thread
From: Steve Glendinning @ 2006-12-30 16:34 UTC (permalink / raw)
  To: netdev; +Cc: Ian Saturley, Bahadir Balban, Pierre Tardy, Steve Glendinning

Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
ethernet controllers.  There is an existing smc911x driver in the tree;
this is intended to replace it.

This driver contains workarounds for all known hardware issues, and has
been tested on all flavours of the chip on multiple architectures.

Dustin McIntire (the author of the smc911x driver) has expressed his
support for switching to this driver.

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
---
 drivers/net/Kconfig    |   13 +
 drivers/net/Makefile   |    1 +
 drivers/net/smsc911x.c | 2095 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h |  417 ++++++++++
 4 files changed, 2526 insertions(+), 0 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 8aa8dd0..45a5ed6 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -873,6 +873,19 @@ config NET_NETX
 	  <file:Documentation/networking/net-modules.txt>. The module
 	  will be called netx-eth.
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config DM9000
 	tristate "DM9000 support"
 	depends on (ARM || MIPS) && NET_ETHERNET
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 4c0d4e5..0b65c85 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -194,6 +194,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
 
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..36b975e
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2095 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2006 SMSC
+ * Copyright (C) 2005-2006 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *
+ * History:
+ *   05/05/2005 bahadir.balban@arm.com
+ *     - Transition to linux coding style
+ *     - Platform driver and module interface
+ *
+ *   17/07/2006 steve.glendinning@smsc.com
+ *     - Added support for LAN921x family
+ *     - Added workaround for multicast filters
+ *
+ *   31/07/2006 steve.glendinning@smsc.com
+ *     - Removed tasklet, using NAPI poll instead
+ *     - Multiple device support
+ *     - Large tidy-up following feedback from netdev list
+ *
+ *   03/08/2006 steve.glendinning@smsc.com
+ *     - Added ethtool support
+ *     - Convert to use generic MII interface
+ *
+ *   04/08/2006 bahadir.balban@arm.com
+ *     - Added ethtool eeprom r/w support
+ *
+ *   30/12/2006 steve.glendinning@smsc.com
+ *     - Fix for external PHY initialisation
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <asm/bug.h>
+#include <asm/bitops.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_DRV_VERSION	"2006-12-30"
+
+MODULE_LICENSE("GPL");
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	u32 reg_val;
+	unsigned long flags;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		   ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	local_irq_restore(flags);
+
+	return reg_val;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	local_irq_restore(flags);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes phy_lock is held */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 1;
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", val);
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata,
+			   MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return;
+
+	SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+			return smsc911x_mac_read(pdata, MII_DATA);
+		}
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+	return 0xFFFF;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, u16 val)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return;
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	int reg;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	reg = smsc911x_phy_read(pdata, location);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	return reg;
+}
+
+static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
+				int location, int val)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, location, val);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/*
+		 * Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		spin_lock_irq(&pdata->phy_lock);
+		for (address = 0; address <= 31; address++) {
+			pdata->mii.phy_id = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+		spin_unlock_irq(&pdata->phy_lock);
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to internal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+			pdata->using_extphy = 1;
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* called by phy_initialise and loopback test */
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int i = 100000;
+	unsigned long flags;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but sleeping for 1ms here to be safe
+	 */
+	msleep(1);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to transmit during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING("Transmit encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int i;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Initialise tx packet */
+	for (i = 0; i < 6; i++) {
+		/* Use broadcast destination address */
+		pdata->loopback_tx_pkt[i] = (char)0xFF;
+	}
+
+	for (i = 6; i < 12; i++) {
+		/* Use incrementing source address */
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (i = 14; i < MIN_PACKET_SIZE; i++) {
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* assumes phy_lock is held */
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+
+	if (pdata->mii.full_duplex) {
+		unsigned int phy_adv;
+		unsigned int phy_lpa;
+		phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE);
+		phy_lpa = smsc911x_phy_read(pdata, MII_LPA);
+		if (phy_adv & phy_lpa & LPA_PAUSE_CAP) {
+			/* Both ends support symmetric pause, enable
+			 * PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp |= 0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else if (((phy_adv & ADVERTISE_PAUSE_ALL) ==
+			    ADVERTISE_PAUSE_ALL) &&
+			   ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) {
+			/* We support symmetric and asym pause, the
+			 * other end only supports asym, Enable PAUSE
+			 * receive, disable PAUSE transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else {
+			/* Disable PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		}
+	} else {
+		smsc911x_mac_write(pdata, FLOW, 0);
+		temp = smsc911x_reg_read(pdata, AFC_CFG);
+		temp |= 0xF;
+		smsc911x_reg_write(temp, pdata, AFC_CFG);
+	}
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) {
+		/* duplex state has changed */
+		unsigned int mac_cr;
+
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (pdata->mii.full_duplex) {
+			SMSC_TRACE("configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE("configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	}
+#ifdef USE_LED1_WORK_AROUND
+	if (netif_carrier_ok(dev)) {
+		if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+		    (!pdata->using_extphy)) {
+			/* Restore orginal GPIO configuration */
+			pdata->gpio_setting = pdata->gpio_orig_setting;
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	} else {
+		/* Check global setting that LED1
+		 * usage is 10/100 indicator */
+		pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG);
+		if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+		    && (!pdata->using_extphy)) {
+			/* Force 10/100 LED off, after saving
+			 * orginal GPIO configuration */
+			pdata->gpio_orig_setting = pdata->gpio_setting;
+
+			pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+			pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+						| GPIO_CFG_GPIODIR0_
+						| GPIO_CFG_GPIOD0_);
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	}
+#endif				/* USE_LED1_WORK_AROUND */
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_phy_update_linkmode(dev, 0);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + HZ;
+		add_timer(&(pdata->link_poll_timer));
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation by open() so
+ * interrupts are enabled */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->mii.phy_id = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->mii.phy_id = 1;
+		break;
+	}
+
+	spin_lock_irq(&pdata->phy_lock);
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+	pdata->mii.advertising = temp;
+
+	if (!pdata->using_extphy) {
+		/* using internal phy, enable PHY interrupts */
+		smsc911x_phy_read(pdata, MII_INTSTS);
+		smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_);
+	}
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_phy_update_linkmode(dev, 1);
+
+	init_timer(&pdata->link_poll_timer);
+	pdata->link_poll_timer.function = smsc911x_phy_checklink;
+	pdata->link_poll_timer.data = (unsigned long)dev;
+	pdata->link_poll_timer.expires = jiffies + HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata)
+{
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet length.
+			 * Since a packet length can never reach the size of 0x8000,
+			 * this bit is reserved so that if packet tracking tags were
+			 * ever used, then those tracking tags would set the reserved bit.
+			 * Accordingly, this control path would be used to look up the
+			 * packet and perhaps free it. It is worth noting that the
+			 * "reserved bit" in the warning above does not reference a
+			 * hardware defined reserved bit but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				pdata->stats.tx_errors++;
+			} else {
+				pdata->stats.tx_packets++;
+				pdata->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				pdata->stats.collisions += 16;
+				pdata->stats.tx_aborted_errors += 1;
+			} else {
+				pdata->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800)) {
+				pdata->stats.tx_carrier_errors += 1;
+			}
+			if (unlikely(tx_stat & 0x00000200)) {
+				pdata->stats.collisions++;
+				pdata->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		pdata->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			pdata->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			pdata->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			pdata->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int count)
+{
+	unsigned int temp;
+
+	if (likely(count >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		while (count--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct net_device *dev, int *budget)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	int npackets = 0;
+	int quota = min(dev->quota, *budget);
+
+	while (npackets < quota) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		/* break out of while loop if there are no more packets waiting */
+		if (!rxstat)
+			break;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(pdata, rxstat);
+
+		if (likely((rxstat & RX_STS_ES_) == 0)) {
+			struct sk_buff *skb;
+			skb = dev_alloc_skb(pktlength + NET_IP_ALIGN);
+			if (likely(skb)) {
+				skb->data = skb->head;
+				skb->tail = skb->head;
+				/* Align IP on 16B boundary */
+				skb_reserve(skb, NET_IP_ALIGN);
+				skb_put(skb, pktlength - 4);
+				smsc911x_rx_readfifo(pdata,
+						     (unsigned int *)skb->head,
+						     pktwords);
+				skb->dev = dev;
+				skb->protocol = eth_type_trans(skb, dev);
+				skb->ip_summed = CHECKSUM_NONE;
+				netif_receive_skb(skb);
+
+				/* Update counters */
+				pdata->stats.rx_packets++;
+				pdata->stats.rx_bytes += (pktlength - 4);
+				dev->last_rx = jiffies;
+				npackets++;
+				continue;
+			} else {
+				SMSC_WARNING("Unable to allocate sk_buff "
+					     "for rx packet, in PIO path");
+				pdata->stats.rx_dropped++;
+			}
+		}
+		/* At this point, the packet is to be read out
+		 * of the fifo and discarded */
+		smsc911x_rx_fastforward(pdata, pktwords);
+	}
+
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+
+	*budget -= npackets;
+	dev->quota -= npackets;
+
+	if (npackets < quota) {
+		unsigned int temp;
+		/* We processed all packets available.  Tell NAPI it can
+		 * stop polling then re-enable rx interrupts */
+		netif_rx_complete(dev);
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp |= INT_EN_RSFL_EN_;
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		return 0;
+	}
+
+	/* There are still packets waiting */
+	return 1;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the phy_lock held */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices 
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->phy_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->phy_lock);
+}
+
+static int smsc911x_soft_reset(struct smsc911x_data *pdata)
+{
+	unsigned int timeout;
+	unsigned int temp;
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int mac_high16;
+	unsigned int mac_low32;
+	unsigned int timeout;
+	unsigned int temp;
+
+	spin_lock_init(&pdata->phy_lock);
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear\n");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+	/* Set interrupt deassertion to 100uS */
+	smsc911x_reg_write(((10 << 24) | INT_CFG_IRQ_EN_), pdata, INT_CFG);
+
+	/*
+	 * intcfg |= INT_CFG_IRQ_POL_;  use this to set IRQ_POL bit
+	 * intcfg |= INT_CFG_IRQ_TYPE_;  use this to set IRQ_TYPE bit
+	 */
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	timeout = 1000;
+	while (timeout--) {
+		smp_rmb();
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	spin_lock_irq(&pdata->phy_lock);
+
+	/* Read mac address from EEPROM */
+	mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+	mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+
+	/* Generate random MAC address if eeprom values are invalid */
+	if ((mac_high16 == 0x0000FFFF) && (mac_low32 == 0xFFFFFFFF)) {
+		u8 random_mac[6];
+		random_ether_addr(random_mac);
+		mac_high16 = (random_mac[5] << 8) | random_mac[4];
+		mac_low32 = (random_mac[3] << 24) | (random_mac[2] << 16) |
+		    (random_mac[1] << 8) | random_mac[0];
+
+		smsc911x_mac_write(pdata, ADDRH, mac_high16);
+		smsc911x_mac_write(pdata, ADDRL, mac_low32);
+		SMSC_TRACE("MAC Address is set to random_ether_addr");
+	} else {
+		SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+	}
+
+	spin_unlock_irq(&pdata->phy_lock);
+
+	dev->dev_addr[0] = (u8)(mac_low32);
+	dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+	dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+	dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+	dev->dev_addr[4] = (u8)(mac_high16);
+	dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	netif_carrier_off(dev);
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	/* Preserve TX FIFO size and external PHY configuration */
+	temp &= (HW_CFG_TX_FIF_SZ_|0x00000FFF);
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	smsc911x_reg_write((smsc911x_reg_read(pdata, INT_CFG) &
+			    (~INT_CFG_IRQ_EN_)), pdata, INT_CFG);
+	netif_stop_queue(dev);
+
+	/* At this point all Rx and Tx activity is stopped */
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(pdata);
+
+	SMSC_TRACE("<--Simp911x_stop");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	dev_kfree_skb(skb);
+	freespace -= (skb->len + 32);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(pdata);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	smsc911x_tx_update_txcounters(pdata);
+	return &pdata->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (pdata->multicast_update_pending == 0) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		/* Disable Rx interrupts and schedule NAPI poll */
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RSFL_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		netif_rx_schedule(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_PHY_INT_)) {
+		smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS);
+		spin_lock(&pdata->phy_lock);
+		temp = smsc911x_phy_read(pdata, MII_INTSTS);
+		spin_unlock(&pdata->phy_lock);
+		SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp);
+		smsc911x_phy_update_linkmode(dev, 0);
+		serviced = IRQ_HANDLED;
+	}
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		data->phy_id = pdata->mii.phy_id;
+		return 0;
+	case SIOCGMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return mii_ethtool_gset(&pdata->mii, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_ethtool_sset(&pdata->mii, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strncpy(info->bus_info, dev->class_dev.dev->bus_id,
+		sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_nway_restart(&pdata->mii);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_DATA - ID_REV) / 4) + 1 + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_DATA; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	for (i = MAC_CR; i <= WUCSR; i++)
+		data[j++] = smsc911x_mac_read(pdata, i);
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_phy_read(pdata, i);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(temp, pdata, GPIO_CFG);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE("op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING("Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(e2cmd, pdata, E2P_CMD);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE("TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE("Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write((u32)data, pdata, E2P_DATA);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures */
+static int smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	/* Disable all interrupt sources until we bring the device up */
+	smsc911x_reg_write(0, pdata, INT_EN);
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	dev->poll = smsc911x_poll;
+	dev->weight = 64;
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	pdata->mii.phy_id_mask = 0x1f;
+	pdata->mii.reg_num_mask = 0x1f;
+	pdata->mii.force_media = 0;
+	pdata->mii.full_duplex = 0;
+	pdata->mii.dev = dev;
+	pdata->mii.mdio_read = smsc911x_mdio_read;
+	pdata->mii.mdio_write = smsc911x_mdio_write;
+
+	pdata->msg_enable = NETIF_MSG_LINK;
+
+	return 0;
+}
+
+static int smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	int res_size;
+	int retval;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+	SET_MODULE_OWNER(dev);
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+	memset(pdata, 0, sizeof(struct smsc911x_data));
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	if ((retval = smsc911x_init(dev)) < 0)
+		goto out_unmap_io_3;
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, SA_INTERRUPT,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	return 0;
+
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.suspend = 0,		/* TODO: Add suspend routine */
+	.resume = 0,		/* TODO: Add resume routine */
+	.driver = {
+		   .name = SMSC_CHIPNAME,
+		   },
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
+
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..e2600e4
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,417 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2006 SMSC
+ * Copyright (C) 2005-2006 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	((u32)1600)
+#define SMSC911X_EEPROM_SIZE	((u32)7)
+#define USE_DEBUG 		0
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+/* 10/100 LED link-state inversion when media is disconnected */
+#define USE_LED1_WORK_AROUND
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt,args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_TRACE(msg,args...)
+#endif				/* USE_DEBUG >= 2 */
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+	unsigned int idrev;
+	unsigned int generation;	/* used to decide which workarounds apply */
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+	struct net_device_stats stats;
+	struct mii_if_info mii;
+	unsigned int using_extphy;
+	u32 msg_enable;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+#endif
+	struct timer_list link_poll_timer;
+	int stop_link_poll;
+	int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define ENDIAN				0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
+
-- 
1.4.4.2


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2006-12-30 16:34 Steve Glendinning
@ 2007-01-04 14:42 ` Pierre TARDY
  0 siblings, 0 replies; 35+ messages in thread
From: Pierre TARDY @ 2007-01-04 14:42 UTC (permalink / raw)
  To: Steve Glendinning; +Cc: netdev, Ian Saturley, Bahadir Balban

Steve Glendinning wrote:
> Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
> ethernet controllers.  There is an existing smc911x driver in the tree;
> this is intended to replace it.
>
> This driver contains workarounds for all known hardware issues, and has
> been tested on all flavours of the chip on multiple architectures.
>
> Dustin McIntire (the author of the smc911x driver) has expressed his
> support for switching to this driver.
>
> Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
>   
This patch has just been tested on my Freescale mx31 board, with a 
smsc9117, and Linux version 2.6.16.19.
I agree to include it in mainstream.
old smc911x is restricted to one architecture (PXA) and one chip (9118).

Acked-by: Pierre Tardy <tardyp@gmail.com>




-- 
Pierre Tardy


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
@ 2007-07-16 18:54 Steve Glendinning
  2007-07-18 22:38 ` Jeff Garzik
  2007-07-29 20:53 ` Peter Korsgaard
  0 siblings, 2 replies; 35+ messages in thread
From: Steve Glendinning @ 2007-07-16 18:54 UTC (permalink / raw)
  To: netdev
  Cc: Ian Saturley, Steve Glendinning, Bahadir Balban, Dustin Mcintire,
	Bill Gatliff

Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
ethernet controllers.

There is an existing smc911x driver in the tree; this is intended to
replace it.  Dustin McIntire (the author of the smc911x driver) has
expressed his support for switching to this driver.

This driver contains workarounds for all known hardware issues, and has
been tested on all flavours of the chip on multiple architectures.

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
Signed-off-by: Bahadir Balban <Bahadir.Balban@arm.com>
Signed-off-by: Dustin Mcintire <dustin@sensoria.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 drivers/net/Kconfig    |   13 +
 drivers/net/Makefile   |    1 +
 drivers/net/smsc911x.c | 2173 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h |  385 +++++++++
 4 files changed, 2572 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/smsc911x.c
 create mode 100644 drivers/net/smsc911x.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 713ab05..048ce55 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -883,6 +883,19 @@ config NET_NETX
 	  <file:Documentation/networking/net-modules.txt>. The module
 	  will be called netx-eth.
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config DM9000
 	tristate "DM9000 support"
 	depends on ARM || BLACKFIN || MIPS
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index eb41676..44008e7 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -198,6 +198,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
 obj-$(CONFIG_PASEMI_MAC) += pasemi_mac.o
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..bcfb24e
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2173 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2007  SMSC
+ * Copyright (C) 2005 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *
+ * History:
+ *   05/05/2005 bahadir.balban@arm.com
+ *     - Transition to linux coding style
+ *     - Platform driver and module interface
+ *
+ *   17/07/2006 steve.glendinning@smsc.com
+ *     - Added support for LAN921x family
+ *     - Added workaround for multicast filters
+ *
+ *   31/07/2006 steve.glendinning@smsc.com
+ *     - Removed tasklet, using NAPI poll instead
+ *     - Multiple device support
+ *     - Large tidy-up following feedback from netdev list
+ *
+ *   03/08/2006 steve.glendinning@smsc.com
+ *     - Added ethtool support
+ *     - Convert to use generic MII interface
+ *
+ *   04/08/2006 bahadir.balban@arm.com
+ *     - Added ethtool eeprom r/w support
+ *
+ *   17/06/2007 steve.glendinning@smsc.com
+ *     - Incorporate changes from Bill Gatliff and Russell King
+ *
+ *   04/07/2007 steve.glendinning@smsc.com
+ *     - move irq configuration to platform_device
+ *     - fix link poller after interface is stopped and restarted
+ *
+ *   13/07/2007 bahadir.balban@arm.com
+ *     - set irq polarity before requesting irq
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <linux/bug.h>
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <asm/io.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_DRV_VERSION	"2007-07-13"
+
+MODULE_LICENSE("GPL");
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+
+	unsigned int idrev;
+	unsigned int generation;	/* used to decide which workarounds apply */
+
+	/* device configuration */
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+	
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+	struct net_device_stats stats;
+	struct mii_if_info mii;
+	unsigned int using_extphy;
+	u32 msg_enable;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+#endif
+	struct timer_list link_poll_timer;
+	unsigned int stop_link_poll;
+
+	unsigned int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	u32 reg_val;
+	unsigned long flags;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		   ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	local_irq_restore(flags);
+
+	return reg_val;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	local_irq_save(flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	local_irq_restore(flags);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes phy_lock is held */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 1;
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", val);
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata,
+			   MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return;
+
+	SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+			return smsc911x_mac_read(pdata, MII_DATA);
+		}
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+	return 0xFFFF;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, u16 val)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++) {
+		/* See if MII is finished yet */
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return;
+	}
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	int reg;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	reg = smsc911x_phy_read(pdata, location);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	return reg;
+}
+
+static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
+				int location, int val)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, location, val);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped because
+		 * smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		spin_lock_irq(&pdata->phy_lock);
+		for (address = 0; address <= 31; address++) {
+			pdata->mii.phy_id = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+		spin_unlock_irq(&pdata->phy_lock);
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to internal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+			pdata->using_extphy = 1;
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* called by phy_initialise and loopback test */
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int i = 100000;
+	unsigned long flags;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but using 1ms here to be safe
+	 */
+	msleep(1);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to transmit during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING("Transmit encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int i;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Initialise tx packet */
+	for (i = 0; i < 6; i++) {
+		/* Use broadcast destination address */
+		pdata->loopback_tx_pkt[i] = (char)0xFF;
+	}
+
+	for (i = 6; i < 12; i++) {
+		/* Use incrementing source address */
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (i = 14; i < MIN_PACKET_SIZE; i++) {
+		pdata->loopback_tx_pkt[i] = (char)i;
+	}
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* assumes phy_lock is held */
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+
+	if (pdata->mii.full_duplex) {
+		unsigned int phy_adv;
+		unsigned int phy_lpa;
+		phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE);
+		phy_lpa = smsc911x_phy_read(pdata, MII_LPA);
+		if (phy_adv & phy_lpa & LPA_PAUSE_CAP) {
+			/* Both ends support symmetric pause, enable
+			 * PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp |= 0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else if (((phy_adv & ADVERTISE_PAUSE_ALL) ==
+			    ADVERTISE_PAUSE_ALL) &&
+			   ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) {
+			/* We support symmetric and asym pause, the
+			 * other end only supports asym, Enable PAUSE
+			 * receive, disable PAUSE transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else {
+			/* Disable PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		}
+	} else {
+		smsc911x_mac_write(pdata, FLOW, 0);
+		temp = smsc911x_reg_read(pdata, AFC_CFG);
+		temp |= 0xF;
+		smsc911x_reg_write(temp, pdata, AFC_CFG);
+	}
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) {
+		/* duplex state has changed */
+		unsigned int mac_cr;
+
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (pdata->mii.full_duplex) {
+			SMSC_TRACE("configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE("configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	}
+#ifdef USE_LED1_WORK_AROUND
+	if (netif_carrier_ok(dev)) {
+		if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+		    (!pdata->using_extphy)) {
+			/* Restore orginal GPIO configuration */
+			pdata->gpio_setting = pdata->gpio_orig_setting;
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	} else {
+		/* Check global setting that LED1
+		 * usage is 10/100 indicator */
+		pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG);
+		if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+		    && (!pdata->using_extphy)) {
+			/* Force 10/100 LED off, after saving
+			 * orginal GPIO configuration */
+			pdata->gpio_orig_setting = pdata->gpio_setting;
+
+			pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+			pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+						| GPIO_CFG_GPIODIR0_
+						| GPIO_CFG_GPIOD0_);
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	}
+#endif				/* USE_LED1_WORK_AROUND */
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_phy_update_linkmode(dev, 0);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + 2 * HZ;
+		add_timer(&pdata->link_poll_timer);
+	} else {
+		pdata->stop_link_poll = 0;
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation by open() so
+ * interrupts are enabled */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->mii.phy_id = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->mii.phy_id = 1;
+		break;
+	}
+
+	spin_lock_irq(&pdata->phy_lock);
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+	pdata->mii.advertising = temp;
+
+	if (!pdata->using_extphy) {
+		/* using internal phy, enable PHY interrupts */
+		smsc911x_phy_read(pdata, MII_INTSTS);
+		smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_);
+	}
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_phy_update_linkmode(dev, 1);
+
+	setup_timer(&pdata->link_poll_timer, smsc911x_phy_checklink,
+		(unsigned long)dev);
+	pdata->link_poll_timer.expires = jiffies + 2 * HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata)
+{
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet
+			 * length. Since a packet length can never reach the
+			 * size of 0x8000, this bit is reserved. It is worth
+			 * noting that the "reserved bit" in the warning above
+			 * does not reference a hardware defined reserved bit
+			 * but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				pdata->stats.tx_errors++;
+			} else {
+				pdata->stats.tx_packets++;
+				pdata->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				pdata->stats.collisions += 16;
+				pdata->stats.tx_aborted_errors += 1;
+			} else {
+				pdata->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800)) {
+				pdata->stats.tx_carrier_errors += 1;
+			}
+			if (unlikely(tx_stat & 0x00000200)) {
+				pdata->stats.collisions++;
+				pdata->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		pdata->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			pdata->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			pdata->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			pdata->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes)
+{
+	unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2;
+
+	if (likely(pktwords >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		unsigned int temp;
+		while (pktwords--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct net_device *dev, int *budget)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	int npackets = 0;
+	int quota = min(dev->quota, *budget);
+
+	while (npackets < quota) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		/* break out of while loop if there are no more packets waiting */
+		if (!rxstat)
+			break;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(pdata, rxstat);
+
+		if (likely((rxstat & RX_STS_ES_) == 0)) {
+			struct sk_buff *skb;
+			skb = dev_alloc_skb(pktlength + NET_IP_ALIGN);
+			if (likely(skb)) {
+				skb->data = skb->head;
+				skb->tail = skb->head;
+				/* Align IP on 16B boundary */
+				skb_reserve(skb, NET_IP_ALIGN);
+				skb_put(skb, pktlength - 4);
+				smsc911x_rx_readfifo(pdata,
+						     (unsigned int *)skb->head,
+						     pktwords);
+				skb->dev = dev;
+				skb->protocol = eth_type_trans(skb, dev);
+				skb->ip_summed = CHECKSUM_NONE;
+				netif_receive_skb(skb);
+
+				/* Update counters */
+				pdata->stats.rx_packets++;
+				pdata->stats.rx_bytes += (pktlength - 4);
+				dev->last_rx = jiffies;
+				npackets++;
+				continue;
+			} else {
+				SMSC_WARNING("Unable to allocate sk_buff "
+					     "for rx packet, in PIO path");
+				pdata->stats.rx_dropped++;
+			}
+		}
+		/* At this point, the packet is to be read out
+		 * of the fifo and discarded */
+		smsc911x_rx_fastforward(pdata, pktlength);
+	}
+
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+
+	*budget -= npackets;
+	dev->quota -= npackets;
+
+	if (npackets < quota) {
+		unsigned int temp;
+		/* We processed all packets available.  Tell NAPI it can
+		 * stop polling then re-enable rx interrupts */
+		netif_rx_complete(dev);
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp |= INT_EN_RSFL_EN_;
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		return 0;
+	}
+
+	/* There are still packets waiting */
+	return 1;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the phy_lock held */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices 
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->phy_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->phy_lock);
+}
+
+/* Sets the device MAC address to dev_addr, called with phy_lock held */
+static void
+smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6])
+{
+	u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4];
+	u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) |
+	    (dev_addr[1] << 8) | dev_addr[0];
+
+	smsc911x_mac_write(pdata, ADDRH, mac_high16);
+	smsc911x_mac_write(pdata, ADDRL, mac_low32);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int timeout;
+	unsigned int temp;
+	unsigned int intcfg;
+
+	spin_lock_init(&pdata->phy_lock);
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+	
+	/* Set interrupt deassertion to 100uS */
+	intcfg = ((10 << 24) | INT_CFG_IRQ_EN_);
+
+	if (pdata->irq_polarity) {
+		SMSC_TRACE("irq polarity: active high");
+		intcfg |= INT_CFG_IRQ_POL_;
+	} else {
+		SMSC_TRACE("irq polarity: active low");
+	}
+	
+	if (pdata->irq_type) {
+		SMSC_TRACE("irq type: push-pull");
+		intcfg |= INT_CFG_IRQ_TYPE_;
+	} else {
+		SMSC_TRACE("irq type: open drain");
+	}
+ 
+	smsc911x_reg_write(intcfg, pdata, INT_CFG);
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	timeout = 1000;
+	while (timeout--) {
+		smp_rmb();
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	spin_lock_irq(&pdata->phy_lock);
+
+	/* Check if mac address has been specified when bringing interface up */
+	if (is_valid_ether_addr(dev->dev_addr)) {
+		smsc911x_set_mac_address(pdata, dev->dev_addr);
+		SMSC_TRACE("MAC Address is specified by configuration");	
+	} else {
+		/* Try reading mac address from device. if EEPROM is present
+		 * it will already have been set */
+		u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+		u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+		dev->dev_addr[0] = (u8)(mac_low32);
+		dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+		dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+		dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+		dev->dev_addr[4] = (u8)(mac_high16);
+		dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+
+		if (is_valid_ether_addr(dev->dev_addr)) {
+			/* eeprom values are valid  so use them */
+			SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+		} else {
+			/* eeprom values are invalid, generate random MAC */
+			random_ether_addr(dev->dev_addr);
+			smsc911x_set_mac_address(pdata, dev->dev_addr);
+			SMSC_TRACE("MAC Address is set to random_ether_addr");
+		}
+	}
+
+	spin_unlock_irq(&pdata->phy_lock);
+
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	netif_carrier_off(dev);
+	
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	temp &= HW_CFG_TX_FIF_SZ_;
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	smsc911x_reg_write((smsc911x_reg_read(pdata, INT_CFG) &
+			    (~INT_CFG_IRQ_EN_)), pdata, INT_CFG);
+	netif_stop_queue(dev);
+
+	/* At this point all Rx and Tx activity is stopped */
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(pdata);
+
+	SMSC_TRACE("Interface stopped");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	freespace -= (skb->len + 32);
+	dev_kfree_skb(skb);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(pdata);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	smsc911x_tx_update_txcounters(pdata);
+	pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	return &pdata->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (!pdata->multicast_update_pending) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		/* Disable Rx interrupts and schedule NAPI poll */
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RSFL_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		netif_rx_schedule(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_PHY_INT_)) {
+		smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS);
+		spin_lock(&pdata->phy_lock);
+		temp = smsc911x_phy_read(pdata, MII_INTSTS);
+		spin_unlock(&pdata->phy_lock);
+		SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp);
+		smsc911x_phy_update_linkmode(dev, 0);
+		serviced = IRQ_HANDLED;
+	}
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		data->phy_id = pdata->mii.phy_id;
+		return 0;
+	case SIOCGMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return mii_ethtool_gset(&pdata->mii, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_ethtool_sset(&pdata->mii, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strncpy(info->bus_info, dev->dev.bus_id, sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_nway_restart(&pdata->mii);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	for (i = MAC_CR; i <= WUCSR; i++)
+		data[j++] = smsc911x_mac_read(pdata, i);
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_phy_read(pdata, i);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(temp, pdata, GPIO_CFG);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE("op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING("Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(e2cmd, pdata, E2P_CMD);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE("TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE("Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write((u32)data, pdata, E2P_DATA);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures */
+static int smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000UL:
+		switch (pdata->idrev & 0x0000FFFFUL) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	dev->poll = smsc911x_poll;
+	dev->weight = 64;
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	pdata->mii.phy_id_mask = 0x1f;
+	pdata->mii.reg_num_mask = 0x1f;
+	pdata->mii.force_media = 0;
+	pdata->mii.full_duplex = 0;
+	pdata->mii.dev = dev;
+	pdata->mii.mdio_read = smsc911x_mdio_read;
+	pdata->mii.mdio_write = smsc911x_mdio_write;
+
+	pdata->msg_enable = NETIF_MSG_LINK;
+
+	return 0;
+}
+
+static int smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	unsigned int intcfg = 0;
+	int res_size;
+	int retval;
+
+	printk(KERN_INFO "%s: Driver version %s.\n", SMSC_CHIPNAME,
+		SMSC_DRV_VERSION);
+	
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		printk(KERN_WARNING "%s: Could not allocate resource.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+	
+	SET_MODULE_OWNER(dev);
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+	
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	/* copy config parameters across if present, otherwise pdata
+	 * defaults to zeros */
+	if (pdev->dev.platform_data) {
+		struct smsc911x_platform_config *config = pdev->dev.platform_data;
+		pdata->irq_polarity = config->irq_polarity;
+		pdata->irq_type  = config->irq_type;
+	}
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	if ((retval = smsc911x_init(dev)) < 0)
+		goto out_unmap_io_3;
+
+	/* configure irq polarity and type before connecting isr */
+	if (pdata->irq_polarity)
+		intcfg |= INT_CFG_IRQ_POL_;
+	
+	if (pdata->irq_type)
+		intcfg |= INT_CFG_IRQ_TYPE_;
+ 
+	smsc911x_reg_write(intcfg, pdata, INT_CFG);
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, IRQF_DISABLED,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	return 0;
+
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.driver = {
+		.name = SMSC_CHIPNAME,
+	},
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..8e79ff3
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,385 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2007  SMSC
+ * Copyright (C) 2005 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************/
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	(u32)1600
+#define SMSC911X_EEPROM_SIZE	(u32)7
+#define USE_DEBUG 		0
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+/* 10/100 LED link-state inversion when media is disconnected */
+#define USE_LED1_WORK_AROUND
+
+/* platform_device configuration data, should be assigned to
+ * the platform_device's dev.platform_data */
+struct smsc911x_platform_config {
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+};
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt,args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__FUNCTION__ , ## args)
+#else
+#define SMSC_TRACE(msg,args...)
+#endif				/* USE_DEBUG >= 2 */
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define ENDIAN				0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
-- 
1.5.2.2


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2007-07-16 18:54 [PATCH] SMSC LAN911x and LAN921x vendor driver Steve Glendinning
@ 2007-07-18 22:38 ` Jeff Garzik
  2007-07-20 16:22   ` Steve.Glendinning
  2007-07-29 20:53 ` Peter Korsgaard
  1 sibling, 1 reply; 35+ messages in thread
From: Jeff Garzik @ 2007-07-18 22:38 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: netdev, Ian Saturley, Bahadir Balban, Dustin Mcintire,
	Bill Gatliff

Where is the hard_start_xmit/TX-completion locking?



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2007-07-18 22:38 ` Jeff Garzik
@ 2007-07-20 16:22   ` Steve.Glendinning
  0 siblings, 0 replies; 35+ messages in thread
From: Steve.Glendinning @ 2007-07-20 16:22 UTC (permalink / raw)
  To: Jeff Garzik
  Cc: Bahadir Balban, Bill Gatliff, Dustin Mcintire, ian.saturley,
	netdev

Hi Jeff,

> Where is the hard_start_xmit/TX-completion locking?

The entire PIO Tx operation is performed within hard_start_xmit, so should 
be covered by netif_tx_lock.

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2007-07-16 18:54 [PATCH] SMSC LAN911x and LAN921x vendor driver Steve Glendinning
  2007-07-18 22:38 ` Jeff Garzik
@ 2007-07-29 20:53 ` Peter Korsgaard
  2007-07-30 18:31   ` Steve.Glendinning
  1 sibling, 1 reply; 35+ messages in thread
From: Peter Korsgaard @ 2007-07-29 20:53 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: netdev, Ian Saturley, Bahadir Balban, Dustin Mcintire,
	Bill Gatliff

>>>>> "Steve" == Steve Glendinning <steve.glendinning@smsc.com> writes:

Hi,

 Steve> Attached is a driver for SMSC's LAN911x and LAN921x families
 Steve> of embedded ethernet controllers.

 Steve> There is an existing smc911x driver in the tree; this is intended to
 Steve> replace it.  Dustin McIntire (the author of the smc911x driver) has
 Steve> expressed his support for switching to this driver.

What's the problem with Dustin's driver? It seems to work fine here
with a lan9117. Why not just add lan921x support to the existing
driver?

-- 
Bye, Peter Korsgaard


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2007-07-29 20:53 ` Peter Korsgaard
@ 2007-07-30 18:31   ` Steve.Glendinning
  2007-08-01 22:27     ` Peter Korsgaard
  0 siblings, 1 reply; 35+ messages in thread
From: Steve.Glendinning @ 2007-07-30 18:31 UTC (permalink / raw)
  To: Peter Korsgaard
  Cc: Bahadir Balban, Bill Gatliff, Dustin Mcintire, ian.saturley,
	netdev

Hi Peter,

> What's the problem with Dustin's driver? It seems to work fine here
> with a lan9117. Why not just add lan921x support to the existing
> driver?

I have heard Dustin's driver works very well on PXA, but on others it 
doesn't even compile (hence why it depends on ARCH_PXA).

Dustin's driver was based on the smc91x code, but these two ethernet 
devices share nothing other than a similar name.  smsc911x is a new, 
completely platform-independent driver with workarounds for several 
hardware issues, and it doesn't suffer from quite as much macro abuse :-)

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2007-07-30 18:31   ` Steve.Glendinning
@ 2007-08-01 22:27     ` Peter Korsgaard
  2007-08-07 23:09       ` Peter Korsgaard
  0 siblings, 1 reply; 35+ messages in thread
From: Peter Korsgaard @ 2007-08-01 22:27 UTC (permalink / raw)
  To: Steve.Glendinning
  Cc: Bahadir Balban, Bill Gatliff, Dustin Mcintire, ian.saturley,
	netdev

>>>>> "Steve" == Steve Glendinning <Steve.Glendinning@smsc.com> writes:

Hi,

 >> What's the problem with Dustin's driver? It seems to work fine here
 >> with a lan9117. Why not just add lan921x support to the existing
 >> driver?

 Steve> I have heard Dustin's driver works very well on PXA, but on
 Steve> others it doesn't even compile (hence why it depends on
 Steve> ARCH_PXA).

I'm using it on PPC.

 Steve> Dustin's driver was based on the smc91x code, but these two
 Steve> ethernet devices share nothing other than a similar name.
 Steve> smsc911x is a new, completely platform-independent driver with
 Steve> workarounds for several hardware issues, and it doesn't suffer
 Steve> from quite as much macro abuse :-)

The macros are not that bad as the hw people sligtly misconnected the
chip, so I need special access routines (enable the big endian mode,
not byteswap on normal registar access and byteswap on fifo access).

I'll give your driver a try and report back.

-- 
Bye, Peter Korsgaard


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2007-08-01 22:27     ` Peter Korsgaard
@ 2007-08-07 23:09       ` Peter Korsgaard
  0 siblings, 0 replies; 35+ messages in thread
From: Peter Korsgaard @ 2007-08-07 23:09 UTC (permalink / raw)
  To: Steve.Glendinning
  Cc: Bahadir Balban, Bill Gatliff, Dustin Mcintire, ian.saturley,
	netdev

>>>>> "Peter" == Peter Korsgaard <jacmet@sunsite.dk> writes:

Hi,

 Peter> I'll give your driver a try and report back.

Ok, the driver seems to be working (after fixing up the accessor
routines for my hw setup) and performance is comparable to Dustin's
driver.

It would be nice if the driver would enable the byte swapping support
in the hw if it detects the wrong endian - Like this:

Index: linux/drivers/net/smsc911x.c
===================================================================
--- linux.orig/drivers/net/smsc911x.c
+++ linux/drivers/net/smsc911x.c
@@ -1787,6 +1787,13 @@
 		return -ENODEV;
 	}
 
+	/* check endian */
+	if (smsc911x_reg_read(pdata, BYTE_TEST) == 0x43218765) {
+		SMSC_TRACE("Byte test looks swapped, inverting");
+		smsc911x_reg_write(~smsc911x_reg_read(pdata, ENDIAN),
+				   pdata, ENDIAN);
+	}
+
 	/* Default generation to zero (all workarounds apply) */
 	pdata->generation = 0;

-- 
Bye, Peter Korsgaard


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
       [not found] <20080515170728.GA3176@PTXN0038.genpitfi01.og.ge.com>
@ 2008-05-19 12:34 ` Steve Glendinning
  2008-05-19 16:24   ` Catalin Marinas
                     ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Steve Glendinning @ 2008-05-19 12:34 UTC (permalink / raw)
  To: Michael.Hennerich
  Cc: Enrik.Berkhan, hennerich, ian.saturley, uclinux-dist-devel,
	catalin.marinas, netdev, Steve Glendinning, Bahadir Balban,
	Dustin Mcintire, Bill Gatliff

Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
ethernet controllers.

There is an existing smc911x driver in the tree; this is intended to
replace it.  Dustin McIntire (the author of the smc911x driver) has
expressed his support for switching to this driver.

This driver contains workarounds for all known hardware issues, and has
been tested on all flavours of the chip on multiple architectures.

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
Signed-off-by: Bahadir Balban <Bahadir.Balban@arm.com>
Signed-off-by: Dustin Mcintire <dustin@sensoria.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 drivers/net/Kconfig    |   13 +
 drivers/net/Makefile   |    1 +
 drivers/net/smsc911x.c | 2285 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h |  389 ++++++++
 4 files changed, 2688 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/smsc911x.c
 create mode 100644 drivers/net/smsc911x.h

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 9f6cc8a..8f12a8e 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -967,6 +967,19 @@ config SMC911X
 	  called smc911x.  If you want to compile it as a module, say M 
 	  here and read <file:Documentation/kbuild/modules.txt>
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config NET_VENDOR_RACAL
 	bool "Racal-Interlan (Micom) NI cards"
 	depends on ISA
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index dcbfe84..7d51b18 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -215,6 +215,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_BFIN_MAC) += bfin_mac.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..08fc8ba
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2285 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *
+ * History:
+ *   05/05/2005 bahadir.balban@arm.com
+ *     - Transition to linux coding style
+ *     - Platform driver and module interface
+ *
+ *   17/07/2006 steve.glendinning@smsc.com
+ *     - Added support for LAN921x family
+ *     - Added workaround for multicast filters
+ *
+ *   31/07/2006 steve.glendinning@smsc.com
+ *     - Removed tasklet, using NAPI poll instead
+ *     - Multiple device support
+ *     - Large tidy-up following feedback from netdev list
+ *
+ *   03/08/2006 steve.glendinning@smsc.com
+ *     - Added ethtool support
+ *     - Convert to use generic MII interface
+ *
+ *   04/08/2006 bahadir.balban@arm.com
+ *     - Added ethtool eeprom r/w support
+ *
+ *   30/12/2006 steve.glendinning@smsc.com
+ *     - Fix for external PHY initialisation
+ *
+ *   17/06/2007 steve.glendinning@smsc.com
+ *     - Incorporate changes from Bill Gatliff and Russell King
+ *
+ *   04/07/2007 steve.glendinning@smsc.com
+ *     - move irq configuration to platform_device
+ *     - fix link poller after interface is stopped and restarted
+ *
+ *   13/07/2007 bahadir.balban@arm.com
+ *     - set irq polarity before requesting irq
+ *
+ *   14/04/2008 steve.glendinning@smsc.com
+ *     - update for upstream NAPI API changes
+ *     - set device mac address during probe
+ *
+ *   29/04/2008 steve.glendinning@smsc.com
+ *     - Add support for LAN9210 & LAN9211
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <linux/bug.h>
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_DRV_VERSION	"2008-05-19"
+
+MODULE_LICENSE("GPL");
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+
+	unsigned int idrev;
+
+	/* used to decide which workarounds apply */
+	unsigned int generation;
+
+	/* device configuration */
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+#if (!SMSC_CAN_USE_32BIT)
+	/* spinlock to ensure 16-bit accesses are serialised */
+	spinlock_t dev_lock;
+#endif
+
+	struct mii_if_info mii;
+	unsigned int using_extphy;
+	u32 msg_enable;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+#endif
+	struct net_device *dev;
+	struct napi_struct napi;
+	struct timer_list link_poll_timer;
+	unsigned int stop_link_poll;
+
+	unsigned int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	writesl((u_long)pdata->ioaddr + TX_DATA_FIFO, buf, wordcount);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	readsl((u_long)pdata->ioaddr + RX_DATA_FIFO, buf, wordcount);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	unsigned long flags;
+	u32 data;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	spin_lock_irqsave(&pdata->dev_lock, flags);
+	data = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	spin_unlock_irqrestore(&pdata->dev_lock, flags);
+
+	return data;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	spin_lock_irqsave(&pdata->dev_lock, flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	spin_unlock_irqrestore(&pdata->dev_lock, flags);
+}
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes phy_lock is held */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 1;
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", val);
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata,
+			   MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return;
+
+	SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++)
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return smsc911x_mac_read(pdata, MII_DATA);
+
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+	return 0xFFFF;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, u16 val)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++)
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return;
+
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	int reg;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	reg = smsc911x_phy_read(pdata, location);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	return reg;
+}
+
+static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
+				int location, int val)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, location, val);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped because
+		 * smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		spin_lock_irq(&pdata->phy_lock);
+		for (address = 0; address <= 31; address++) {
+			pdata->mii.phy_id = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+		spin_unlock_irq(&pdata->phy_lock);
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to internal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+			pdata->using_extphy = 1;
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* called by phy_initialise and loopback test */
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int i = 100000;
+	unsigned long flags;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but using 1ms here to be safe
+	 */
+	msleep(1);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to transmit during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING("Transmit encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int i;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Initialise tx packet using broadcast destination address */
+	for (i = 0; i < 6; i++)
+		pdata->loopback_tx_pkt[i] = (char)0xFF;
+
+	/* Use incrementing source address */
+	for (i = 6; i < 12; i++)
+		pdata->loopback_tx_pkt[i] = (char)i;
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (i = 14; i < MIN_PACKET_SIZE; i++)
+		pdata->loopback_tx_pkt[i] = (char)i;
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* assumes phy_lock is held */
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+
+	if (pdata->mii.full_duplex) {
+		unsigned int phy_adv;
+		unsigned int phy_lpa;
+		phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE);
+		phy_lpa = smsc911x_phy_read(pdata, MII_LPA);
+		if (phy_adv & phy_lpa & LPA_PAUSE_CAP) {
+			/* Both ends support symmetric pause, enable
+			 * PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp |= 0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else if (((phy_adv & ADVERTISE_PAUSE_ALL) ==
+			    ADVERTISE_PAUSE_ALL) &&
+			   ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) {
+			/* We support symmetric and asym pause, the
+			 * other end only supports asym, Enable PAUSE
+			 * receive, disable PAUSE transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else {
+			/* Disable PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		}
+	} else {
+		smsc911x_mac_write(pdata, FLOW, 0);
+		temp = smsc911x_reg_read(pdata, AFC_CFG);
+		temp |= 0xF;
+		smsc911x_reg_write(temp, pdata, AFC_CFG);
+	}
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) {
+		/* duplex state has changed */
+		unsigned int mac_cr;
+
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (pdata->mii.full_duplex) {
+			SMSC_TRACE("configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE("configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	}
+#ifdef USE_LED1_WORK_AROUND
+	if (netif_carrier_ok(dev)) {
+		if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+		    (!pdata->using_extphy)) {
+			/* Restore orginal GPIO configuration */
+			pdata->gpio_setting = pdata->gpio_orig_setting;
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	} else {
+		/* Check global setting that LED1
+		 * usage is 10/100 indicator */
+		pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG);
+		if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+		    && (!pdata->using_extphy)) {
+			/* Force 10/100 LED off, after saving
+			 * orginal GPIO configuration */
+			pdata->gpio_orig_setting = pdata->gpio_setting;
+
+			pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+			pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+						| GPIO_CFG_GPIODIR0_
+						| GPIO_CFG_GPIOD0_);
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	}
+#endif				/* USE_LED1_WORK_AROUND */
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_phy_update_linkmode(dev, 0);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + 2 * HZ;
+		add_timer(&pdata->link_poll_timer);
+	} else {
+		pdata->stop_link_poll = 0;
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation by open() so
+ * interrupts are enabled */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->mii.phy_id = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->mii.phy_id = 1;
+		break;
+	}
+
+	spin_lock_irq(&pdata->phy_lock);
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+	pdata->mii.advertising = temp;
+
+	if (!pdata->using_extphy) {
+		/* using internal phy, enable PHY interrupts */
+		smsc911x_phy_read(pdata, MII_INTSTS);
+		smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_);
+	}
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_phy_update_linkmode(dev, 1);
+
+	setup_timer(&pdata->link_poll_timer, smsc911x_phy_checklink,
+		(unsigned long)dev);
+	pdata->link_poll_timer.expires = jiffies + 2 * HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet
+			 * length. Since a packet length can never reach the
+			 * size of 0x8000, this bit is reserved. It is worth
+			 * noting that the "reserved bit" in the warning above
+			 * does not reference a hardware defined reserved bit
+			 * but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				dev->stats.tx_errors++;
+			} else {
+				dev->stats.tx_packets++;
+				dev->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				dev->stats.collisions += 16;
+				dev->stats.tx_aborted_errors += 1;
+			} else {
+				dev->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800))
+				dev->stats.tx_carrier_errors += 1;
+			if (unlikely(tx_stat & 0x00000200)) {
+				dev->stats.collisions++;
+				dev->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct net_device *dev, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		dev->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			dev->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			dev->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			dev->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes)
+{
+	unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2;
+
+	if (likely(pktwords >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		unsigned int temp;
+		while (pktwords--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct napi_struct *napi, int budget)
+{
+	struct smsc911x_data *pdata =
+		container_of(napi, struct smsc911x_data, napi);
+	struct net_device *dev = pdata->dev;
+	int npackets = 0;
+
+	while (likely(netif_running(dev)) && (npackets < budget)) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		struct sk_buff *skb;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		if (!rxstat) {
+			unsigned int temp;
+			/* We processed all packets available.  Tell NAPI it can
+			 * stop polling then re-enable rx interrupts */
+			smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+			netif_rx_complete(dev, napi);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RSFL_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+			break;
+		}
+
+		/* Count packet for NAPI scheduling, even if it has an error.
+		 * Error packets still require cycles to discard */
+		npackets++;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(dev, rxstat);
+
+		if (unlikely(rxstat & RX_STS_ES_)) {
+			SMSC_WARNING("Discarding packet with error bit set");
+			/* Packet has an error, discard it and continue with
+			 * the next */
+			smsc911x_rx_fastforward(pdata, pktwords);
+			dev->stats.rx_dropped++;
+			continue;
+		}
+
+		skb = netdev_alloc_skb(dev, pktlength + NET_IP_ALIGN);
+		if (unlikely(!skb)) {
+			SMSC_WARNING("Unable to allocate skb for rx packet");
+			/* Drop the packet and stop this polling iteration */
+			smsc911x_rx_fastforward(pdata, pktwords);
+			dev->stats.rx_dropped++;
+			break;
+		}
+
+		skb->data = skb->head;
+		skb->tail = skb->head;
+
+		/* Align IP on 16B boundary */
+		skb_reserve(skb, NET_IP_ALIGN);
+		skb_put(skb, pktlength - 4);
+		smsc911x_rx_readfifo(pdata, (unsigned int *)skb->head,
+				     pktwords);
+		skb->protocol = eth_type_trans(skb, dev);
+		skb->ip_summed = CHECKSUM_NONE;
+		netif_receive_skb(skb);
+
+		/* Update counters */
+		dev->stats.rx_packets++;
+		dev->stats.rx_bytes += (pktlength - 4);
+		dev->last_rx = jiffies;
+	}
+
+	/* Return total received packets */
+	return npackets;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the phy_lock held */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->phy_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->phy_lock);
+}
+
+static int smsc911x_soft_reset(struct smsc911x_data *pdata)
+{
+	unsigned int timeout;
+	unsigned int temp;
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* Sets the device MAC address to dev_addr, called with phy_lock held */
+static void
+smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6])
+{
+	u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4];
+	u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) |
+	    (dev_addr[1] << 8) | dev_addr[0];
+
+	smsc911x_mac_write(pdata, ADDRH, mac_high16);
+	smsc911x_mac_write(pdata, ADDRL, mac_low32);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int timeout;
+	unsigned int temp;
+	unsigned int intcfg;
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* The soft reset above cleared the device's MAC address,
+	 * restore it from local copy (set in probe) */
+	if (!is_valid_ether_addr(dev->dev_addr))
+		SMSC_WARNING("dev_addr is an invalid MAC address");
+
+	smsc911x_set_mac_address(pdata, dev->dev_addr);
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+
+	/* Set interrupt deassertion to 100uS */
+	intcfg = ((10 << 24) | INT_CFG_IRQ_EN_);
+
+	if (pdata->irq_polarity) {
+		SMSC_TRACE("irq polarity: active high");
+		intcfg |= INT_CFG_IRQ_POL_;
+	} else {
+		SMSC_TRACE("irq polarity: active low");
+	}
+
+	if (pdata->irq_type) {
+		SMSC_TRACE("irq type: push-pull");
+		intcfg |= INT_CFG_IRQ_TYPE_;
+	} else {
+		SMSC_TRACE("irq type: open drain");
+	}
+
+	smsc911x_reg_write(intcfg, pdata, INT_CFG);
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	timeout = 1000;
+	while (timeout--) {
+		smp_rmb();
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	netif_carrier_off(dev);
+
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	/* Preserve TX FIFO size and external PHY configuration */
+	temp &= (HW_CFG_TX_FIF_SZ_|0x00000FFF);
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	/* enable NAPI polling before enabling RX interrupts */
+	napi_enable(&pdata->napi);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int temp;
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	/* Disable all device interrupts */
+	temp = smsc911x_reg_read(pdata, INT_CFG);
+	temp &= ~INT_CFG_IRQ_EN_;
+	smsc911x_reg_write(temp, pdata, INT_CFG);
+
+	/* Stop Tx and Rx polling */
+	netif_stop_queue(dev);
+	napi_disable(&pdata->napi);
+
+	/* At this point all Rx and Tx activity is stopped */
+	dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(dev);
+
+	SMSC_TRACE("Interface stopped");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	freespace -= (skb->len + 32);
+	dev_kfree_skb(skb);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(dev);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	smsc911x_tx_update_txcounters(dev);
+	dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	return &dev->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (!pdata->multicast_update_pending) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		SMSC_TRACE("RX Error interrupt");
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		if (likely(netif_rx_schedule_prep(dev, &pdata->napi))) {
+			/* Disable Rx interrupts */
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp &= (~INT_EN_RSFL_EN_);
+			smsc911x_reg_write(temp, pdata, INT_EN);
+			/* Schedule a NAPI poll */
+			__netif_rx_schedule(dev, &pdata->napi);
+		} else {
+			SMSC_WARNING("netif_rx_schedule_prep failed");
+		}
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_PHY_INT_)) {
+		smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS);
+		spin_lock(&pdata->phy_lock);
+		temp = smsc911x_phy_read(pdata, MII_INTSTS);
+		spin_unlock(&pdata->phy_lock);
+		SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp);
+		smsc911x_phy_update_linkmode(dev, 0);
+		serviced = IRQ_HANDLED;
+	}
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		data->phy_id = pdata->mii.phy_id;
+		return 0;
+	case SIOCGMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return mii_ethtool_gset(&pdata->mii, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_ethtool_sset(&pdata->mii, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strncpy(info->bus_info, dev->dev.parent->bus_id,
+		sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_nway_restart(&pdata->mii);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	for (i = MAC_CR; i <= WUCSR; i++)
+		data[j++] = smsc911x_mac_read(pdata, i);
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_phy_read(pdata, i);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(temp, pdata, GPIO_CFG);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE("op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING("Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(e2cmd, pdata, E2P_CMD);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE("TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE("Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write((u32)data, pdata, E2P_DATA);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures */
+static int smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+#if (!SMSC_CAN_USE_32BIT)
+	spin_lock_init(&pdata->dev_lock);
+#endif
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x92100000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9210 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 4;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9210 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 4;
+			break;
+		}
+		break;
+
+	case 0x92110000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9211 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 4;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9211 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 4;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	/* Disable all interrupt sources until we bring the device up */
+	smsc911x_reg_write(0, pdata, INT_EN);
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	netif_napi_add(dev, &pdata->napi, smsc911x_poll, SMSC_NAPI_WEIGHT);
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	pdata->mii.phy_id_mask = 0x1f;
+	pdata->mii.reg_num_mask = 0x1f;
+	pdata->mii.force_media = 0;
+	pdata->mii.full_duplex = 0;
+	pdata->mii.dev = dev;
+	pdata->mii.mdio_read = smsc911x_mdio_read;
+	pdata->mii.mdio_write = smsc911x_mdio_write;
+
+	pdata->msg_enable = NETIF_MSG_LINK;
+
+	return 0;
+}
+
+static int smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	unsigned int intcfg = 0;
+	int res_size;
+	int retval;
+
+	printk(KERN_INFO "%s: Driver version %s.\n", SMSC_CHIPNAME,
+		SMSC_DRV_VERSION);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		printk(KERN_WARNING "%s: Could not allocate resource.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	/* copy config parameters across if present, otherwise pdata
+	 * defaults to zeros */
+	if (pdev->dev.platform_data) {
+		struct smsc911x_platform_config *config =
+			pdev->dev.platform_data;
+		pdata->irq_polarity = config->irq_polarity;
+		pdata->irq_type  = config->irq_type;
+	}
+
+	pdata->dev = dev;
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	retval = smsc911x_init(dev);
+	if (retval < 0)
+		goto out_unmap_io_3;
+
+	/* configure irq polarity and type before connecting isr */
+	if (pdata->irq_polarity)
+		intcfg |= INT_CFG_IRQ_POL_;
+
+	if (pdata->irq_type)
+		intcfg |= INT_CFG_IRQ_TYPE_;
+
+	smsc911x_reg_write(intcfg, pdata, INT_CFG);
+
+	/* Ensure interrupts are globally disabled before connecting ISR */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, IRQF_DISABLED,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	spin_lock_init(&pdata->phy_lock);
+
+	spin_lock_irq(&pdata->phy_lock);
+
+	/* Check if mac address has been specified when bringing interface up */
+	if (is_valid_ether_addr(dev->dev_addr)) {
+		smsc911x_set_mac_address(pdata, dev->dev_addr);
+		SMSC_TRACE("MAC Address is specified by configuration");
+	} else {
+		/* Try reading mac address from device. if EEPROM is present
+		 * it will already have been set */
+		u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+		u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+		dev->dev_addr[0] = (u8)(mac_low32);
+		dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+		dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+		dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+		dev->dev_addr[4] = (u8)(mac_high16);
+		dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+
+		if (is_valid_ether_addr(dev->dev_addr)) {
+			/* eeprom values are valid  so use them */
+			SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+		} else {
+			/* eeprom values are invalid, generate random MAC */
+			random_ether_addr(dev->dev_addr);
+			smsc911x_set_mac_address(pdata, dev->dev_addr);
+			SMSC_TRACE("MAC Address is set to random_ether_addr");
+		}
+	}
+
+	spin_unlock_irq(&pdata->phy_lock);
+
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	return 0;
+
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.driver = {
+		.name = SMSC_CHIPNAME,
+	},
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..a1e60d6
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,389 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************/
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	((u32)1600)
+#define SMSC911X_EEPROM_SIZE	((u32)7)
+#define USE_DEBUG 		0
+
+/* This is the maximum number of packets to be received every
+ * NAPI poll */
+#define SMSC_NAPI_WEIGHT	16
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+/* 10/100 LED link-state inversion when media is disconnected */
+#define USE_LED1_WORK_AROUND
+
+/* platform_device configuration data, should be assigned to
+ * the platform_device's dev.platform_data */
+struct smsc911x_platform_config {
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+};
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__func__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt, args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__func__ , ## args)
+#else
+#define SMSC_TRACE(msg, args...)
+#endif				/* USE_DEBUG >= 2 */
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define ENDIAN				0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
-- 
1.5.4.1


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-05-19 12:34 ` Steve Glendinning
@ 2008-05-19 16:24   ` Catalin Marinas
  2008-05-19 19:06     ` Steve.Glendinning
  2008-06-02 10:45   ` Steve Glendinning
  2008-06-02 18:47   ` Peter Korsgaard
  2 siblings, 1 reply; 35+ messages in thread
From: Catalin Marinas @ 2008-05-19 16:24 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: Michael.Hennerich, Enrik.Berkhan, hennerich, ian.saturley,
	uclinux-dist-devel, netdev, Bahadir Balban, Dustin Mcintire,
	Bill Gatliff

On Mon, 2008-05-19 at 13:34 +0100, Steve Glendinning wrote:
> Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
> ethernet controllers.
[...]
>  drivers/net/Kconfig    |   13 +
>  drivers/net/Makefile   |    1 +
>  drivers/net/smsc911x.c | 2285 ++++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/net/smsc911x.h |  389 ++++++++

Maybe you should patch the MAINTAINERS file as well since it's not clear
from the file header who is maintaining it.

> --- /dev/null
> +++ b/drivers/net/smsc911x.h
[...]
> +/* platform_device configuration data, should be assigned to
> + * the platform_device's dev.platform_data */
> +struct smsc911x_platform_config {
> +	unsigned int irq_polarity;
> +	unsigned int irq_type;
> +};

BTW, how could this structure be used in the platform specific code
(arch/arm/mach-*/* in my case)? This header file cannot be included
since it is not under the include/ directory and redefining the
structure for each platform is prone to errors (and too much
duplication). I can see other drivers place a header file (maybe only
containing the structure definition) in include/linux/ or include/asm-*/
(the latter if it is specific to only one architecture which I don't
think it is the case here). 

-- 
Catalin


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-05-19 16:24   ` Catalin Marinas
@ 2008-05-19 19:06     ` Steve.Glendinning
  0 siblings, 0 replies; 35+ messages in thread
From: Steve.Glendinning @ 2008-05-19 19:06 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: netdev, Ian.Saturley

Hi Catalin,

> > --- /dev/null
> > +++ b/drivers/net/smsc911x.h
> [...]
> > +/* platform_device configuration data, should be assigned to
> > + * the platform_device's dev.platform_data */
> > +struct smsc911x_platform_config {
> > +   unsigned int irq_polarity;
> > +   unsigned int irq_type;
> > +};
> 
> BTW, how could this structure be used in the platform specific code
> (arch/arm/mach-*/* in my case)? This header file cannot be included
> since it is not under the include/ directory and redefining the
> structure for each platform is prone to errors (and too much
> duplication). I can see other drivers place a header file (maybe only
> containing the structure definition) in include/linux/ or include/asm-*/
> (the latter if it is specific to only one architecture which I don't
> think it is the case here). 

Thanks Catalin, I meant to ask if this the right thing to do.

I'll keep the device register definitions in the same place and move
struct smsc911x_platform_config to a new file in include/linux/.

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-05-19 12:34 ` Steve Glendinning
  2008-05-19 16:24   ` Catalin Marinas
@ 2008-06-02 10:45   ` Steve Glendinning
  2008-06-02 15:54     ` Ben Hutchings
  2008-06-02 18:47   ` Peter Korsgaard
  2 siblings, 1 reply; 35+ messages in thread
From: Steve Glendinning @ 2008-06-02 10:45 UTC (permalink / raw)
  To: netdev
  Cc: Michael.Hennerich, Enrik.Berkhan, hennerich, ian.saturley,
	uclinux-dist-devel, catalin.marinas, Steve Glendinning,
	Bahadir Balban, Dustin Mcintire, Bill Gatliff

Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
ethernet controllers.

There is an existing smc911x driver in the tree; this is intended to
replace it.  Dustin McIntire (the author of the smc911x driver) has
expressed his support for switching to this driver.

This driver contains workarounds for all known hardware issues, and has
been tested on all flavours of the chip on multiple architectures.

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
Signed-off-by: Bahadir Balban <Bahadir.Balban@arm.com>
Signed-off-by: Dustin Mcintire <dustin@sensoria.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 MAINTAINERS              |    6 +
 drivers/net/Kconfig      |   13 +
 drivers/net/Makefile     |    1 +
 drivers/net/smsc911x.c   | 2247 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h   |  382 ++++++++
 include/linux/smsc911x.h |   31 +
 6 files changed, 2680 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/smsc911x.c
 create mode 100644 drivers/net/smsc911x.h
 create mode 100644 include/linux/smsc911x.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 0a6d2ca..8523491 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3715,6 +3715,12 @@ M:	mhoffman@lightlink.com
 L:	lm-sensors@lm-sensors.org
 S:	Maintained
 
+SMSC911x ETHERNET DRIVER
+P:	Steve Glendinning
+M:	steve.glendinning@smsc.com
+L:	netdev@vger.kernel.org
+S:	Supported
+
 SMX UIO Interface
 P:	Ben Nizette
 M:	bn@niasdigital.com
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index dd0ec9e..f01bcbb 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -967,6 +967,19 @@ config SMC911X
 	  called smc911x.  If you want to compile it as a module, say M 
 	  here and read <file:Documentation/kbuild/modules.txt>
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config NET_VENDOR_RACAL
 	bool "Racal-Interlan (Micom) NI cards"
 	depends on ISA
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index dcbfe84..7d51b18 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -215,6 +215,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_BFIN_MAC) += bfin_mac.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..9210c92
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2247 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *   LAN9210, LAN9211
+ *
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <linux/bug.h>
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/smsc911x.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_DRV_VERSION	"2008-06-02"
+
+MODULE_LICENSE("GPL");
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+
+	unsigned int idrev;
+
+	/* used to decide which workarounds apply */
+	unsigned int generation;
+
+	/* device configuration */
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 * smsc911x_phy_read(), smsc911x_phy_write()
+	 */
+	spinlock_t phy_lock;
+
+#if (!SMSC_CAN_USE_32BIT)
+	/* spinlock to ensure 16-bit accesses are serialised */
+	spinlock_t dev_lock;
+#endif
+
+	struct mii_if_info mii;
+	unsigned int using_extphy;
+	u32 msg_enable;
+#ifdef USE_LED1_WORK_AROUND
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+#endif
+	struct net_device *dev;
+	struct napi_struct napi;
+	struct timer_list link_poll_timer;
+	unsigned int stop_link_poll;
+
+	unsigned int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	writesl((u_long)pdata->ioaddr + TX_DATA_FIFO, buf, wordcount);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	readsl((u_long)pdata->ioaddr + RX_DATA_FIFO, buf, wordcount);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	unsigned long flags;
+	u32 data;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	spin_lock_irqsave(&pdata->dev_lock, flags);
+	data = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	spin_unlock_irqrestore(&pdata->dev_lock, flags);
+
+	return data;
+}
+
+static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
+				      u32 reg)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	spin_lock_irqsave(&pdata->dev_lock, flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	spin_unlock_irqrestore(&pdata->dev_lock, flags);
+}
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes phy_lock is held */
+static int smsc911x_mac_notbusy(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 1;
+	}
+	SMSC_WARNING("Timed out waiting for MAC not BUSY. "
+		     "MAC_CSR_CMD: 0x%08X", val);
+	return 0;
+}
+
+/* Fetches a MAC register value. Assumes phy_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_
+			    | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to happen */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, phy_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(val, pdata, MAC_CSR_DATA);
+
+	/* Write the actual data */
+	smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata,
+			   MAC_CSR_CMD);
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_notbusy(pdata)))
+		return;
+
+	SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Gets a phy register, phy_lock must be acquired before calling */
+static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_phy_read???");
+		return 0;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11)
+	    | ((index & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++)
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return smsc911x_mac_read(pdata, MII_DATA);
+
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+	return 0xFFFF;
+}
+
+/* Sets a phy register, phy_lock must be acquired before calling */
+static void smsc911x_phy_write(struct smsc911x_data *pdata,
+			       unsigned int index, u16 val)
+{
+	unsigned int addr;
+	int i;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	if (!spin_is_locked(&pdata->phy_lock))
+		SMSC_WARNING("phy_lock not held");
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
+		return;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = (((pdata->mii.phy_id) & 0x1F) << 11) |
+	    ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++)
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_))
+			return;
+
+	SMSC_WARNING("Timed out waiting for MII write to finish");
+}
+
+static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	int reg;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	reg = smsc911x_phy_read(pdata, location);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	return reg;
+}
+
+static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
+				int location, int val)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, location, val);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int address;
+	unsigned int hwcfg;
+	unsigned int phyid1;
+	unsigned int phyid2;
+
+	hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Attempt to switch to external phy for auto-detecting
+		 * its address. Assuming tx and rx are stopped because
+		 * smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise.
+		 */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+		/* Auto-detect PHY */
+		spin_lock_irq(&pdata->phy_lock);
+		for (address = 0; address <= 31; address++) {
+			pdata->mii.phy_id = address;
+			phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+			phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+			if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) {
+				SMSC_TRACE("Detected PHY at address = "
+					   "0x%02X = %d", address, address);
+				break;
+			}
+		}
+		spin_unlock_irq(&pdata->phy_lock);
+
+		if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) {
+			SMSC_WARNING("External PHY is not accessable, "
+				     "using internal PHY instead");
+			/* Revert back to internal phy settings. */
+
+			/* Disable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to stop */
+
+			/* Switch to internal phy */
+			hwcfg &= (~HW_CFG_EXT_PHY_EN_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+
+			/* Enable phy clocks to the MAC */
+			hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+			hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_;
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			udelay(10);	/* Enough time for clocks to restart */
+
+			hwcfg &= (~HW_CFG_SMI_SEL_);
+			smsc911x_reg_write(hwcfg, pdata, HW_CFG);
+			/* Use internal phy */
+			return -ENODEV;
+		} else {
+			SMSC_TRACE("Successfully switched to external PHY");
+			pdata->using_extphy = 1;
+		}
+	} else {
+		SMSC_WARNING("No external PHY detected.");
+		SMSC_WARNING("Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* called by phy_initialise and loopback test */
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+	unsigned int i = 100000;
+	unsigned long flags;
+
+	SMSC_TRACE("Performing PHY BCR Reset");
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
+	do {
+		udelay(10);
+		temp = smsc911x_phy_read(pdata, MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+	/* Extra delay required because the phy may not be completed with
+	 * its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	 * enough delay but using 1ms here to be safe
+	 */
+	msleep(1);
+
+	return 1;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	u32 bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (((unsigned int)pdata->loopback_tx_pkt)
+			   & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO);
+		smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO);
+
+		bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC;
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to transmit during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING("Transmit encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING("Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING("Receive encountered errors during "
+				     "loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (u32)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3;
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING("Unexpected packet size during "
+				     "loop back test, size=%d, "
+				     "will retry", pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE("Successfully verified "
+					   "loopback packet");
+				return 1;
+			} else {
+				SMSC_WARNING("Data miss match during "
+					     "loop back test, will retry.");
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
+{
+	int result = 0;
+	unsigned int i;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Initialise tx packet using broadcast destination address */
+	for (i = 0; i < 6; i++)
+		pdata->loopback_tx_pkt[i] = (char)0xFF;
+
+	/* Use incrementing source address */
+	for (i = 6; i < 12; i++)
+		pdata->loopback_tx_pkt[i] = (char)i;
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+	for (i = 14; i < MIN_PACKET_SIZE; i++)
+		pdata->loopback_tx_pkt[i] = (char)i;
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(val, pdata, HW_CFG);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
+			    & 0x03) << 8, pdata, RX_CFG);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);
+
+		/* Enable MAC tx/rx, FD */
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata)) {
+			result = 1;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_phy_write(pdata, MII_BMCR, 0);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+
+	smsc911x_reg_write(0, pdata, TX_CFG);
+	smsc911x_reg_write(0, pdata, RX_CFG);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+/* assumes phy_lock is held */
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	unsigned int temp;
+
+	if (pdata->mii.full_duplex) {
+		unsigned int phy_adv;
+		unsigned int phy_lpa;
+		phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE);
+		phy_lpa = smsc911x_phy_read(pdata, MII_LPA);
+		if (phy_adv & phy_lpa & LPA_PAUSE_CAP) {
+			/* Both ends support symmetric pause, enable
+			 * PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp |= 0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else if (((phy_adv & ADVERTISE_PAUSE_ALL) ==
+			    ADVERTISE_PAUSE_ALL) &&
+			   ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) {
+			/* We support symmetric and asym pause, the
+			 * other end only supports asym, Enable PAUSE
+			 * receive, disable PAUSE transmit */
+			smsc911x_mac_write(pdata, FLOW, 0xFFFF0002);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		} else {
+			/* Disable PAUSE receive and transmit */
+			smsc911x_mac_write(pdata, FLOW, 0);
+			temp = smsc911x_reg_read(pdata, AFC_CFG);
+			temp &= ~0xF;
+			smsc911x_reg_write(temp, pdata, AFC_CFG);
+		}
+	} else {
+		smsc911x_mac_write(pdata, FLOW, 0);
+		temp = smsc911x_reg_read(pdata, AFC_CFG);
+		temp |= 0xF;
+		smsc911x_reg_write(temp, pdata, AFC_CFG);
+	}
+}
+
+/* Update link mode if any thing has changed */
+static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) {
+		/* duplex state has changed */
+		unsigned int mac_cr;
+
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (pdata->mii.full_duplex) {
+			SMSC_TRACE("configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE("configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+	}
+#ifdef USE_LED1_WORK_AROUND
+	if (netif_carrier_ok(dev)) {
+		if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+		    (!pdata->using_extphy)) {
+			/* Restore orginal GPIO configuration */
+			pdata->gpio_setting = pdata->gpio_orig_setting;
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	} else {
+		/* Check global setting that LED1
+		 * usage is 10/100 indicator */
+		pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG);
+		if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+		    && (!pdata->using_extphy)) {
+			/* Force 10/100 LED off, after saving
+			 * orginal GPIO configuration */
+			pdata->gpio_orig_setting = pdata->gpio_setting;
+
+			pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+			pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+						| GPIO_CFG_GPIODIR0_
+						| GPIO_CFG_GPIOD0_);
+			smsc911x_reg_write(pdata->gpio_setting, pdata,
+					   GPIO_CFG);
+		}
+	}
+#endif				/* USE_LED1_WORK_AROUND */
+}
+
+/* Entry point for the link poller */
+static void smsc911x_phy_checklink(unsigned long ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_phy_update_linkmode(dev, 0);
+
+	if (!(pdata->stop_link_poll)) {
+		pdata->link_poll_timer.expires = jiffies + 2 * HZ;
+		add_timer(&pdata->link_poll_timer);
+	} else {
+		pdata->stop_link_poll = 0;
+	}
+}
+
+/* Initialises the PHY layer.  Called at initialisation by open() so
+ * interrupts are enabled */
+static int smsc911x_phy_initialise(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int phyid1 = 0;
+	unsigned int phyid2 = 0;
+	unsigned int temp;
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE("External PHY is not detected, using "
+				   "internal PHY instead");
+			pdata->mii.phy_id = 1;
+		}
+		break;
+	default:
+		SMSC_TRACE("External PHY is not supported, using internal PHY "
+			   "instead");
+		pdata->mii.phy_id = 1;
+		break;
+	}
+
+	spin_lock_irq(&pdata->phy_lock);
+	phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1);
+	phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) {
+		SMSC_WARNING("Internal PHY not detected!");
+		return 0;
+	}
+
+	/* Reset the phy */
+	if (!smsc911x_phy_reset(pdata)) {
+		SMSC_WARNING("PHY reset failed to complete.");
+		return 0;
+	}
+#ifdef USE_PHY_WORK_AROUND
+	if (!smsc911x_phy_loopbacktest(pdata)) {
+		SMSC_WARNING("Failed Loop Back Test");
+		return 0;
+	} else {
+		SMSC_TRACE("Passed Loop Back Test");
+	}
+#endif				/* USE_PHY_WORK_AROUND */
+
+	/* Advertise all speeds and pause capabilities */
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_phy_read(pdata, MII_ADVERTISE);
+	temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+	smsc911x_phy_write(pdata, MII_ADVERTISE, temp);
+	pdata->mii.advertising = temp;
+
+	if (!pdata->using_extphy) {
+		/* using internal phy, enable PHY interrupts */
+		smsc911x_phy_read(pdata, MII_INTSTS);
+		smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_);
+	}
+
+	/* begin to establish link */
+	smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_phy_update_linkmode(dev, 1);
+
+	setup_timer(&pdata->link_poll_timer, smsc911x_phy_checklink,
+		(unsigned long)dev);
+	pdata->link_poll_timer.expires = jiffies + 2 * HZ;
+	add_timer(&pdata->link_poll_timer);
+
+	SMSC_TRACE("phy initialised succesfully");
+	return 1;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF)
+			       & TX_FIFO_INF_TSUSED_) >> 16;
+	return result;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet
+			 * length. Since a packet length can never reach the
+			 * size of 0x8000, this bit is reserved. It is worth
+			 * noting that the "reserved bit" in the warning above
+			 * does not reference a hardware defined reserved bit
+			 * but rather a driver defined one.
+			 */
+			SMSC_WARNING("Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				dev->stats.tx_errors++;
+			} else {
+				dev->stats.tx_packets++;
+				dev->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				dev->stats.collisions += 16;
+				dev->stats.tx_aborted_errors += 1;
+			} else {
+				dev->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800))
+				dev->stats.tx_carrier_errors += 1;
+			if (unlikely(tx_stat & 0x00000200)) {
+				dev->stats.collisions++;
+				dev->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct net_device *dev, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		dev->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			dev->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			dev->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			dev->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes)
+{
+	unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2;
+
+	if (likely(pktwords >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING("Timed out waiting for RX FFWD "
+				     "to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		unsigned int temp;
+		while (pktwords--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct napi_struct *napi, int budget)
+{
+	struct smsc911x_data *pdata =
+		container_of(napi, struct smsc911x_data, napi);
+	struct net_device *dev = pdata->dev;
+	int npackets = 0;
+
+	while (likely(netif_running(dev)) && (npackets < budget)) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		struct sk_buff *skb;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		if (!rxstat) {
+			unsigned int temp;
+			/* We processed all packets available.  Tell NAPI it can
+			 * stop polling then re-enable rx interrupts */
+			smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS);
+			netif_rx_complete(dev, napi);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RSFL_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+			break;
+		}
+
+		/* Count packet for NAPI scheduling, even if it has an error.
+		 * Error packets still require cycles to discard */
+		npackets++;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(dev, rxstat);
+
+		if (unlikely(rxstat & RX_STS_ES_)) {
+			SMSC_WARNING("Discarding packet with error bit set");
+			/* Packet has an error, discard it and continue with
+			 * the next */
+			smsc911x_rx_fastforward(pdata, pktwords);
+			dev->stats.rx_dropped++;
+			continue;
+		}
+
+		skb = netdev_alloc_skb(dev, pktlength + NET_IP_ALIGN);
+		if (unlikely(!skb)) {
+			SMSC_WARNING("Unable to allocate skb for rx packet");
+			/* Drop the packet and stop this polling iteration */
+			smsc911x_rx_fastforward(pdata, pktwords);
+			dev->stats.rx_dropped++;
+			break;
+		}
+
+		skb->data = skb->head;
+		skb->tail = skb->head;
+
+		/* Align IP on 16B boundary */
+		skb_reserve(skb, NET_IP_ALIGN);
+		skb_put(skb, pktlength - 4);
+		smsc911x_rx_readfifo(pdata, (unsigned int *)skb->head,
+				     pktwords);
+		skb->protocol = eth_type_trans(skb, dev);
+		skb->ip_summed = CHECKSUM_NONE;
+		netif_receive_skb(skb);
+
+		/* Update counters */
+		dev->stats.rx_packets++;
+		dev->stats.rx_bytes += (pktlength - 4);
+		dev->last_rx = jiffies;
+	}
+
+	/* Return total received packets */
+	return npackets;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	unsigned int crc;
+	unsigned int result;
+
+	crc = ether_crc(ETH_ALEN, addr);
+	result = (crc >> 26) & 0x3f;
+
+	return result;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the phy_lock held */
+	unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr,
+		   pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->phy_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING("Rx not stopped\n");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->phy_lock);
+}
+
+static int smsc911x_soft_reset(struct smsc911x_data *pdata)
+{
+	unsigned int timeout;
+	unsigned int temp;
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING("Failed to complete reset");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* Sets the device MAC address to dev_addr, called with phy_lock held */
+static void
+smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6])
+{
+	u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4];
+	u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) |
+	    (dev_addr[1] << 8) | dev_addr[0];
+
+	smsc911x_mac_write(pdata, ADDRH, mac_high16);
+	smsc911x_mac_write(pdata, ADDRL, mac_low32);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int timeout;
+	unsigned int temp;
+	unsigned int intcfg;
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	smsc911x_reg_write(0x00050000, pdata, HW_CFG);
+	smsc911x_reg_write(0x006E3740, pdata, AFC_CFG);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0)) {
+		SMSC_WARNING("Timed out waiting for EEPROM "
+			     "busy bit to clear");
+	}
+#if USE_DEBUG >= 1
+	smsc911x_reg_write(0x00670700, pdata, GPIO_CFG);
+#else
+	smsc911x_reg_write(0x70070000, pdata, GPIO_CFG);
+#endif
+
+	/* The soft reset above cleared the device's MAC address,
+	 * restore it from local copy (set in probe) */
+	if (!is_valid_ether_addr(dev->dev_addr))
+		SMSC_WARNING("dev_addr is an invalid MAC address");
+
+	smsc911x_set_mac_address(pdata, dev->dev_addr);
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+
+	/* Set interrupt deassertion to 100uS */
+	intcfg = ((10 << 24) | INT_CFG_IRQ_EN_);
+
+	if (pdata->irq_polarity) {
+		SMSC_TRACE("irq polarity: active high");
+		intcfg |= INT_CFG_IRQ_POL_;
+	} else {
+		SMSC_TRACE("irq polarity: active low");
+	}
+
+	if (pdata->irq_type) {
+		SMSC_TRACE("irq type: push-pull");
+		intcfg |= INT_CFG_IRQ_TYPE_;
+	} else {
+		SMSC_TRACE("irq type: open drain");
+	}
+
+	smsc911x_reg_write(intcfg, pdata, INT_CFG);
+
+	SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq);
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	timeout = 1000;
+	while (timeout--) {
+		smp_rmb();
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
+		       dev->name, dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
+
+	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);
+
+	netif_carrier_off(dev);
+
+	if (!smsc911x_phy_initialise(dev)) {
+		SMSC_WARNING("Failed to initialize PHY");
+		return -ENODEV;
+	}
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	/* Preserve TX FIFO size and external PHY configuration */
+	temp &= (HW_CFG_TX_FIF_SZ_|0x00000FFF);
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(temp, pdata, HW_CFG);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(temp, pdata, FIFO_INT);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write((2 << 8), pdata, RX_CFG);
+
+	/* enable NAPI polling before enabling RX interrupts */
+	napi_enable(&pdata->napi);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_);
+	smsc911x_reg_write(temp, pdata, INT_EN);
+
+	spin_lock_irq(&pdata->phy_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->phy_lock);
+
+	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int temp;
+
+	pdata->stop_link_poll = 1;
+	del_timer_sync(&pdata->link_poll_timer);
+
+	/* Disable all device interrupts */
+	temp = smsc911x_reg_read(pdata, INT_CFG);
+	temp &= ~INT_CFG_IRQ_EN_;
+	smsc911x_reg_write(temp, pdata, INT_CFG);
+
+	/* Stop Tx and Rx polling */
+	netif_stop_queue(dev);
+	napi_disable(&pdata->napi);
+
+	/* At this point all Rx and Tx activity is stopped */
+	dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(dev);
+
+	SMSC_TRACE("Interface stopped");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	u32 bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING("Tx data fifo low, space available: %d",
+			     freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16);
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO);
+	smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO);
+
+	bufp = ((u32)skb->data) & 0xFFFFFFFC;
+	wrsz = (u32)skb->len + 3;
+	wrsz += ((u32)skb->data) & 0x3;
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	freespace -= (skb->len + 32);
+	dev_kfree_skb(skb);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(dev);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	smsc911x_tx_update_txcounters(dev);
+	dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	return &dev->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING("dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING("mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (!pdata->multicast_update_pending) {
+			unsigned int temp;
+			SMSC_TRACE("scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(temp, pdata, INT_EN);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int intsts;
+	unsigned int inten;
+	unsigned int temp;
+	int serviced = IRQ_NONE;
+
+	intsts = smsc911x_reg_read(pdata, INT_STS);
+	inten = smsc911x_reg_read(pdata, INT_EN);
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE("RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(temp, pdata, INT_EN);
+		smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(temp, pdata, FIFO_INT);
+		smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		SMSC_TRACE("RX Error interrupt");
+		smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		if (likely(netif_rx_schedule_prep(dev, &pdata->napi))) {
+			/* Disable Rx interrupts */
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp &= (~INT_EN_RSFL_EN_);
+			smsc911x_reg_write(temp, pdata, INT_EN);
+			/* Schedule a NAPI poll */
+			__netif_rx_schedule(dev, &pdata->napi);
+		} else {
+			SMSC_WARNING("netif_rx_schedule_prep failed");
+		}
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_PHY_INT_)) {
+		smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS);
+		spin_lock(&pdata->phy_lock);
+		temp = smsc911x_phy_read(pdata, MII_INTSTS);
+		spin_unlock(&pdata->phy_lock);
+		SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp);
+		smsc911x_phy_update_linkmode(dev, 0);
+		serviced = IRQ_HANDLED;
+	}
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct mii_ioctl_data *data = if_mii(ifr);
+	unsigned long flags;
+
+	SMSC_TRACE("ioctl cmd 0x%x", cmd);
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		data->phy_id = pdata->mii.phy_id;
+		return 0;
+	case SIOCGMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		data->val_out = smsc911x_phy_read(pdata, data->reg_num);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	case SIOCSMIIREG:
+		spin_lock_irqsave(&pdata->phy_lock, flags);
+		smsc911x_phy_write(pdata, data->reg_num, data->val_in);
+		spin_unlock_irqrestore(&pdata->phy_lock, flags);
+		return 0;
+	}
+
+	SMSC_TRACE("unsupported ioctl cmd");
+	return -1;
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return mii_ethtool_gset(&pdata->mii, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_ethtool_sset(&pdata->mii, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strncpy(info->bus_info, dev->dev.parent->bus_id,
+		sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return mii_nway_restart(&pdata->mii);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	spin_lock_irqsave(&pdata->phy_lock, flags);
+	for (i = MAC_CR; i <= WUCSR; i++)
+		data[j++] = smsc911x_mac_read(pdata, i);
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_phy_read(pdata, i);
+	spin_unlock_irqrestore(&pdata->phy_lock, flags);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(temp, pdata, GPIO_CFG);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE("op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING("Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(e2cmd, pdata, E2P_CMD);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE("TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE("Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE("address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write((u32)data, pdata, E2P_DATA);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures, only called from probe */
+static int __devinit smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	SMSC_TRACE("Driver Parameters:");
+	SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr);
+	SMSC_TRACE("IRQ: %d", dev->irq);
+	SMSC_TRACE("PHY will be autodetected.");
+
+#if (!SMSC_CAN_USE_32BIT)
+	spin_lock_init(&pdata->dev_lock);
+#endif
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING("pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) {
+		SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, "
+			     "idrev: 0x%08X", pdata->idrev);
+		SMSC_TRACE("This may mean the chip is set for 32 bit while "
+			   "the bus is reading as 16 bit");
+		return -ENODEV;
+	}
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9118 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01170000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X",
+				   pdata->idrev);
+			pdata->generation = 0;
+			break;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9117 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01160000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9116 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x01150000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+				     pdata->idrev);
+			return -ENODEV;
+		case 1UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A0 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 1;
+			break;
+		case 2UL:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 2;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9115 Concord A1 identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 2;
+			break;
+		}
+		break;
+
+	case 0x118A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9218 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x117A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9217 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x116A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9216 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x115A0000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 3;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9215 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 3;
+			break;
+		}
+		break;
+
+	case 0x92100000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9210 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 4;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9210 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 4;
+			break;
+		}
+		break;
+
+	case 0x92110000:
+		switch (pdata->idrev & 0x0000FFFF) {
+		case 0UL:
+			SMSC_TRACE
+			    ("LAN9211 Boylston identified, idrev: 0x%08X",
+			     pdata->idrev);
+			pdata->generation = 4;
+			break;
+		default:
+			SMSC_TRACE
+			    ("LAN9211 Boylston identified (NEW), "
+			     "idrev: 0x%08X", pdata->idrev);
+			pdata->generation = 4;
+			break;
+		}
+		break;
+
+	default:
+		SMSC_WARNING("LAN911x not identified, idrev: 0x%08X",
+			     pdata->idrev);
+		return -ENODEV;
+	}
+
+	if (pdata->generation == 0)
+		SMSC_WARNING("This driver is not intended "
+			     "for this chip revision");
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	/* Disable all interrupt sources until we bring the device up */
+	smsc911x_reg_write(0, pdata, INT_EN);
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	netif_napi_add(dev, &pdata->napi, smsc911x_poll, SMSC_NAPI_WEIGHT);
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	pdata->mii.phy_id_mask = 0x1f;
+	pdata->mii.reg_num_mask = 0x1f;
+	pdata->mii.force_media = 0;
+	pdata->mii.full_duplex = 0;
+	pdata->mii.dev = dev;
+	pdata->mii.mdio_read = smsc911x_mdio_read;
+	pdata->mii.mdio_write = smsc911x_mdio_write;
+
+	pdata->msg_enable = NETIF_MSG_LINK;
+
+	return 0;
+}
+
+static int __devexit smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+
+	SMSC_TRACE("Stopping driver.");
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	unsigned int intcfg = 0;
+	int res_size;
+	int retval;
+
+	printk(KERN_INFO "%s: Driver version %s.\n", SMSC_CHIPNAME,
+		SMSC_DRV_VERSION);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		printk(KERN_WARNING "%s: Could not allocate resource.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		printk(KERN_WARNING "%s: Could not allocate device.\n",
+		       SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	/* copy config parameters across if present, otherwise pdata
+	 * defaults to zeros */
+	if (pdev->dev.platform_data) {
+		struct smsc911x_platform_config *config =
+			pdev->dev.platform_data;
+		pdata->irq_polarity = config->irq_polarity;
+		pdata->irq_type  = config->irq_type;
+	}
+
+	pdata->dev = dev;
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING("Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	retval = smsc911x_init(dev);
+	if (retval < 0)
+		goto out_unmap_io_3;
+
+	/* configure irq polarity and type before connecting isr */
+	if (pdata->irq_polarity)
+		intcfg |= INT_CFG_IRQ_POL_;
+
+	if (pdata->irq_type)
+		intcfg |= INT_CFG_IRQ_TYPE_;
+
+	smsc911x_reg_write(intcfg, pdata, INT_CFG);
+
+	/* Ensure interrupts are globally disabled before connecting ISR */
+	smsc911x_reg_write(0, pdata, INT_EN);
+	smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS);
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, IRQF_DISABLED,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING("Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING("Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE("Network interface: \"%s\"", dev->name);
+	}
+
+	spin_lock_init(&pdata->phy_lock);
+
+	spin_lock_irq(&pdata->phy_lock);
+
+	/* Check if mac address has been specified when bringing interface up */
+	if (is_valid_ether_addr(dev->dev_addr)) {
+		smsc911x_set_mac_address(pdata, dev->dev_addr);
+		SMSC_TRACE("MAC Address is specified by configuration");
+	} else {
+		/* Try reading mac address from device. if EEPROM is present
+		 * it will already have been set */
+		u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+		u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+		dev->dev_addr[0] = (u8)(mac_low32);
+		dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+		dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+		dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+		dev->dev_addr[4] = (u8)(mac_high16);
+		dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+
+		if (is_valid_ether_addr(dev->dev_addr)) {
+			/* eeprom values are valid  so use them */
+			SMSC_TRACE("Mac Address is read from LAN911x EEPROM");
+		} else {
+			/* eeprom values are invalid, generate random MAC */
+			random_ether_addr(dev->dev_addr);
+			smsc911x_set_mac_address(pdata, dev->dev_addr);
+			SMSC_TRACE("MAC Address is set to random_ether_addr");
+		}
+	}
+
+	spin_unlock_irq(&pdata->phy_lock);
+
+	printk(KERN_INFO
+	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+	return 0;
+
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.driver = {
+		.name = SMSC_CHIPNAME,
+	},
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..b48aa7e
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,382 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************/
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	((u32)1600)
+#define SMSC911X_EEPROM_SIZE	((u32)7)
+#define USE_DEBUG 		0
+
+/* This is the maximum number of packets to be received every
+ * NAPI poll */
+#define SMSC_NAPI_WEIGHT	16
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+/* 10/100 LED link-state inversion when media is disconnected */
+#define USE_LED1_WORK_AROUND
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(fmt, args...) \
+		printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \
+			__func__ , ## args)
+#else
+#define SMSC_WARNING(msg, args...)
+#endif				/* USE_DEBUG >= 1 */
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(fmt, args...) \
+		printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \
+			__func__ , ## args)
+#else
+#define SMSC_TRACE(msg, args...)
+#endif				/* USE_DEBUG >= 2 */
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define ENDIAN				0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
diff --git a/include/linux/smsc911x.h b/include/linux/smsc911x.h
new file mode 100644
index 0000000..9fea5c4
--- /dev/null
+++ b/include/linux/smsc911x.h
@@ -0,0 +1,31 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************/
+#ifndef __LINUX_SMSC911X_H__
+#define __LINUX_SMSC911X_H__
+
+/* platform_device configuration data, should be assigned to
+ * the platform_device's dev.platform_data */
+struct smsc911x_platform_config {
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+};
+
+#endif /* __LINUX_SMSC911X_H__ */
-- 
1.5.4.1


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-06-02 10:45   ` Steve Glendinning
@ 2008-06-02 15:54     ` Ben Hutchings
  2008-06-02 16:09       ` Bill Gatliff
                         ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Ben Hutchings @ 2008-06-02 15:54 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: netdev, Michael.Hennerich, Enrik.Berkhan, hennerich, ian.saturley,
	uclinux-dist-devel, catalin.marinas, Bahadir Balban,
	Dustin Mcintire, Bill Gatliff

Steve Glendinning wrote:
[...]
> +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata,
> +				      u32 reg)

It's more common to make the value the last parameter to a write-register
function.

[...]
> +/* Fetches a MAC register value. Assumes phy_lock is acquired */
> +static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
> +{
> +	unsigned int temp;
> +
> +#ifdef CONFIG_DEBUG_SPINLOCK
> +	if (!spin_is_locked(&pdata->phy_lock))
> +		SMSC_WARNING("phy_lock not held");
> +#endif				/* CONFIG_DEBUG_SPINLOCK */

This is replicated in several functions; why not make it a macro, and use
the standard warning macro:

#ifdef CONFIG_DEBUG_SPINLOCK
#define ASSERT_PHY_LOCK(pdata) WARN_ON(!spin_is_locked(&pdata->phy_lock))
#else
#define ASSERT_PHY_LOCK(pdata) do {} while (0)
#endif

(Also, why is it called phy_lock if it's also used by the MAC access
functions?)

[...]
> +/* Set a mac register, phy_lock must be acquired before calling */
> +static void smsc911x_mac_write(struct smsc911x_data *pdata,
> +			       unsigned int offset, u32 val)
> +{
> +	unsigned int temp;
> +
> +#ifdef CONFIG_DEBUG_SPINLOCK
> +	if (!spin_is_locked(&pdata->phy_lock))
> +		SMSC_WARNING("phy_lock not held");
> +#endif				/* CONFIG_DEBUG_SPINLOCK */
> +
> +	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
> +	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
> +		SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
> +		return;
> +	}

Shouldn't this return an error code?

[...]
> +/* Sets a phy register, phy_lock must be acquired before calling */
> +static void smsc911x_phy_write(struct smsc911x_data *pdata,
> +			       unsigned int index, u16 val)
> +{
> +	unsigned int addr;
> +	int i;
> +
> +#ifdef CONFIG_DEBUG_SPINLOCK
> +	if (!spin_is_locked(&pdata->phy_lock))
> +		SMSC_WARNING("phy_lock not held");
> +#endif				/* CONFIG_DEBUG_SPINLOCK */
> +
> +	/* Confirm MII not busy */
> +	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
> +		SMSC_WARNING("MII is busy in smsc911x_write_phy???");
> +		return;
> +	}

Similarly for this function.

[...]
> +/* called by phy_initialise and loopback test */
> +static int smsc911x_phy_reset(struct smsc911x_data *pdata)
> +{
> +	unsigned int temp;
> +	unsigned int i = 100000;
> +	unsigned long flags;
> +
> +	SMSC_TRACE("Performing PHY BCR Reset");
> +	spin_lock_irqsave(&pdata->phy_lock, flags);
> +	smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET);
> +	do {
> +		udelay(10);
> +		temp = smsc911x_phy_read(pdata, MII_BMCR);
> +	} while ((i--) && (temp & BMCR_RESET));
> +	spin_unlock_irqrestore(&pdata->phy_lock, flags);

I think this was already mentioned, but that's a very long time to busy-
wait.  Maybe you could find a way to block PHY access that doesn't require
holding phy_lock; then you could sleep while waiting.

[...]
> +static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
> +{
> +	int result = 0;
> +	unsigned int i;
> +	unsigned int val;
> +	unsigned long flags;
> +
> +	/* Initialise tx packet using broadcast destination address */
> +	for (i = 0; i < 6; i++)
> +		pdata->loopback_tx_pkt[i] = (char)0xFF;

The cast to char is just noise.

[...]
> +	/* Set length type field */
> +	pdata->loopback_tx_pkt[12] = 0x00;
> +	pdata->loopback_tx_pkt[13] = 0x00;
> +	for (i = 14; i < MIN_PACKET_SIZE; i++)
> +		pdata->loopback_tx_pkt[i] = (char)i;

The comment applies to the following two lines only, so you could do with a
blank line after them.

> +	val = smsc911x_reg_read(pdata, HW_CFG);
> +	val &= HW_CFG_TX_FIF_SZ_;
> +	val |= HW_CFG_SF_;
> +	smsc911x_reg_write(val, pdata, HW_CFG);
> +
> +	smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG);
> +	smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt)
> +			    & 0x03) << 8, pdata, RX_CFG);
> +
> +	for (i = 0; i < 10; i++) {
> +		/* Set PHY to 10/FD, no ANEG, and loopback mode */
> +		spin_lock_irqsave(&pdata->phy_lock, flags);
> +		smsc911x_phy_write(pdata, MII_BMCR, 0x4100);

You could write BMCR_LOOPBACK | BMCR_FULLDPLX instead of 0x4100; then the
comment is unnecessary.

[...]
> +/* Update link mode if any thing has changed */
> +static void smsc911x_phy_update_linkmode(struct net_device *dev, int init)
[...]
> +#ifdef USE_LED1_WORK_AROUND

Shouldn't this workaround be controlled by platform data or a module
parameter rather than a compile-time option which isn't in Kconfig?  I
notice this macro is defined by default, so maybe it shouldn't be
conditional at all.

[...]
> +static int smsc911x_soft_reset(struct smsc911x_data *pdata)
> +{
> +	unsigned int timeout;
> +	unsigned int temp;
> +
> +	/* Reset the LAN911x */
> +	smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
> +	timeout = 10;
> +	do {
> +		udelay(10);
> +		temp = smsc911x_reg_read(pdata, HW_CFG);
> +	} while ((--timeout) && (temp & HW_CFG_SRST_));
> +
> +	if (unlikely(temp & HW_CFG_SRST_)) {
> +		SMSC_WARNING("Failed to complete reset");
> +		return -ENODEV;

I think this should be -EIO unless this is only called during probe.

[...]
> +static int smsc911x_open(struct net_device *dev)
[...]
> +	timeout = 1000;
> +	while (timeout--) {
> +		smp_rmb();

I think you're trying to ensure that software_irq_signal is re-read each
time round the loop.  For that you should use barrier(), not smp_rmb().
However, msleep() acts as a compiler barrier already.  So just remove the
smp_rmb().

> +		if (pdata->software_irq_signal)
> +			break;
> +		msleep(1);
> +	}
> +
> +	if (!pdata->software_irq_signal) {
> +		printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n",
> +		       dev->name, dev->irq);
> +		return -ENODEV;
> +	}
> +	SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq);
> +
> +	printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n",
> +	       dev->name, (unsigned long)pdata->ioaddr, dev->irq);

Should use dev_info().

[...]
> +static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
> +					struct ethtool_drvinfo *info)
> +{
> +	strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
> +	strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
> +	strncpy(info->bus_info, dev->dev.parent->bus_id,
> +		sizeof(info->bus_info));
> +}

Use strlcpy(), not strncpy().

[...]
> +static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
> +{
> +	struct smsc911x_data *pdata = netdev_priv(dev);
> +	pdata->msg_enable = level;
> +}

It would be nice if the logging macros actually tested msg_enable too. ;-)

[...]
> +static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
> +				       struct ethtool_eeprom *eeprom, u8 *data)
> +{
> +	struct smsc911x_data *pdata = netdev_priv(dev);
> +	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
> +	int len;
> +	int i;
> +
> +	smsc911x_eeprom_enable_access(pdata);
> +
> +	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
> +	for (i = 0; i < len; i++) {
> +		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
> +		if (ret < 0) {
> +			eeprom->len = 0;
> +			return ret;
> +		}
> +	}

Doesn't this need to take eeprom->offset into account?

[...]
> +	printk(KERN_INFO
> +	       "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
> +	       dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
> +	       dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);

This should use print_mac().

[...]
> +/* SMSC911x registers and bitfields */
> +#define RX_DATA_FIFO			0x00
> +
> +#define TX_DATA_FIFO			0x20
> +#define TX_CMD_A_ON_COMP_		0x80000000

Why do these flag/mask names have trailing underscores?

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-06-02 15:54     ` Ben Hutchings
@ 2008-06-02 16:09       ` Bill Gatliff
  2008-06-02 18:32         ` Peter Korsgaard
  2008-06-02 18:30       ` Steve.Glendinning
  2008-06-04 16:34       ` Steve.Glendinning
  2 siblings, 1 reply; 35+ messages in thread
From: Bill Gatliff @ 2008-06-02 16:09 UTC (permalink / raw)
  To: Ben Hutchings
  Cc: Steve Glendinning, netdev, Michael.Hennerich, Enrik.Berkhan,
	hennerich, ian.saturley, uclinux-dist-devel, catalin.marinas,
	Bahadir Balban, Dustin Mcintire

Ben Hutchings wrote:
>> +static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata)
>> +{
>> +	int result = 0;
>> +	unsigned int i;
>> +	unsigned int val;
>> +	unsigned long flags;
>> +
>> +	/* Initialise tx packet using broadcast destination address */
>> +	for (i = 0; i < 6; i++)
>> +		pdata->loopback_tx_pkt[i] = (char)0xFF;
> 
> The cast to char is just noise.

Not sure.  What about signed vs. unsigned?


b.g.
-- 
Bill Gatliff
bgat@billgatliff.com

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-06-02 15:54     ` Ben Hutchings
  2008-06-02 16:09       ` Bill Gatliff
@ 2008-06-02 18:30       ` Steve.Glendinning
  2008-06-02 19:03         ` Ben Hutchings
  2008-06-04 16:34       ` Steve.Glendinning
  2 siblings, 1 reply; 35+ messages in thread
From: Steve.Glendinning @ 2008-06-02 18:30 UTC (permalink / raw)
  To: Ben Hutchings
  Cc: Bahadir Balban, Bill Gatliff, catalin.marinas, Dustin Mcintire,
	Enrik.Berkhan, hennerich, ian.saturley, Michael.Hennerich, netdev,
	uclinux-dist-devel

Hi Ben,

Thanks for the feedback.  I've implemented most of your suggestions, but 
have a few questions:

> [...]
> > +/* Set a mac register, phy_lock must be acquired before calling */
> > +static void smsc911x_mac_write(struct smsc911x_data *pdata,
> > +                unsigned int offset, u32 val)
> > +{
> > +   unsigned int temp;
> > +
> > +#ifdef CONFIG_DEBUG_SPINLOCK
> > +   if (!spin_is_locked(&pdata->phy_lock))
> > +      SMSC_WARNING("phy_lock not held");
> > +#endif            /* CONFIG_DEBUG_SPINLOCK */
> > +
> > +   temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
> > +   if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
> > +      SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
> > +      return;
> > +   }
> 
> Shouldn't this return an error code?
> 
> [...]
> > +/* Sets a phy register, phy_lock must be acquired before calling */
> > +static void smsc911x_phy_write(struct smsc911x_data *pdata,
> > +                unsigned int index, u16 val)
> > +{
> > +   unsigned int addr;
> > +   int i;
> > +
> > +#ifdef CONFIG_DEBUG_SPINLOCK
> > +   if (!spin_is_locked(&pdata->phy_lock))
> > +      SMSC_WARNING("phy_lock not held");
> > +#endif            /* CONFIG_DEBUG_SPINLOCK */
> > +
> > +   /* Confirm MII not busy */
> > +   if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & 
MII_ACC_MII_BUSY_)) {
> > +      SMSC_WARNING("MII is busy in smsc911x_write_phy???");
> > +      return;
> > +   }
> 
> Similarly for this function.

I could return an error code from these write functions, but the 
equivalent read functions currently return their register value.  Would 
you change the read functions to take a result pointer?

I should mention this "MAC busy at entry" check is an assert to catch 
driver locking problems during development (as is the spinlock check 
above).  If the driver is "correct" it should only be seen in the case of 
hardware failure.

> [...]
> > +static int smsc911x_soft_reset(struct smsc911x_data *pdata)
> > +{
> > +   unsigned int timeout;
> > +   unsigned int temp;
> > +
> > +   /* Reset the LAN911x */
> > +   smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
> > +   timeout = 10;
> > +   do {
> > +      udelay(10);
> > +      temp = smsc911x_reg_read(pdata, HW_CFG);
> > +   } while ((--timeout) && (temp & HW_CFG_SRST_));
> > +
> > +   if (unlikely(temp & HW_CFG_SRST_)) {
> > +      SMSC_WARNING("Failed to complete reset");
> > +      return -ENODEV;
> 
> I think this should be -EIO unless this is only called during probe.

This reset function is called from both probe and open, although its 
return code is only checked for nonzero by both.  Should probe return 
-ENODEV, and open return -EIO if this device reset fails?

> [...]
> > +/* SMSC911x registers and bitfields */
> > +#define RX_DATA_FIFO         0x00
> > +
> > +#define TX_DATA_FIFO         0x20
> > +#define TX_CMD_A_ON_COMP_      0x80000000
> 
> Why do these flag/mask names have trailing underscores?

"It was like that when i got here" :-)  The register offsets don't, but 
the bitmasks do.  Should I remove them?

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-06-02 16:09       ` Bill Gatliff
@ 2008-06-02 18:32         ` Peter Korsgaard
  0 siblings, 0 replies; 35+ messages in thread
From: Peter Korsgaard @ 2008-06-02 18:32 UTC (permalink / raw)
  To: Bill Gatliff
  Cc: Ben Hutchings, Steve Glendinning, netdev, Michael.Hennerich,
	Enrik.Berkhan, hennerich, ian.saturley, uclinux-dist-devel,
	catalin.marinas, Bahadir Balban, Dustin Mcintire

>>>>> "Bill" == Bill Gatliff <bgat@billgatliff.com> writes:

 >>> +	/* Initialise tx packet using broadcast destination address */
 >>> +	for (i = 0; i < 6; i++)
 >>> +		pdata->loopback_tx_pkt[i] = (char)0xFF;
 >> 
 >> The cast to char is just noise.

 Bill> Not sure.  What about signed vs. unsigned?

No, because char can be both signed and unsigned. The driver should
rather do a memset(pdate->loopback_tx_pkt, 0xff, ETH_ALEN);

-- 
Bye, Peter Korsgaard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-05-19 12:34 ` Steve Glendinning
  2008-05-19 16:24   ` Catalin Marinas
  2008-06-02 10:45   ` Steve Glendinning
@ 2008-06-02 18:47   ` Peter Korsgaard
  2 siblings, 0 replies; 35+ messages in thread
From: Peter Korsgaard @ 2008-06-02 18:47 UTC (permalink / raw)
  To: Steve Glendinning
  Cc: Michael.Hennerich, Enrik.Berkhan, hennerich, ian.saturley,
	uclinux-dist-devel, catalin.marinas, netdev, Bahadir Balban,
	Dustin Mcintire, Bill Gatliff

>>>>> "Steve" == Steve Glendinning <steve.glendinning@smsc.com> writes:

Hi,

 Steve> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
 Steve> index 9f6cc8a..8f12a8e 100644
 Steve> --- a/drivers/net/Kconfig
 Steve> +++ b/drivers/net/Kconfig
 Steve> @@ -967,6 +967,19 @@ config SMC911X
 Steve>  	  called smc911x.  If you want to compile it as a module, say M 
 Steve>  	  here and read <file:Documentation/kbuild/modules.txt>
 
 Steve> +config SMSC911X
 Steve> +	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
 Steve> +	depends on NET_ETHERNET
 Steve> +	select CRC32
 Steve> +	select MII
 Steve> +	---help---
 Steve> +	  Say Y here if you want support for SMSC LAN911x and LAN921x families
 Steve> +	  of ethernet controllers.
 Steve> +
 Steve> +	  To compile this driver as a module, choose M here and read
 Steve> +	  <file:Documentation/networking/net-modules.txt>. The module
 Steve> +	  will be called smsc911x.
 Steve> +

What is the point of 2 drivers for the same hardware? Either fix the
existing driver or remove the old one and reuse the Kconfig symbols
for the new one so the existing defconfig continues to work.

Like I said last year, I would really prefer the first option:

http://thread.gmane.org/gmane.linux.network/66703/focus=67812

You also don't seem to have added the big endian test I mentioned
further in the thread.

 Steve> +static void smsc911x_mdio_write(struct net_device *dev, int phy_id,
 Steve> +				int location, int val)
 Steve> +{
 Steve> +	struct smsc911x_data *pdata = netdev_priv(dev);
 Steve> +	unsigned long flags;
 Steve> +
 Steve> +	spin_lock_irqsave(&pdata->phy_lock, flags);
 Steve> +	smsc911x_phy_write(pdata, location, val);
 Steve> +	spin_unlock_irqrestore(&pdata->phy_lock, flags);
 Steve> +}
 Steve> +
 Steve> +/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
 Steve> + * If something goes wrong, returns -ENODEV to revert back to internal phy.
 Steve> + * Performed at initialisation only, so interrupts are enabled */
 Steve> +static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)

Any reason why you don't use phylib?

-- 
Bye, Peter Korsgaard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-06-02 18:30       ` Steve.Glendinning
@ 2008-06-02 19:03         ` Ben Hutchings
  0 siblings, 0 replies; 35+ messages in thread
From: Ben Hutchings @ 2008-06-02 19:03 UTC (permalink / raw)
  To: Steve.Glendinning
  Cc: Bahadir Balban, Bill Gatliff, catalin.marinas, Dustin Mcintire,
	Enrik.Berkhan, hennerich, ian.saturley, Michael.Hennerich, netdev,
	uclinux-dist-devel

Steve.Glendinning@smsc.com wrote:
> Hi Ben,
> 
> Thanks for the feedback.  I've implemented most of your suggestions, but 
> have a few questions:
> 
> > [...]
> > > +/* Set a mac register, phy_lock must be acquired before calling */
> > > +static void smsc911x_mac_write(struct smsc911x_data *pdata,
> > > +                unsigned int offset, u32 val)
> > > +{
> > > +   unsigned int temp;
> > > +
> > > +#ifdef CONFIG_DEBUG_SPINLOCK
> > > +   if (!spin_is_locked(&pdata->phy_lock))
> > > +      SMSC_WARNING("phy_lock not held");
> > > +#endif            /* CONFIG_DEBUG_SPINLOCK */
> > > +
> > > +   temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
> > > +   if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
> > > +      SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry");
> > > +      return;
> > > +   }
> > 
> > Shouldn't this return an error code?
> > 
> > [...]
> > > +/* Sets a phy register, phy_lock must be acquired before calling */
> > > +static void smsc911x_phy_write(struct smsc911x_data *pdata,
> > > +                unsigned int index, u16 val)
> > > +{
> > > +   unsigned int addr;
> > > +   int i;
> > > +
> > > +#ifdef CONFIG_DEBUG_SPINLOCK
> > > +   if (!spin_is_locked(&pdata->phy_lock))
> > > +      SMSC_WARNING("phy_lock not held");
> > > +#endif            /* CONFIG_DEBUG_SPINLOCK */
> > > +
> > > +   /* Confirm MII not busy */
> > > +   if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & 
> MII_ACC_MII_BUSY_)) {
> > > +      SMSC_WARNING("MII is busy in smsc911x_write_phy???");
> > > +      return;
> > > +   }
> > 
> > Similarly for this function.
> 
> I could return an error code from these write functions, but the 
> equivalent read functions currently return their register value.  Would 
> you change the read functions to take a result pointer?

If you can't reserve some range of values for errors (e.g. 32-bit reads
which may yield any 32-bit value) then I suppose so.  For 16-bit reads you
can return an int with negative values reserved for errors.

> I should mention this "MAC busy at entry" check is an assert to catch 
> driver locking problems during development (as is the spinlock check 
> above).  If the driver is "correct" it should only be seen in the case of 
> hardware failure.

Hardware failure does happen though, and once you've detected it it seems
like a bad idea to hide it.

> > [...]
> > > +static int smsc911x_soft_reset(struct smsc911x_data *pdata)
> > > +{
> > > +   unsigned int timeout;
> > > +   unsigned int temp;
> > > +
> > > +   /* Reset the LAN911x */
> > > +   smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG);
> > > +   timeout = 10;
> > > +   do {
> > > +      udelay(10);
> > > +      temp = smsc911x_reg_read(pdata, HW_CFG);
> > > +   } while ((--timeout) && (temp & HW_CFG_SRST_));
> > > +
> > > +   if (unlikely(temp & HW_CFG_SRST_)) {
> > > +      SMSC_WARNING("Failed to complete reset");
> > > +      return -ENODEV;
> > 
> > I think this should be -EIO unless this is only called during probe.
> 
> This reset function is called from both probe and open, although its 
> return code is only checked for nonzero by both.  Should probe return 
> -ENODEV, and open return -EIO if this device reset fails?

Personally I think -EIO is a perfectly reasonable error code for I/O
errors after the device has been positively identified, even during
probe.  You could have the probe function squash other errors into
-ENODEV if you want.

Ben.

-- 
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] SMSC LAN911x and LAN921x vendor driver
  2008-06-02 15:54     ` Ben Hutchings
  2008-06-02 16:09       ` Bill Gatliff
  2008-06-02 18:30       ` Steve.Glendinning
@ 2008-06-04 16:34       ` Steve.Glendinning
  2 siblings, 0 replies; 35+ messages in thread
From: Steve.Glendinning @ 2008-06-04 16:34 UTC (permalink / raw)
  To: Ben Hutchings
  Cc: Bahadir Balban, Bill Gatliff, catalin.marinas, hennerich,
	ian.saturley, Michael.Hennerich, netdev, jeff

Hi Ben,

> [...]
> > +static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 
level)
> > +{
> > +   struct smsc911x_data *pdata = netdev_priv(dev);
> > +   pdata->msg_enable = level;
> > +}
> 
> It would be nice if the logging macros actually tested msg_enable too. 
;-)

I'm implementing this now, using e100's approach (and DPRINTF macro) as a 
guide.
I have a few questions on the "right way" to do this.

1. What's the recommended way to initialise msg_enable? Some drivers set 
it to
a constant, but e100 has a module param called debug and sets msg_enable 
to
((1 << debug) - 1) during probe.  This implies the bits are in order of
importance (i.e. to debug IFDOWN messages one would also have to view DRV,
PROBE, LINK and TIMER messages), is this right?

Once the interface is up, we can turn any combination of bits on and off 
using
ethtool, but a lot of this embedded driver's debugging is done in probe(), 
when
embedded developers/platform integrators are verifying the platform's bus 
layout
and timing.

2. My driver had two levels of message importance (warning and trace), 
chosen
at compile-time so they were #defined out on non-debug builds.  Should I 
remove
this compile-time decision and print the two as KERN_WARNING and 
KERN_DEBUG,
or would printing KERN_DEBUG messages in non-debug builds (when no-one is
interested) be discouraged?

3. DPRINTF adds dev->name to the messages, which is useful.  If I use it 
before
I call register_netdev, dev->name is set to "eth%d" so the debugging 
messages
look strange.  Should I skip the whole msg_enable/DPRINTF thing in probe 
or is
there a better way?

Regards,
--
Steve Glendinning
SMSC GmbH
m: +44 777 933 9124
e: steve.glendinning@smsc.com


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] SMSC LAN911x and LAN921x vendor driver
@ 2008-10-21 13:00 Steve Glendinning
  0 siblings, 0 replies; 35+ messages in thread
From: Steve Glendinning @ 2008-10-21 13:00 UTC (permalink / raw)
  To: netdev
  Cc: catalin.marinas, Bahadir.Balban, dustin, bgat, Ian Saturley,
	g.liakhovetski, Peter Korsgaard, Steve Glendinning

Attached is a driver for SMSC's LAN911x and LAN921x families of embedded
ethernet controllers.

There is an existing smc911x driver in the tree; this is intended to
replace it.  Dustin McIntire (the author of the smc911x driver) has
expressed his support for switching to this driver.

This driver contains workarounds for all known hardware issues, and has
been tested on all flavours of the chip on multiple architectures.

This driver now uses phylib, so this patch also adds support for the
device's internal phy

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
Signed-off-by: Bahadir Balban <Bahadir.Balban@arm.com>
Signed-off-by: Dustin Mcintire <dustin@sensoria.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 MAINTAINERS              |    6 +
 drivers/net/Kconfig      |   14 +
 drivers/net/Makefile     |    1 +
 drivers/net/phy/smsc.c   |   28 +
 drivers/net/smsc911x.c   | 2091 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/smsc911x.h   |  394 +++++++++
 include/linux/smsc911x.h |   42 +
 7 files changed, 2576 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/smsc911x.c
 create mode 100644 drivers/net/smsc911x.h
 create mode 100644 include/linux/smsc911x.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 5c3f79c..ef79094 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3821,6 +3821,12 @@ M:	mhoffman@lightlink.com
 L:	lm-sensors@lm-sensors.org
 S:	Maintained
 
+SMSC911x ETHERNET DRIVER
+P:	Steve Glendinning
+M:	steve.glendinning@smsc.com
+L:	netdev@vger.kernel.org
+S:	Supported
+
 SMX UIO Interface
 P:	Ben Nizette
 M:	bn@niasdigital.com
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ad301ac..1604a1f 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -972,6 +972,20 @@ config SMC911X
 	  called smc911x.  If you want to compile it as a module, say M 
 	  here and read <file:Documentation/kbuild/modules.txt>
 
+config SMSC911X
+	tristate "SMSC LAN911x/LAN921x families embedded ethernet support"
+	depends on ARM || SUPERH
+	select CRC32
+	select MII
+	select PHYLIB
+	---help---
+	  Say Y here if you want support for SMSC LAN911x and LAN921x families
+	  of ethernet controllers.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>. The module
+	  will be called smsc911x.
+
 config NET_VENDOR_RACAL
 	bool "Racal-Interlan (Micom) NI cards"
 	depends on ISA
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index fa2510b..371c759 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -220,6 +220,7 @@ obj-$(CONFIG_S2IO) += s2io.o
 obj-$(CONFIG_MYRI10GE) += myri10ge/
 obj-$(CONFIG_SMC91X) += smc91x.o
 obj-$(CONFIG_SMC911X) += smc911x.o
+obj-$(CONFIG_SMSC911X) += smsc911x.o
 obj-$(CONFIG_BFIN_MAC) += bfin_mac.o
 obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_PASEMI_MAC) += pasemi_mac_driver.o
diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c
index 73baa7a..c05d38d 100644
--- a/drivers/net/phy/smsc.c
+++ b/drivers/net/phy/smsc.c
@@ -126,6 +126,27 @@ static struct phy_driver lan8700_driver = {
 	.driver		= { .owner = THIS_MODULE, }
 };
 
+static struct phy_driver lan911x_int_driver = {
+	.phy_id		= 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN911x Internal PHY",
+
+	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause
+				| SUPPORTED_Asym_Pause),
+	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
+
+	/* basic functions */
+	.config_aneg	= genphy_config_aneg,
+	.read_status	= genphy_read_status,
+	.config_init	= smsc_phy_config_init,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	.driver		= { .owner = THIS_MODULE, }
+};
+
 static int __init smsc_init(void)
 {
 	int ret;
@@ -142,8 +163,14 @@ static int __init smsc_init(void)
 	if (ret)
 		goto err3;
 
+	ret = phy_driver_register (&lan911x_int_driver);
+	if (ret)
+		goto err4;
+
 	return 0;
 
+err4:
+	phy_driver_unregister (&lan8700_driver);
 err3:
 	phy_driver_unregister (&lan8187_driver);
 err2:
@@ -154,6 +181,7 @@ err1:
 
 static void __exit smsc_exit(void)
 {
+	phy_driver_unregister (&lan911x_int_driver);
 	phy_driver_unregister (&lan8700_driver);
 	phy_driver_unregister (&lan8187_driver);
 	phy_driver_unregister (&lan83c185_driver);
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c
new file mode 100644
index 0000000..fe51788
--- /dev/null
+++ b/drivers/net/smsc911x.c
@@ -0,0 +1,2091 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************
+ * Rewritten, heavily based on smsc911x simple driver by SMSC.
+ * Partly uses io macros from smc91x.c by Nicolas Pitre
+ *
+ * Supported devices:
+ *   LAN9115, LAN9116, LAN9117, LAN9118
+ *   LAN9215, LAN9216, LAN9217, LAN9218
+ *   LAN9210, LAN9211
+ *   LAN9220, LAN9221
+ *
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <linux/bug.h>
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/phy.h>
+#include <linux/smsc911x.h>
+#include "smsc911x.h"
+
+#define SMSC_CHIPNAME		"smsc911x"
+#define SMSC_MDIONAME		"smsc911x-mdio"
+#define SMSC_DRV_VERSION	"2008-10-21"
+
+MODULE_LICENSE("GPL");
+MODULE_VERSION(SMSC_DRV_VERSION);
+
+#if USE_DEBUG > 0
+static int debug = 16;
+#else
+static int debug = 3;
+#endif
+
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
+
+struct smsc911x_data {
+	void __iomem *ioaddr;
+
+	unsigned int idrev;
+
+	/* used to decide which workarounds apply */
+	unsigned int generation;
+
+	/* device configuration (copied from platform_data during probe) */
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+	phy_interface_t phy_interface;
+
+	/* This needs to be acquired before calling any of below:
+	 * smsc911x_mac_read(), smsc911x_mac_write()
+	 */
+	spinlock_t mac_lock;
+
+#if (!SMSC_CAN_USE_32BIT)
+	/* spinlock to ensure 16-bit accesses are serialised */
+	spinlock_t dev_lock;
+#endif
+
+	struct phy_device *phy_dev;
+	struct mii_bus *mii_bus;
+	int phy_irq[PHY_MAX_ADDR];
+	unsigned int using_extphy;
+	int last_duplex;
+	int last_carrier;
+
+	u32 msg_enable;
+	unsigned int gpio_setting;
+	unsigned int gpio_orig_setting;
+	struct net_device *dev;
+	struct napi_struct napi;
+
+	unsigned int software_irq_signal;
+
+#ifdef USE_PHY_WORK_AROUND
+#define MIN_PACKET_SIZE (64)
+	char loopback_tx_pkt[MIN_PACKET_SIZE];
+	char loopback_rx_pkt[MIN_PACKET_SIZE];
+	unsigned int resetcount;
+#endif
+
+	/* Members for Multicast filter workaround */
+	unsigned int multicast_update_pending;
+	unsigned int set_bits_mask;
+	unsigned int clear_bits_mask;
+	unsigned int hashhi;
+	unsigned int hashlo;
+};
+
+#if SMSC_CAN_USE_32BIT
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	return readl(pdata->ioaddr + reg);
+}
+
+static inline void smsc911x_reg_write(struct smsc911x_data *pdata, u32 reg,
+				      u32 val)
+{
+	writel(val, pdata->ioaddr + reg);
+}
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	writesl(pdata->ioaddr + TX_DATA_FIFO, buf, wordcount);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	readsl(pdata->ioaddr + RX_DATA_FIFO, buf, wordcount);
+}
+
+#else				/* SMSC_CAN_USE_32BIT */
+
+/* These 16-bit access functions are significantly slower, due to the locking
+ * necessary.  If your bus hardware can be configured to do this for you
+ * (in response to a single 32-bit operation from software), you should use
+ * the 32-bit access functions instead. */
+
+static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg)
+{
+	unsigned long flags;
+	u32 data;
+
+	/* these two 16-bit reads must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	spin_lock_irqsave(&pdata->dev_lock, flags);
+	data = ((readw(pdata->ioaddr + reg) & 0xFFFF) |
+		((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16));
+	spin_unlock_irqrestore(&pdata->dev_lock, flags);
+
+	return data;
+}
+
+static inline void smsc911x_reg_write(struct smsc911x_data *pdata, u32 reg,
+				      u32 val)
+{
+	unsigned long flags;
+
+	/* these two 16-bit writes must be performed consecutively, so must
+	 * not be interrupted by our own ISR (which would start another
+	 * read operation) */
+	spin_lock_irqsave(&pdata->dev_lock, flags);
+	writew(val & 0xFFFF, pdata->ioaddr + reg);
+	writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2);
+	spin_unlock_irqrestore(&pdata->dev_lock, flags);
+}
+
+/* Writes a packet to the TX_DATA_FIFO */
+static inline void
+smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf,
+		      unsigned int wordcount)
+{
+	while (wordcount--)
+		smsc911x_reg_write(pdata, TX_DATA_FIFO, *buf++);
+}
+
+/* Reads a packet out of the RX_DATA_FIFO */
+static inline void
+smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf,
+		     unsigned int wordcount)
+{
+	while (wordcount--)
+		*buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+}
+
+#endif				/* SMSC_CAN_USE_32BIT */
+
+/* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
+ * and smsc911x_mac_write, so assumes mac_lock is held */
+static int smsc911x_mac_complete(struct smsc911x_data *pdata)
+{
+	int i;
+	u32 val;
+
+	SMSC_ASSERT_MAC_LOCK(pdata);
+
+	for (i = 0; i < 40; i++) {
+		val = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+		if (!(val & MAC_CSR_CMD_CSR_BUSY_))
+			return 0;
+	}
+	SMSC_WARNING(HW, "Timed out waiting for MAC not BUSY. "
+		"MAC_CSR_CMD: 0x%08X", val);
+	return -EIO;
+}
+
+/* Fetches a MAC register value. Assumes mac_lock is acquired */
+static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset)
+{
+	unsigned int temp;
+
+	SMSC_ASSERT_MAC_LOCK(pdata);
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING(HW, "MAC busy at entry");
+		return 0xFFFFFFFF;
+	}
+
+	/* Send the MAC cmd */
+	smsc911x_reg_write(pdata, MAC_CSR_CMD, ((offset & 0xFF) |
+		MAC_CSR_CMD_CSR_BUSY_ | MAC_CSR_CMD_R_NOT_W_));
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the read to complete */
+	if (likely(smsc911x_mac_complete(pdata) == 0))
+		return smsc911x_reg_read(pdata, MAC_CSR_DATA);
+
+	SMSC_WARNING(HW, "MAC busy after read");
+	return 0xFFFFFFFF;
+}
+
+/* Set a mac register, mac_lock must be acquired before calling */
+static void smsc911x_mac_write(struct smsc911x_data *pdata,
+			       unsigned int offset, u32 val)
+{
+	unsigned int temp;
+
+	SMSC_ASSERT_MAC_LOCK(pdata);
+
+	temp = smsc911x_reg_read(pdata, MAC_CSR_CMD);
+	if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) {
+		SMSC_WARNING(HW,
+			"smsc911x_mac_write failed, MAC busy at entry");
+		return;
+	}
+
+	/* Send data to write */
+	smsc911x_reg_write(pdata, MAC_CSR_DATA, val);
+
+	/* Write the actual data */
+	smsc911x_reg_write(pdata, MAC_CSR_CMD, ((offset & 0xFF) |
+		MAC_CSR_CMD_CSR_BUSY_));
+
+	/* Workaround for hardware read-after-write restriction */
+	temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
+	/* Wait for the write to complete */
+	if (likely(smsc911x_mac_complete(pdata) == 0))
+		return;
+
+	SMSC_WARNING(HW,
+		"smsc911x_mac_write failed, MAC busy after write");
+}
+
+/* Get a phy register */
+static int smsc911x_mii_read(struct mii_bus *bus, int phyaddr, int regidx)
+{
+	struct smsc911x_data *pdata = (struct smsc911x_data *)bus->priv;
+	unsigned long flags;
+	unsigned int addr;
+	int i, reg;
+
+	spin_lock_irqsave(&pdata->mac_lock, flags);
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING(HW,
+			"MII is busy in smsc911x_mii_read???");
+		reg = -EIO;
+		goto out;
+	}
+
+	/* Set the address, index & direction (read from PHY) */
+	addr = ((phyaddr & 0x1F) << 11) | ((regidx & 0x1F) << 6);
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for read to complete w/ timeout */
+	for (i = 0; i < 100; i++)
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+			reg = smsc911x_mac_read(pdata, MII_DATA);
+			goto out;
+		}
+
+	SMSC_WARNING(HW, "Timed out waiting for MII write to finish");
+	reg = -EIO;
+
+out:
+	spin_unlock_irqrestore(&pdata->mac_lock, flags);
+	return reg;
+}
+
+/* Set a phy register */
+static int smsc911x_mii_write(struct mii_bus *bus, int phyaddr, int regidx,
+			   u16 val)
+{
+	struct smsc911x_data *pdata = (struct smsc911x_data *)bus->priv;
+	unsigned long flags;
+	unsigned int addr;
+	int i, reg;
+
+	spin_lock_irqsave(&pdata->mac_lock, flags);
+
+	/* Confirm MII not busy */
+	if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+		SMSC_WARNING(HW,
+			"MII is busy in smsc911x_mii_write???");
+		reg = -EIO;
+		goto out;
+	}
+
+	/* Put the data to write in the MAC */
+	smsc911x_mac_write(pdata, MII_DATA, val);
+
+	/* Set the address, index & direction (write to PHY) */
+	addr = ((phyaddr & 0x1F) << 11) | ((regidx & 0x1F) << 6) |
+		MII_ACC_MII_WRITE_;
+	smsc911x_mac_write(pdata, MII_ACC, addr);
+
+	/* Wait for write to complete w/ timeout */
+	for (i = 0; i < 100; i++)
+		if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) {
+			reg = 0;
+			goto out;
+		}
+
+	SMSC_WARNING(HW, "Timed out waiting for MII write to finish");
+	reg = -EIO;
+
+out:
+	spin_unlock_irqrestore(&pdata->mac_lock, flags);
+	return reg;
+}
+
+/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors.
+ * If something goes wrong, returns -ENODEV to revert back to internal phy.
+ * Performed at initialisation only, so interrupts are enabled */
+static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+	unsigned int hwcfg = smsc911x_reg_read(pdata, HW_CFG);
+
+	/* External phy is requested, supported, and detected */
+	if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+
+		/* Switch to external phy. Assuming tx and rx are stopped
+		 * because smsc911x_phy_initialise is called before
+		 * smsc911x_rx_initialise and tx_initialise. */
+
+		/* Disable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_;
+		smsc911x_reg_write(pdata, HW_CFG, hwcfg);
+		udelay(10);	/* Enough time for clocks to stop */
+
+		/* Switch to external phy */
+		hwcfg |= HW_CFG_EXT_PHY_EN_;
+		smsc911x_reg_write(pdata, HW_CFG, hwcfg);
+
+		/* Enable phy clocks to the MAC */
+		hwcfg &= (~HW_CFG_PHY_CLK_SEL_);
+		hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_;
+		smsc911x_reg_write(pdata, HW_CFG, hwcfg);
+		udelay(10);	/* Enough time for clocks to restart */
+
+		hwcfg |= HW_CFG_SMI_SEL_;
+		smsc911x_reg_write(pdata, HW_CFG, hwcfg);
+
+		SMSC_TRACE(HW, "Successfully switched to external PHY");
+		pdata->using_extphy = 1;
+	} else {
+		SMSC_WARNING(HW, "No external PHY detected, "
+			"Using internal PHY instead.");
+		/* Use internal phy */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* Fetches a tx status out of the status fifo */
+static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, TX_STATUS_FIFO);
+
+	return result;
+}
+
+/* Fetches the next rx status */
+static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata)
+{
+	unsigned int result =
+	    smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_;
+
+	if (result != 0)
+		result = smsc911x_reg_read(pdata, RX_STATUS_FIFO);
+
+	return result;
+}
+
+#ifdef USE_PHY_WORK_AROUND
+static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata)
+{
+	unsigned int tries;
+	u32 wrsz;
+	u32 rdsz;
+	ulong bufp;
+
+	for (tries = 0; tries < 10; tries++) {
+		unsigned int txcmd_a;
+		unsigned int txcmd_b;
+		unsigned int status;
+		unsigned int pktlength;
+		unsigned int i;
+
+		/* Zero-out rx packet memory */
+		memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE);
+
+		/* Write tx packet to 118 */
+		txcmd_a = (u32)((ulong)pdata->loopback_tx_pkt & 0x03) << 16;
+		txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+		txcmd_a |= MIN_PACKET_SIZE;
+
+		txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE;
+
+		smsc911x_reg_write(pdata, TX_DATA_FIFO, txcmd_a);
+		smsc911x_reg_write(pdata, TX_DATA_FIFO, txcmd_b);
+
+		bufp = (ulong)pdata->loopback_tx_pkt & (~0x3);
+		wrsz = MIN_PACKET_SIZE + 3;
+		wrsz += (u32)((ulong)pdata->loopback_tx_pkt & 0x3);
+		wrsz >>= 2;
+
+		smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+
+		/* Wait till transmit is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_tx_get_txstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING(HW, "Failed to transmit "
+				"during loopback test");
+			continue;
+		}
+		if (status & TX_STS_ES_) {
+			SMSC_WARNING(HW, "Transmit encountered "
+				"errors during loopback test");
+			continue;
+		}
+
+		/* Wait till receive is done */
+		i = 60;
+		do {
+			udelay(5);
+			status = smsc911x_rx_get_rxstatus(pdata);
+		} while ((i--) && (!status));
+
+		if (!status) {
+			SMSC_WARNING(HW,
+				"Failed to receive during loopback test");
+			continue;
+		}
+		if (status & RX_STS_ES_) {
+			SMSC_WARNING(HW, "Receive encountered "
+				"errors during loopback test");
+			continue;
+		}
+
+		pktlength = ((status & 0x3FFF0000UL) >> 16);
+		bufp = (ulong)pdata->loopback_rx_pkt;
+		rdsz = pktlength + 3;
+		rdsz += (u32)((ulong)pdata->loopback_rx_pkt & 0x3);
+		rdsz >>= 2;
+
+		smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz);
+
+		if (pktlength != (MIN_PACKET_SIZE + 4)) {
+			SMSC_WARNING(HW, "Unexpected packet size "
+				"during loop back test, size=%d, will retry",
+				pktlength);
+		} else {
+			unsigned int j;
+			int mismatch = 0;
+			for (j = 0; j < MIN_PACKET_SIZE; j++) {
+				if (pdata->loopback_tx_pkt[j]
+				    != pdata->loopback_rx_pkt[j]) {
+					mismatch = 1;
+					break;
+				}
+			}
+			if (!mismatch) {
+				SMSC_TRACE(HW, "Successfully verified "
+					   "loopback packet");
+				return 0;
+			} else {
+				SMSC_WARNING(HW, "Data mismatch "
+					"during loop back test, will retry");
+			}
+		}
+	}
+
+	return -EIO;
+}
+
+static int smsc911x_phy_reset(struct smsc911x_data *pdata)
+{
+	struct phy_device *phy_dev = pdata->phy_dev;
+	unsigned int temp;
+	unsigned int i = 100000;
+
+	BUG_ON(!phy_dev);
+	BUG_ON(!phy_dev->bus);
+
+	SMSC_TRACE(HW, "Performing PHY BCR Reset");
+	smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, BMCR_RESET);
+	do {
+		msleep(1);
+		temp = smsc911x_mii_read(phy_dev->bus, phy_dev->addr,
+			MII_BMCR);
+	} while ((i--) && (temp & BMCR_RESET));
+
+	if (temp & BMCR_RESET) {
+		SMSC_WARNING(HW, "PHY reset failed to complete.");
+		return -EIO;
+	}
+	/* Extra delay required because the phy may not be completed with
+	* its reset when BMCR_RESET is cleared. Specs say 256 uS is
+	* enough delay but using 1ms here to be safe */
+	msleep(1);
+
+	return 0;
+}
+
+static int smsc911x_phy_loopbacktest(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct phy_device *phy_dev = pdata->phy_dev;
+	int result = -EIO;
+	unsigned int i, val;
+	unsigned long flags;
+
+	/* Initialise tx packet using broadcast destination address */
+	memset(pdata->loopback_tx_pkt, 0xff, ETH_ALEN);
+
+	/* Use incrementing source address */
+	for (i = 6; i < 12; i++)
+		pdata->loopback_tx_pkt[i] = (char)i;
+
+	/* Set length type field */
+	pdata->loopback_tx_pkt[12] = 0x00;
+	pdata->loopback_tx_pkt[13] = 0x00;
+
+	for (i = 14; i < MIN_PACKET_SIZE; i++)
+		pdata->loopback_tx_pkt[i] = (char)i;
+
+	val = smsc911x_reg_read(pdata, HW_CFG);
+	val &= HW_CFG_TX_FIF_SZ_;
+	val |= HW_CFG_SF_;
+	smsc911x_reg_write(pdata, HW_CFG, val);
+
+	smsc911x_reg_write(pdata, TX_CFG, TX_CFG_TX_ON_);
+	smsc911x_reg_write(pdata, RX_CFG,
+		(u32)((ulong)pdata->loopback_rx_pkt & 0x03) << 8);
+
+	for (i = 0; i < 10; i++) {
+		/* Set PHY to 10/FD, no ANEG, and loopback mode */
+		smsc911x_mii_write(phy_dev->bus, phy_dev->addr,	MII_BMCR,
+			BMCR_LOOPBACK | BMCR_FULLDPLX);
+
+		/* Enable MAC tx/rx, FD */
+		spin_lock_irqsave(&pdata->mac_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_
+				   | MAC_CR_TXEN_ | MAC_CR_RXEN_);
+		spin_unlock_irqrestore(&pdata->mac_lock, flags);
+
+		if (smsc911x_phy_check_loopbackpkt(pdata) == 0) {
+			result = 0;
+			break;
+		}
+		pdata->resetcount++;
+
+		/* Disable MAC rx */
+		spin_lock_irqsave(&pdata->mac_lock, flags);
+		smsc911x_mac_write(pdata, MAC_CR, 0);
+		spin_unlock_irqrestore(&pdata->mac_lock, flags);
+
+		smsc911x_phy_reset(pdata);
+	}
+
+	/* Disable MAC */
+	spin_lock_irqsave(&pdata->mac_lock, flags);
+	smsc911x_mac_write(pdata, MAC_CR, 0);
+	spin_unlock_irqrestore(&pdata->mac_lock, flags);
+
+	/* Cancel PHY loopback mode */
+	smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, 0);
+
+	smsc911x_reg_write(pdata, TX_CFG, 0);
+	smsc911x_reg_write(pdata, RX_CFG, 0);
+
+	return result;
+}
+#endif				/* USE_PHY_WORK_AROUND */
+
+static u8 smsc95xx_resolve_flowctrl_fulldplx(u16 lcladv, u16 rmtadv)
+{
+	u8 cap = 0;
+
+	if (lcladv & ADVERTISE_PAUSE_CAP) {
+		if (lcladv & ADVERTISE_PAUSE_ASYM) {
+			if (rmtadv & LPA_PAUSE_CAP)
+				cap = FLOW_CTRL_TX | FLOW_CTRL_RX;
+			else if (rmtadv & LPA_PAUSE_ASYM)
+				cap = FLOW_CTRL_RX;
+		} else {
+			if (rmtadv & LPA_PAUSE_CAP)
+				cap = FLOW_CTRL_TX | FLOW_CTRL_RX;
+		}
+	} else if (lcladv & ADVERTISE_PAUSE_ASYM) {
+		if ((rmtadv & LPA_PAUSE_CAP) && (rmtadv & LPA_PAUSE_ASYM))
+			cap = FLOW_CTRL_TX;
+	}
+
+	return cap;
+}
+
+static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
+{
+	struct phy_device *phy_dev = pdata->phy_dev;
+	u32 afc = smsc911x_reg_read(pdata, AFC_CFG);
+	u32 flow;
+	unsigned long flags;
+
+	if (phy_dev->duplex == DUPLEX_FULL) {
+		u16 lcladv = phy_read(phy_dev, MII_ADVERTISE);
+		u16 rmtadv = phy_read(phy_dev, MII_LPA);
+		u8 cap = smsc95xx_resolve_flowctrl_fulldplx(lcladv, rmtadv);
+
+		if (cap & FLOW_CTRL_RX)
+			flow = 0xFFFF0002;
+		else
+			flow = 0;
+
+		if (cap & FLOW_CTRL_TX)
+			afc |= 0xF;
+		else
+			afc &= ~0xF;
+
+		SMSC_TRACE(HW, "rx pause %s, tx pause %s",
+			(cap & FLOW_CTRL_RX ? "enabled" : "disabled"),
+			(cap & FLOW_CTRL_TX ? "enabled" : "disabled"));
+	} else {
+		SMSC_TRACE(HW, "half duplex");
+		flow = 0;
+		afc |= 0xF;
+	}
+
+	spin_lock_irqsave(&pdata->mac_lock, flags);
+	smsc911x_mac_write(pdata, FLOW, flow);
+	spin_unlock_irqrestore(&pdata->mac_lock, flags);
+
+	smsc911x_reg_write(pdata, AFC_CFG, afc);
+}
+
+/* Update link mode if anything has changed.  Called periodically when the
+ * PHY is in polling mode, even if nothing has changed. */
+static void smsc911x_phy_adjust_link(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct phy_device *phy_dev = pdata->phy_dev;
+	unsigned long flags;
+	int carrier;
+
+	if (phy_dev->duplex != pdata->last_duplex) {
+		unsigned int mac_cr;
+		SMSC_TRACE(HW, "duplex state has changed");
+
+		spin_lock_irqsave(&pdata->mac_lock, flags);
+		mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+		if (phy_dev->duplex) {
+			SMSC_TRACE(HW,
+				"configuring for full duplex mode");
+			mac_cr |= MAC_CR_FDPX_;
+		} else {
+			SMSC_TRACE(HW,
+				"configuring for half duplex mode");
+			mac_cr &= ~MAC_CR_FDPX_;
+		}
+		smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+		spin_unlock_irqrestore(&pdata->mac_lock, flags);
+
+		smsc911x_phy_update_flowcontrol(pdata);
+		pdata->last_duplex = phy_dev->duplex;
+	}
+
+	carrier = netif_carrier_ok(dev);
+	if (carrier != pdata->last_carrier) {
+		SMSC_TRACE(HW, "carrier state has changed");
+		if (carrier) {
+			SMSC_TRACE(HW, "configuring for carrier OK");
+			if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) &&
+			    (!pdata->using_extphy)) {
+				/* Restore orginal GPIO configuration */
+				pdata->gpio_setting = pdata->gpio_orig_setting;
+				smsc911x_reg_write(pdata, GPIO_CFG,
+					pdata->gpio_setting);
+			}
+		} else {
+			SMSC_TRACE(HW, "configuring for no carrier");
+			/* Check global setting that LED1
+			 * usage is 10/100 indicator */
+			pdata->gpio_setting = smsc911x_reg_read(pdata,
+				GPIO_CFG);
+			if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_)
+			    && (!pdata->using_extphy)) {
+				/* Force 10/100 LED off, after saving
+				 * orginal GPIO configuration */
+				pdata->gpio_orig_setting = pdata->gpio_setting;
+
+				pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_;
+				pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_
+							| GPIO_CFG_GPIODIR0_
+							| GPIO_CFG_GPIOD0_);
+				smsc911x_reg_write(pdata, GPIO_CFG,
+					pdata->gpio_setting);
+			}
+		}
+		pdata->last_carrier = carrier;
+	}
+}
+
+static int smsc911x_mii_probe(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct phy_device *phydev = NULL;
+	int phy_addr;
+
+	/* find the first phy */
+	for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) {
+		if (pdata->mii_bus->phy_map[phy_addr]) {
+			phydev = pdata->mii_bus->phy_map[phy_addr];
+			SMSC_TRACE(PROBE, "PHY %d: addr %d, phy_id 0x%08X",
+				phy_addr, phydev->addr, phydev->phy_id);
+			break;
+		}
+	}
+
+	if (!phydev) {
+		pr_err("%s: no PHY found\n", dev->name);
+		return -ENODEV;
+	}
+
+	phydev = phy_connect(dev, phydev->dev.bus_id,
+		&smsc911x_phy_adjust_link, 0, pdata->phy_interface);
+
+	if (IS_ERR(phydev)) {
+		pr_err("%s: Could not attach to PHY\n", dev->name);
+		return PTR_ERR(phydev);
+	}
+
+	pr_info("%s: attached PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n",
+		dev->name, phydev->drv->name, phydev->dev.bus_id, phydev->irq);
+
+	/* mask with MAC supported features */
+	phydev->supported &= (PHY_BASIC_FEATURES | SUPPORTED_Pause |
+			      SUPPORTED_Asym_Pause);
+	phydev->advertising = phydev->supported;
+
+	pdata->phy_dev = phydev;
+	pdata->last_duplex = -1;
+	pdata->last_carrier = -1;
+
+#ifdef USE_PHY_WORK_AROUND
+	if (smsc911x_phy_loopbacktest(dev) < 0) {
+		SMSC_WARNING(HW, "Failed Loop Back Test");
+		return -ENODEV;
+	}
+	SMSC_TRACE(HW, "Passed Loop Back Test");
+#endif				/* USE_PHY_WORK_AROUND */
+
+	SMSC_TRACE(HW, "phy initialised succesfully");
+	return 0;
+}
+
+static int __devinit smsc911x_mii_init(struct platform_device *pdev,
+				       struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	int err = -ENXIO, i;
+
+	pdata->mii_bus = mdiobus_alloc();
+	if (!pdata->mii_bus) {
+		err = -ENOMEM;
+		goto err_out_1;
+	}
+
+	pdata->mii_bus->name = SMSC_MDIONAME;
+	snprintf(pdata->mii_bus->id, MII_BUS_ID_SIZE, "%x", pdev->id);
+	pdata->mii_bus->priv = pdata;
+	pdata->mii_bus->read = smsc911x_mii_read;
+	pdata->mii_bus->write = smsc911x_mii_write;
+	pdata->mii_bus->irq = pdata->phy_irq;
+	for (i = 0; i < PHY_MAX_ADDR; ++i)
+		pdata->mii_bus->irq[i] = PHY_POLL;
+
+	pdata->mii_bus->parent = &pdev->dev;
+	dev_set_drvdata(&pdev->dev, &pdata->mii_bus);
+
+	pdata->using_extphy = 0;
+
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01170000:
+	case 0x01150000:
+	case 0x117A0000:
+	case 0x115A0000:
+		/* External PHY supported, try to autodetect */
+		if (smsc911x_phy_initialise_external(pdata) < 0) {
+			SMSC_TRACE(HW, "No external PHY detected, "
+				"using internal PHY");
+		}
+		break;
+	default:
+		SMSC_TRACE(HW, "External PHY is not supported, "
+			"using internal PHY");
+		break;
+	}
+
+	if (!pdata->using_extphy) {
+		/* Mask all PHYs except ID 1 (internal) */
+		pdata->mii_bus->phy_mask = ~(1 << 1);
+	}
+
+	if (mdiobus_register(pdata->mii_bus)) {
+		SMSC_WARNING(PROBE, "Error registering mii bus");
+		goto err_out_free_bus_2;
+	}
+
+	if (smsc911x_mii_probe(dev) < 0) {
+		SMSC_WARNING(PROBE, "Error registering mii bus");
+		goto err_out_unregister_bus_3;
+	}
+
+	return 0;
+
+err_out_unregister_bus_3:
+	mdiobus_unregister(pdata->mii_bus);
+err_out_free_bus_2:
+	mdiobus_free(pdata->mii_bus);
+err_out_1:
+	return err;
+}
+
+/* Gets the number of tx statuses in the fifo */
+static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata)
+{
+	return (smsc911x_reg_read(pdata, TX_FIFO_INF)
+		& TX_FIFO_INF_TSUSED_) >> 16;
+}
+
+/* Reads tx statuses and increments counters where necessary */
+static void smsc911x_tx_update_txcounters(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int tx_stat;
+
+	while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) {
+		if (unlikely(tx_stat & 0x80000000)) {
+			/* In this driver the packet tag is used as the packet
+			 * length. Since a packet length can never reach the
+			 * size of 0x8000, this bit is reserved. It is worth
+			 * noting that the "reserved bit" in the warning above
+			 * does not reference a hardware defined reserved bit
+			 * but rather a driver defined one.
+			 */
+			SMSC_WARNING(HW,
+				"Packet tag reserved bit is high");
+		} else {
+			if (unlikely(tx_stat & 0x00008000)) {
+				dev->stats.tx_errors++;
+			} else {
+				dev->stats.tx_packets++;
+				dev->stats.tx_bytes += (tx_stat >> 16);
+			}
+			if (unlikely(tx_stat & 0x00000100)) {
+				dev->stats.collisions += 16;
+				dev->stats.tx_aborted_errors += 1;
+			} else {
+				dev->stats.collisions +=
+				    ((tx_stat >> 3) & 0xF);
+			}
+			if (unlikely(tx_stat & 0x00000800))
+				dev->stats.tx_carrier_errors += 1;
+			if (unlikely(tx_stat & 0x00000200)) {
+				dev->stats.collisions++;
+				dev->stats.tx_aborted_errors++;
+			}
+		}
+	}
+}
+
+/* Increments the Rx error counters */
+static void
+smsc911x_rx_counterrors(struct net_device *dev, unsigned int rxstat)
+{
+	int crc_err = 0;
+
+	if (unlikely(rxstat & 0x00008000)) {
+		dev->stats.rx_errors++;
+		if (unlikely(rxstat & 0x00000002)) {
+			dev->stats.rx_crc_errors++;
+			crc_err = 1;
+		}
+	}
+	if (likely(!crc_err)) {
+		if (unlikely((rxstat & 0x00001020) == 0x00001020)) {
+			/* Frame type indicates length,
+			 * and length error is set */
+			dev->stats.rx_length_errors++;
+		}
+		if (rxstat & RX_STS_MCAST_)
+			dev->stats.multicast++;
+	}
+}
+
+/* Quickly dumps bad packets */
+static void
+smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes)
+{
+	unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2;
+
+	if (likely(pktwords >= 4)) {
+		unsigned int timeout = 500;
+		unsigned int val;
+		smsc911x_reg_write(pdata, RX_DP_CTRL, RX_DP_CTRL_RX_FFWD_);
+		do {
+			udelay(1);
+			val = smsc911x_reg_read(pdata, RX_DP_CTRL);
+		} while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+
+		if (unlikely(timeout == 0))
+			SMSC_WARNING(HW, "Timed out waiting for "
+				"RX FFWD to finish, RX_DP_CTRL: 0x%08X", val);
+	} else {
+		unsigned int temp;
+		while (pktwords--)
+			temp = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+	}
+}
+
+/* NAPI poll function */
+static int smsc911x_poll(struct napi_struct *napi, int budget)
+{
+	struct smsc911x_data *pdata =
+		container_of(napi, struct smsc911x_data, napi);
+	struct net_device *dev = pdata->dev;
+	int npackets = 0;
+
+	while (likely(netif_running(dev)) && (npackets < budget)) {
+		unsigned int pktlength;
+		unsigned int pktwords;
+		struct sk_buff *skb;
+		unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata);
+
+		if (!rxstat) {
+			unsigned int temp;
+			/* We processed all packets available.  Tell NAPI it can
+			 * stop polling then re-enable rx interrupts */
+			smsc911x_reg_write(pdata, INT_STS, INT_STS_RSFL_);
+			netif_rx_complete(dev, napi);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RSFL_EN_;
+			smsc911x_reg_write(pdata, INT_EN, temp);
+			break;
+		}
+
+		/* Count packet for NAPI scheduling, even if it has an error.
+		 * Error packets still require cycles to discard */
+		npackets++;
+
+		pktlength = ((rxstat & 0x3FFF0000) >> 16);
+		pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2;
+		smsc911x_rx_counterrors(dev, rxstat);
+
+		if (unlikely(rxstat & RX_STS_ES_)) {
+			SMSC_WARNING(RX_ERR,
+				"Discarding packet with error bit set");
+			/* Packet has an error, discard it and continue with
+			 * the next */
+			smsc911x_rx_fastforward(pdata, pktwords);
+			dev->stats.rx_dropped++;
+			continue;
+		}
+
+		skb = netdev_alloc_skb(dev, pktlength + NET_IP_ALIGN);
+		if (unlikely(!skb)) {
+			SMSC_WARNING(RX_ERR,
+				"Unable to allocate skb for rx packet");
+			/* Drop the packet and stop this polling iteration */
+			smsc911x_rx_fastforward(pdata, pktwords);
+			dev->stats.rx_dropped++;
+			break;
+		}
+
+		skb->data = skb->head;
+		skb_reset_tail_pointer(skb);
+
+		/* Align IP on 16B boundary */
+		skb_reserve(skb, NET_IP_ALIGN);
+		skb_put(skb, pktlength - 4);
+		smsc911x_rx_readfifo(pdata, (unsigned int *)skb->head,
+				     pktwords);
+		skb->protocol = eth_type_trans(skb, dev);
+		skb->ip_summed = CHECKSUM_NONE;
+		netif_receive_skb(skb);
+
+		/* Update counters */
+		dev->stats.rx_packets++;
+		dev->stats.rx_bytes += (pktlength - 4);
+		dev->last_rx = jiffies;
+	}
+
+	/* Return total received packets */
+	return npackets;
+}
+
+/* Returns hash bit number for given MAC address
+ * Example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc911x_hash(char addr[ETH_ALEN])
+{
+	return (ether_crc(ETH_ALEN, addr) >> 26) & 0x3f;
+}
+
+static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata)
+{
+	/* Performs the multicast & mac_cr update.  This is called when
+	 * safe on the current hardware, and with the mac_lock held */
+	unsigned int mac_cr;
+
+	SMSC_ASSERT_MAC_LOCK(pdata);
+
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= pdata->set_bits_mask;
+	mac_cr &= ~(pdata->clear_bits_mask);
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+	smsc911x_mac_write(pdata, HASHH, pdata->hashhi);
+	smsc911x_mac_write(pdata, HASHL, pdata->hashlo);
+	SMSC_TRACE(HW, "maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X",
+		mac_cr, pdata->hashhi, pdata->hashlo);
+}
+
+static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata)
+{
+	unsigned int mac_cr;
+
+	/* This function is only called for older LAN911x devices
+	 * (revA or revB), where MAC_CR, HASHH and HASHL should not
+	 * be modified during Rx - newer devices immediately update the
+	 * registers.
+	 *
+	 * This is called from interrupt context */
+
+	spin_lock(&pdata->mac_lock);
+
+	/* Check Rx has stopped */
+	if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_)
+		SMSC_WARNING(DRV, "Rx not stopped");
+
+	/* Perform the update - safe to do now Rx has stopped */
+	smsc911x_rx_multicast_update(pdata);
+
+	/* Re-enable Rx */
+	mac_cr = smsc911x_mac_read(pdata, MAC_CR);
+	mac_cr |= MAC_CR_RXEN_;
+	smsc911x_mac_write(pdata, MAC_CR, mac_cr);
+
+	pdata->multicast_update_pending = 0;
+
+	spin_unlock(&pdata->mac_lock);
+}
+
+static int smsc911x_soft_reset(struct smsc911x_data *pdata)
+{
+	unsigned int timeout;
+	unsigned int temp;
+
+	/* Reset the LAN911x */
+	smsc911x_reg_write(pdata, HW_CFG, HW_CFG_SRST_);
+	timeout = 10;
+	do {
+		udelay(10);
+		temp = smsc911x_reg_read(pdata, HW_CFG);
+	} while ((--timeout) && (temp & HW_CFG_SRST_));
+
+	if (unlikely(temp & HW_CFG_SRST_)) {
+		SMSC_WARNING(DRV, "Failed to complete reset");
+		return -EIO;
+	}
+	return 0;
+}
+
+/* Sets the device MAC address to dev_addr, called with mac_lock held */
+static void
+smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6])
+{
+	u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4];
+	u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) |
+	    (dev_addr[1] << 8) | dev_addr[0];
+
+	SMSC_ASSERT_MAC_LOCK(pdata);
+
+	smsc911x_mac_write(pdata, ADDRH, mac_high16);
+	smsc911x_mac_write(pdata, ADDRL, mac_low32);
+}
+
+static int smsc911x_open(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int timeout;
+	unsigned int temp;
+	unsigned int intcfg;
+
+	/* if the phy is not yet registered, retry later*/
+	if (!pdata->phy_dev) {
+		SMSC_WARNING(HW, "phy_dev is NULL");
+		return -EAGAIN;
+	}
+
+	if (!is_valid_ether_addr(dev->dev_addr)) {
+		SMSC_WARNING(HW, "dev_addr is not a valid MAC address");
+		return -EADDRNOTAVAIL;
+	}
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata)) {
+		SMSC_WARNING(HW, "soft reset failed");
+		return -EIO;
+	}
+
+	smsc911x_reg_write(pdata, HW_CFG, 0x00050000);
+	smsc911x_reg_write(pdata, AFC_CFG, 0x006E3740);
+
+	/* Make sure EEPROM has finished loading before setting GPIO_CFG */
+	timeout = 50;
+	while ((timeout--) &&
+	       (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+		udelay(10);
+	}
+
+	if (unlikely(timeout == 0))
+		SMSC_WARNING(IFUP,
+			"Timed out waiting for EEPROM busy bit to clear");
+
+	smsc911x_reg_write(pdata, GPIO_CFG, 0x70070000);
+
+	/* The soft reset above cleared the device's MAC address,
+	 * restore it from local copy (set in probe) */
+	spin_lock_irq(&pdata->mac_lock);
+	smsc911x_set_mac_address(pdata, dev->dev_addr);
+	spin_unlock_irq(&pdata->mac_lock);
+
+	/* Initialise irqs, but leave all sources disabled */
+	smsc911x_reg_write(pdata, INT_EN, 0);
+	smsc911x_reg_write(pdata, INT_STS, 0xFFFFFFFF);
+
+	/* Set interrupt deassertion to 100uS */
+	intcfg = ((10 << 24) | INT_CFG_IRQ_EN_);
+
+	if (pdata->irq_polarity) {
+		SMSC_TRACE(IFUP, "irq polarity: active high");
+		intcfg |= INT_CFG_IRQ_POL_;
+	} else {
+		SMSC_TRACE(IFUP, "irq polarity: active low");
+	}
+
+	if (pdata->irq_type) {
+		SMSC_TRACE(IFUP, "irq type: push-pull");
+		intcfg |= INT_CFG_IRQ_TYPE_;
+	} else {
+		SMSC_TRACE(IFUP, "irq type: open drain");
+	}
+
+	smsc911x_reg_write(pdata, INT_CFG, intcfg);
+
+	SMSC_TRACE(IFUP, "Testing irq handler using IRQ %d", dev->irq);
+	pdata->software_irq_signal = 0;
+	smp_wmb();
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= INT_EN_SW_INT_EN_;
+	smsc911x_reg_write(pdata, INT_EN, temp);
+
+	timeout = 1000;
+	while (timeout--) {
+		if (pdata->software_irq_signal)
+			break;
+		msleep(1);
+	}
+
+	if (!pdata->software_irq_signal) {
+		dev_warn(&dev->dev, "ISR failed signaling test (IRQ %d)\n",
+			 dev->irq);
+		return -ENODEV;
+	}
+	SMSC_TRACE(IFUP, "IRQ handler passed test using IRQ %d", dev->irq);
+
+	dev_info(&dev->dev, "SMSC911x/921x identified at %#08lx, IRQ: %d\n",
+		 (unsigned long)pdata->ioaddr, dev->irq);
+
+	/* Bring the PHY up */
+	phy_start(pdata->phy_dev);
+
+	temp = smsc911x_reg_read(pdata, HW_CFG);
+	/* Preserve TX FIFO size and external PHY configuration */
+	temp &= (HW_CFG_TX_FIF_SZ_|0x00000FFF);
+	temp |= HW_CFG_SF_;
+	smsc911x_reg_write(pdata, HW_CFG, temp);
+
+	temp = smsc911x_reg_read(pdata, FIFO_INT);
+	temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+	temp &= ~(FIFO_INT_RX_STS_LEVEL_);
+	smsc911x_reg_write(pdata, FIFO_INT, temp);
+
+	/* set RX Data offset to 2 bytes for alignment */
+	smsc911x_reg_write(pdata, RX_CFG, (2 << 8));
+
+	/* enable NAPI polling before enabling RX interrupts */
+	napi_enable(&pdata->napi);
+
+	temp = smsc911x_reg_read(pdata, INT_EN);
+	temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_);
+	smsc911x_reg_write(pdata, INT_EN, temp);
+
+	spin_lock_irq(&pdata->mac_lock);
+	temp = smsc911x_mac_read(pdata, MAC_CR);
+	temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_);
+	smsc911x_mac_write(pdata, MAC_CR, temp);
+	spin_unlock_irq(&pdata->mac_lock);
+
+	smsc911x_reg_write(pdata, TX_CFG, TX_CFG_TX_ON_);
+
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Entry point for stopping the interface */
+static int smsc911x_stop(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int temp;
+
+	BUG_ON(!pdata->phy_dev);
+
+	/* Disable all device interrupts */
+	temp = smsc911x_reg_read(pdata, INT_CFG);
+	temp &= ~INT_CFG_IRQ_EN_;
+	smsc911x_reg_write(pdata, INT_CFG, temp);
+
+	/* Stop Tx and Rx polling */
+	netif_stop_queue(dev);
+	napi_disable(&pdata->napi);
+
+	/* At this point all Rx and Tx activity is stopped */
+	dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	smsc911x_tx_update_txcounters(dev);
+
+	/* Bring the PHY down */
+	phy_stop(pdata->phy_dev);
+
+	SMSC_TRACE(IFDOWN, "Interface stopped");
+	return 0;
+}
+
+/* Entry point for transmitting a packet */
+static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int freespace;
+	unsigned int tx_cmd_a;
+	unsigned int tx_cmd_b;
+	unsigned int temp;
+	u32 wrsz;
+	ulong bufp;
+
+	freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_;
+
+	if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD))
+		SMSC_WARNING(TX_ERR,
+			"Tx data fifo low, space available: %d", freespace);
+
+	/* Word alignment adjustment */
+	tx_cmd_a = (u32)((ulong)skb->data & 0x03) << 16;
+	tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+	tx_cmd_a |= (unsigned int)skb->len;
+
+	tx_cmd_b = ((unsigned int)skb->len) << 16;
+	tx_cmd_b |= (unsigned int)skb->len;
+
+	smsc911x_reg_write(pdata, TX_DATA_FIFO, tx_cmd_a);
+	smsc911x_reg_write(pdata, TX_DATA_FIFO, tx_cmd_b);
+
+	bufp = (ulong)skb->data & (~0x3);
+	wrsz = (u32)skb->len + 3;
+	wrsz += (u32)((ulong)skb->data & 0x3);
+	wrsz >>= 2;
+
+	smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz);
+	freespace -= (skb->len + 32);
+	dev_kfree_skb(skb);
+	dev->trans_start = jiffies;
+
+	if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30))
+		smsc911x_tx_update_txcounters(dev);
+
+	if (freespace < TX_FIFO_LOW_THRESHOLD) {
+		netif_stop_queue(dev);
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp &= 0x00FFFFFF;
+		temp |= 0x32000000;
+		smsc911x_reg_write(pdata, FIFO_INT, temp);
+	}
+
+	return NETDEV_TX_OK;
+}
+
+/* Entry point for getting status counters */
+static struct net_device_stats *smsc911x_get_stats(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	smsc911x_tx_update_txcounters(dev);
+	dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP);
+	return &dev->stats;
+}
+
+/* Entry point for setting addressing modes */
+static void smsc911x_set_multicast_list(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned long flags;
+
+	if (dev->flags & IFF_PROMISC) {
+		/* Enabling promiscuous mode */
+		pdata->set_bits_mask = MAC_CR_PRMS_;
+		pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->flags & IFF_ALLMULTI) {
+		/* Enabling all multicast mode */
+		pdata->set_bits_mask = MAC_CR_MCPAS_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	} else if (dev->mc_count > 0) {
+		/* Enabling specific multicast addresses */
+		unsigned int hash_high = 0;
+		unsigned int hash_low = 0;
+		unsigned int count = 0;
+		struct dev_mc_list *mc_list = dev->mc_list;
+
+		pdata->set_bits_mask = MAC_CR_HPFILT_;
+		pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_);
+
+		while (mc_list) {
+			count++;
+			if ((mc_list->dmi_addrlen) == ETH_ALEN) {
+				unsigned int bitnum =
+				    smsc911x_hash(mc_list->dmi_addr);
+				unsigned int mask = 0x01 << (bitnum & 0x1F);
+				if (bitnum & 0x20)
+					hash_high |= mask;
+				else
+					hash_low |= mask;
+			} else {
+				SMSC_WARNING(DRV, "dmi_addrlen != 6");
+			}
+			mc_list = mc_list->next;
+		}
+		if (count != (unsigned int)dev->mc_count)
+			SMSC_WARNING(DRV, "mc_count != dev->mc_count");
+
+		pdata->hashhi = hash_high;
+		pdata->hashlo = hash_low;
+	} else {
+		/* Enabling local MAC address only */
+		pdata->set_bits_mask = 0;
+		pdata->clear_bits_mask =
+		    (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+		pdata->hashhi = 0;
+		pdata->hashlo = 0;
+	}
+
+	spin_lock_irqsave(&pdata->mac_lock, flags);
+
+	if (pdata->generation <= 1) {
+		/* Older hardware revision - cannot change these flags while
+		 * receiving data */
+		if (!pdata->multicast_update_pending) {
+			unsigned int temp;
+			SMSC_TRACE(HW, "scheduling mcast update");
+			pdata->multicast_update_pending = 1;
+
+			/* Request the hardware to stop, then perform the
+			 * update when we get an RX_STOP interrupt */
+			smsc911x_reg_write(pdata, INT_STS, INT_STS_RXSTOP_INT_);
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp |= INT_EN_RXSTOP_INT_EN_;
+			smsc911x_reg_write(pdata, INT_EN, temp);
+
+			temp = smsc911x_mac_read(pdata, MAC_CR);
+			temp &= ~(MAC_CR_RXEN_);
+			smsc911x_mac_write(pdata, MAC_CR, temp);
+		} else {
+			/* There is another update pending, this should now
+			 * use the newer values */
+		}
+	} else {
+		/* Newer hardware revision - can write immediately */
+		smsc911x_rx_multicast_update(pdata);
+	}
+
+	spin_unlock_irqrestore(&pdata->mac_lock, flags);
+}
+
+static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u32 intsts = smsc911x_reg_read(pdata, INT_STS);
+	u32 inten = smsc911x_reg_read(pdata, INT_EN);
+	int serviced = IRQ_NONE;
+	u32 temp;
+
+	if (unlikely(intsts & inten & INT_STS_SW_INT_)) {
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_SW_INT_EN_);
+		smsc911x_reg_write(pdata, INT_EN, temp);
+		smsc911x_reg_write(pdata, INT_STS, INT_STS_SW_INT_);
+		pdata->software_irq_signal = 1;
+		smp_wmb();
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) {
+		/* Called when there is a multicast update scheduled and
+		 * it is now safe to complete the update */
+		SMSC_TRACE(INTR, "RX Stop interrupt");
+		temp = smsc911x_reg_read(pdata, INT_EN);
+		temp &= (~INT_EN_RXSTOP_INT_EN_);
+		smsc911x_reg_write(pdata, INT_EN, temp);
+		smsc911x_reg_write(pdata, INT_STS, INT_STS_RXSTOP_INT_);
+		smsc911x_rx_multicast_update_workaround(pdata);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (intsts & inten & INT_STS_TDFA_) {
+		temp = smsc911x_reg_read(pdata, FIFO_INT);
+		temp |= FIFO_INT_TX_AVAIL_LEVEL_;
+		smsc911x_reg_write(pdata, FIFO_INT, temp);
+		smsc911x_reg_write(pdata, INT_STS, INT_STS_TDFA_);
+		netif_wake_queue(dev);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (unlikely(intsts & inten & INT_STS_RXE_)) {
+		SMSC_TRACE(INTR, "RX Error interrupt");
+		smsc911x_reg_write(pdata, INT_STS, INT_STS_RXE_);
+		serviced = IRQ_HANDLED;
+	}
+
+	if (likely(intsts & inten & INT_STS_RSFL_)) {
+		if (likely(netif_rx_schedule_prep(dev, &pdata->napi))) {
+			/* Disable Rx interrupts */
+			temp = smsc911x_reg_read(pdata, INT_EN);
+			temp &= (~INT_EN_RSFL_EN_);
+			smsc911x_reg_write(pdata, INT_EN, temp);
+			/* Schedule a NAPI poll */
+			__netif_rx_schedule(dev, &pdata->napi);
+		} else {
+			SMSC_WARNING(RX_ERR,
+				"netif_rx_schedule_prep failed");
+		}
+		serviced = IRQ_HANDLED;
+	}
+
+	return serviced;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+void smsc911x_poll_controller(struct net_device *dev)
+{
+	disable_irq(dev->irq);
+	smsc911x_irqhandler(0, dev);
+	enable_irq(dev->irq);
+}
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+/* Standard ioctls for mii-tool */
+static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	if (!netif_running(dev) || !pdata->phy_dev)
+		return -EINVAL;
+
+	return phy_mii_ioctl(pdata->phy_dev, if_mii(ifr), cmd);
+}
+
+static int
+smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return phy_ethtool_gset(pdata->phy_dev, cmd);
+}
+
+static int
+smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return phy_ethtool_sset(pdata->phy_dev, cmd);
+}
+
+static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
+					struct ethtool_drvinfo *info)
+{
+	strlcpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver));
+	strlcpy(info->version, SMSC_DRV_VERSION, sizeof(info->version));
+	strlcpy(info->bus_info, dev->dev.parent->bus_id,
+		sizeof(info->bus_info));
+}
+
+static int smsc911x_ethtool_nwayreset(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	return phy_start_aneg(pdata->phy_dev);
+}
+
+static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	return pdata->msg_enable;
+}
+
+static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	pdata->msg_enable = level;
+}
+
+static int smsc911x_ethtool_getregslen(struct net_device *dev)
+{
+	return (((E2P_DATA - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) *
+	    sizeof(u32);
+}
+
+static void
+smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs,
+			 void *buf)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	struct phy_device *phy_dev = pdata->phy_dev;
+	unsigned long flags;
+	unsigned int i;
+	unsigned int j = 0;
+	u32 *data = buf;
+
+	regs->version = pdata->idrev;
+	for (i = ID_REV; i <= E2P_DATA; i += (sizeof(u32)))
+		data[j++] = smsc911x_reg_read(pdata, i);
+
+	for (i = MAC_CR; i <= WUCSR; i++) {
+		spin_lock_irqsave(&pdata->mac_lock, flags);
+		data[j++] = smsc911x_mac_read(pdata, i);
+		spin_unlock_irqrestore(&pdata->mac_lock, flags);
+	}
+
+	for (i = 0; i <= 31; i++)
+		data[j++] = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, i);
+}
+
+static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata)
+{
+	unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG);
+	temp &= ~GPIO_CFG_EEPR_EN_;
+	smsc911x_reg_write(pdata, GPIO_CFG, temp);
+	msleep(1);
+}
+
+static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
+{
+	int timeout = 100;
+	u32 e2cmd;
+
+	SMSC_TRACE(DRV, "op 0x%08x", op);
+	if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) {
+		SMSC_WARNING(DRV, "Busy at start");
+		return -EBUSY;
+	}
+
+	e2cmd = op | E2P_CMD_EPC_BUSY_;
+	smsc911x_reg_write(pdata, E2P_CMD, e2cmd);
+
+	do {
+		msleep(1);
+		e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
+	} while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+
+	if (!timeout) {
+		SMSC_TRACE(DRV, "TIMED OUT");
+		return -EAGAIN;
+	}
+
+	if (e2cmd & E2P_CMD_EPC_TIMEOUT_) {
+		SMSC_TRACE(DRV, "Error occured during eeprom operation");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata,
+					 u8 address, u8 *data)
+{
+	u32 op = E2P_CMD_EPC_CMD_READ_ | address;
+	int ret;
+
+	SMSC_TRACE(DRV, "address 0x%x", address);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret)
+		data[address] = smsc911x_reg_read(pdata, E2P_DATA);
+
+	return ret;
+}
+
+static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
+					  u8 address, u8 data)
+{
+	u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+	int ret;
+
+	SMSC_TRACE(DRV, "address 0x%x, data 0x%x", address, data);
+	ret = smsc911x_eeprom_send_cmd(pdata, op);
+
+	if (!ret) {
+		op = E2P_CMD_EPC_CMD_WRITE_ | address;
+		smsc911x_reg_write(pdata, E2P_DATA, (u32)data);
+		ret = smsc911x_eeprom_send_cmd(pdata, op);
+	}
+
+	return ret;
+}
+
+static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev)
+{
+	return SMSC911X_EEPROM_SIZE;
+}
+
+static int smsc911x_ethtool_get_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	u8 eeprom_data[SMSC911X_EEPROM_SIZE];
+	int len;
+	int i;
+
+	smsc911x_eeprom_enable_access(pdata);
+
+	len = min(eeprom->len, SMSC911X_EEPROM_SIZE);
+	for (i = 0; i < len; i++) {
+		int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data);
+		if (ret < 0) {
+			eeprom->len = 0;
+			return ret;
+		}
+	}
+
+	memcpy(data, &eeprom_data[eeprom->offset], len);
+	eeprom->len = len;
+	return 0;
+}
+
+static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
+				       struct ethtool_eeprom *eeprom, u8 *data)
+{
+	int ret;
+	struct smsc911x_data *pdata = netdev_priv(dev);
+
+	smsc911x_eeprom_enable_access(pdata);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_);
+	ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data);
+	smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_);
+
+	/* Single byte write, according to man page */
+	eeprom->len = 1;
+
+	return ret;
+}
+
+static struct ethtool_ops smsc911x_ethtool_ops = {
+	.get_settings = smsc911x_ethtool_getsettings,
+	.set_settings = smsc911x_ethtool_setsettings,
+	.get_link = ethtool_op_get_link,
+	.get_drvinfo = smsc911x_ethtool_getdrvinfo,
+	.nway_reset = smsc911x_ethtool_nwayreset,
+	.get_msglevel = smsc911x_ethtool_getmsglevel,
+	.set_msglevel = smsc911x_ethtool_setmsglevel,
+	.get_regs_len = smsc911x_ethtool_getregslen,
+	.get_regs = smsc911x_ethtool_getregs,
+	.get_eeprom_len = smsc911x_ethtool_get_eeprom_len,
+	.get_eeprom = smsc911x_ethtool_get_eeprom,
+	.set_eeprom = smsc911x_ethtool_set_eeprom,
+};
+
+/* Initializing private device structures, only called from probe */
+static int __devinit smsc911x_init(struct net_device *dev)
+{
+	struct smsc911x_data *pdata = netdev_priv(dev);
+	unsigned int byte_test;
+
+	SMSC_TRACE(PROBE, "Driver Parameters:");
+	SMSC_TRACE(PROBE, "LAN base: 0x%08lX",
+		(unsigned long)pdata->ioaddr);
+	SMSC_TRACE(PROBE, "IRQ: %d", dev->irq);
+	SMSC_TRACE(PROBE, "PHY will be autodetected.");
+
+#if (!SMSC_CAN_USE_32BIT)
+	spin_lock_init(&pdata->dev_lock);
+#endif
+
+	if (pdata->ioaddr == 0) {
+		SMSC_WARNING(PROBE, "pdata->ioaddr: 0x00000000");
+		return -ENODEV;
+	}
+
+	/* Check byte ordering */
+	byte_test = smsc911x_reg_read(pdata, BYTE_TEST);
+	SMSC_TRACE(PROBE, "BYTE_TEST: 0x%08X", byte_test);
+	if (byte_test == 0x43218765) {
+		SMSC_TRACE(PROBE, "BYTE_TEST looks swapped, "
+			"applying WORD_SWAP");
+		smsc911x_reg_write(pdata, WORD_SWAP, 0xffffffff);
+
+		/* 1 dummy read of BYTE_TEST is needed after a write to
+		 * WORD_SWAP before its contents are valid */
+		byte_test = smsc911x_reg_read(pdata, BYTE_TEST);
+
+		byte_test = smsc911x_reg_read(pdata, BYTE_TEST);
+	}
+
+	if (byte_test != 0x87654321) {
+		SMSC_WARNING(DRV, "BYTE_TEST: 0x%08X", byte_test);
+		if (((byte_test >> 16) & 0xFFFF) == (byte_test & 0xFFFF)) {
+			SMSC_WARNING(PROBE,
+				"top 16 bits equal to bottom 16 bits");
+			SMSC_TRACE(PROBE, "This may mean the chip is set "
+				"for 32 bit while the bus is reading 16 bit");
+		}
+		return -ENODEV;
+	}
+
+	/* Default generation to zero (all workarounds apply) */
+	pdata->generation = 0;
+
+	pdata->idrev = smsc911x_reg_read(pdata, ID_REV);
+	switch (pdata->idrev & 0xFFFF0000) {
+	case 0x01180000:
+	case 0x01170000:
+	case 0x01160000:
+	case 0x01150000:
+		/* LAN911[5678] family */
+		pdata->generation = pdata->idrev & 0x0000FFFF;
+		break;
+
+	case 0x118A0000:
+	case 0x117A0000:
+	case 0x116A0000:
+	case 0x115A0000:
+		/* LAN921[5678] family */
+		pdata->generation = 3;
+		break;
+
+	case 0x92100000:
+	case 0x92110000:
+	case 0x92200000:
+	case 0x92210000:
+		/* LAN9210/LAN9211/LAN9220/LAN9221 */
+		pdata->generation = 4;
+		break;
+
+	default:
+		SMSC_WARNING(PROBE, "LAN911x not identified, idrev: 0x%08X",
+			pdata->idrev);
+		return -ENODEV;
+	}
+
+	SMSC_TRACE(PROBE, "LAN911x identified, idrev: 0x%08X, generation: %d",
+		pdata->idrev, pdata->generation);
+
+	if (pdata->generation == 0)
+		SMSC_WARNING(PROBE,
+			"This driver is not intended for this chip revision");
+
+	/* Reset the LAN911x */
+	if (smsc911x_soft_reset(pdata))
+		return -ENODEV;
+
+	/* Disable all interrupt sources until we bring the device up */
+	smsc911x_reg_write(pdata, INT_EN, 0);
+
+	ether_setup(dev);
+	dev->open = smsc911x_open;
+	dev->stop = smsc911x_stop;
+	dev->hard_start_xmit = smsc911x_hard_start_xmit;
+	dev->get_stats = smsc911x_get_stats;
+	dev->set_multicast_list = smsc911x_set_multicast_list;
+	dev->flags |= IFF_MULTICAST;
+	dev->do_ioctl = smsc911x_do_ioctl;
+	netif_napi_add(dev, &pdata->napi, smsc911x_poll, SMSC_NAPI_WEIGHT);
+	dev->ethtool_ops = &smsc911x_ethtool_ops;
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	dev->poll_controller = smsc911x_poll_controller;
+#endif				/* CONFIG_NET_POLL_CONTROLLER */
+
+	return 0;
+}
+
+static int __devexit smsc911x_drv_remove(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+
+	dev = platform_get_drvdata(pdev);
+	BUG_ON(!dev);
+	pdata = netdev_priv(dev);
+	BUG_ON(!pdata);
+	BUG_ON(!pdata->ioaddr);
+	BUG_ON(!pdata->phy_dev);
+
+	SMSC_TRACE(IFDOWN, "Stopping driver.");
+
+	phy_disconnect(pdata->phy_dev);
+	pdata->phy_dev = NULL;
+	mdiobus_unregister(pdata->mii_bus);
+	mdiobus_free(pdata->mii_bus);
+
+	platform_set_drvdata(pdev, NULL);
+	unregister_netdev(dev);
+	free_irq(dev->irq, dev);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	release_mem_region(res->start, res->end - res->start);
+
+	iounmap(pdata->ioaddr);
+
+	free_netdev(dev);
+
+	return 0;
+}
+
+static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct smsc911x_data *pdata;
+	struct resource *res;
+	unsigned int intcfg = 0;
+	int res_size;
+	int retval;
+	DECLARE_MAC_BUF(mac);
+
+	pr_info("%s: Driver version %s.\n", SMSC_CHIPNAME, SMSC_DRV_VERSION);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "smsc911x-memory");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		pr_warning("%s: Could not allocate resource.\n",
+			SMSC_CHIPNAME);
+		retval = -ENODEV;
+		goto out_0;
+	}
+	res_size = res->end - res->start;
+
+	if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
+		retval = -EBUSY;
+		goto out_0;
+	}
+
+	dev = alloc_etherdev(sizeof(struct smsc911x_data));
+	if (!dev) {
+		pr_warning("%s: Could not allocate device.\n", SMSC_CHIPNAME);
+		retval = -ENOMEM;
+		goto out_release_io_1;
+	}
+
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	pdata = netdev_priv(dev);
+
+	dev->irq = platform_get_irq(pdev, 0);
+	pdata->ioaddr = ioremap_nocache(res->start, res_size);
+
+	/* copy config parameters across if present, otherwise pdata
+	 * defaults to zeros */
+	if (pdev->dev.platform_data) {
+		struct smsc911x_platform_config *config =
+			pdev->dev.platform_data;
+		pdata->irq_polarity = config->irq_polarity;
+		pdata->irq_type  = config->irq_type;
+		pdata->phy_interface = config->phy_interface;
+	}
+
+	pdata->dev = dev;
+	pdata->msg_enable = ((1 << debug) - 1);
+
+	if (pdata->ioaddr == NULL) {
+		SMSC_WARNING(PROBE,
+			"Error smsc911x base address invalid");
+		retval = -ENOMEM;
+		goto out_free_netdev_2;
+	}
+
+	retval = smsc911x_init(dev);
+	if (retval < 0)
+		goto out_unmap_io_3;
+
+	/* configure irq polarity and type before connecting isr */
+	if (pdata->irq_polarity == SMSC911X_IRQ_POLARITY_ACTIVE_HIGH)
+		intcfg |= INT_CFG_IRQ_POL_;
+
+	if (pdata->irq_type == SMSC911X_IRQ_TYPE_PUSH_PULL)
+		intcfg |= INT_CFG_IRQ_TYPE_;
+
+	smsc911x_reg_write(pdata, INT_CFG, intcfg);
+
+	/* Ensure interrupts are globally disabled before connecting ISR */
+	smsc911x_reg_write(pdata, INT_EN, 0);
+	smsc911x_reg_write(pdata, INT_STS, 0xFFFFFFFF);
+
+	retval = request_irq(dev->irq, smsc911x_irqhandler, IRQF_DISABLED,
+			     SMSC_CHIPNAME, dev);
+	if (retval) {
+		SMSC_WARNING(PROBE,
+			"Unable to claim requested irq: %d", dev->irq);
+		goto out_unmap_io_3;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	retval = register_netdev(dev);
+	if (retval) {
+		SMSC_WARNING(PROBE,
+			"Error %i registering device", retval);
+		goto out_unset_drvdata_4;
+	} else {
+		SMSC_TRACE(PROBE, "Network interface: \"%s\"", dev->name);
+	}
+
+	spin_lock_init(&pdata->mac_lock);
+
+	retval = smsc911x_mii_init(pdev, dev);
+	if (retval) {
+		SMSC_WARNING(PROBE,
+			"Error %i initialising mii", retval);
+		goto out_unregister_netdev_5;
+	}
+
+	spin_lock_irq(&pdata->mac_lock);
+
+	/* Check if mac address has been specified when bringing interface up */
+	if (is_valid_ether_addr(dev->dev_addr)) {
+		smsc911x_set_mac_address(pdata, dev->dev_addr);
+		SMSC_TRACE(PROBE, "MAC Address is specified by configuration");
+	} else {
+		/* Try reading mac address from device. if EEPROM is present
+		 * it will already have been set */
+		u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH);
+		u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL);
+		dev->dev_addr[0] = (u8)(mac_low32);
+		dev->dev_addr[1] = (u8)(mac_low32 >> 8);
+		dev->dev_addr[2] = (u8)(mac_low32 >> 16);
+		dev->dev_addr[3] = (u8)(mac_low32 >> 24);
+		dev->dev_addr[4] = (u8)(mac_high16);
+		dev->dev_addr[5] = (u8)(mac_high16 >> 8);
+
+		if (is_valid_ether_addr(dev->dev_addr)) {
+			/* eeprom values are valid  so use them */
+			SMSC_TRACE(PROBE,
+				"Mac Address is read from LAN911x EEPROM");
+		} else {
+			/* eeprom values are invalid, generate random MAC */
+			random_ether_addr(dev->dev_addr);
+			smsc911x_set_mac_address(pdata, dev->dev_addr);
+			SMSC_TRACE(PROBE,
+				"MAC Address is set to random_ether_addr");
+		}
+	}
+
+	spin_unlock_irq(&pdata->mac_lock);
+
+	dev_info(&dev->dev, "MAC Address: %s\n",
+		 print_mac(mac, dev->dev_addr));
+
+	return 0;
+
+out_unregister_netdev_5:
+	unregister_netdev(dev);
+out_unset_drvdata_4:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(dev->irq, dev);
+out_unmap_io_3:
+	iounmap(pdata->ioaddr);
+out_free_netdev_2:
+	free_netdev(dev);
+out_release_io_1:
+	release_mem_region(res->start, res->end - res->start);
+out_0:
+	return retval;
+}
+
+static struct platform_driver smsc911x_driver = {
+	.probe = smsc911x_drv_probe,
+	.remove = smsc911x_drv_remove,
+	.driver = {
+		.name = SMSC_CHIPNAME,
+	},
+};
+
+/* Entry point for loading the module */
+static int __init smsc911x_init_module(void)
+{
+	return platform_driver_register(&smsc911x_driver);
+}
+
+/* entry point for unloading the module */
+static void __exit smsc911x_cleanup_module(void)
+{
+	platform_driver_unregister(&smsc911x_driver);
+}
+
+module_init(smsc911x_init_module);
+module_exit(smsc911x_cleanup_module);
diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h
new file mode 100644
index 0000000..feb36de
--- /dev/null
+++ b/drivers/net/smsc911x.h
@@ -0,0 +1,394 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************/
+#ifndef __SMSC911X_H__
+#define __SMSC911X_H__
+
+#define SMSC_CAN_USE_32BIT	1
+#define TX_FIFO_LOW_THRESHOLD	((u32)1600)
+#define SMSC911X_EEPROM_SIZE	((u32)7)
+#define USE_DEBUG		0
+
+/* This is the maximum number of packets to be received every
+ * NAPI poll */
+#define SMSC_NAPI_WEIGHT	16
+
+/* implements a PHY loopback test at initialisation time, to ensure a packet
+ * can be succesfully looped back */
+#define USE_PHY_WORK_AROUND
+
+#define DPRINTK(nlevel, klevel, fmt, args...) \
+	((void)((NETIF_MSG_##nlevel & pdata->msg_enable) && \
+	printk(KERN_##klevel "%s: %s: " fmt "\n", \
+	pdata->dev->name, __func__, ## args)))
+
+#if USE_DEBUG >= 1
+#define SMSC_WARNING(nlevel, fmt, args...) \
+	DPRINTK(nlevel, WARNING, fmt, ## args)
+#else
+#define SMSC_WARNING(nlevel, fmt, args...) \
+	({ do {} while (0); 0; })
+#endif
+
+#if USE_DEBUG >= 2
+#define SMSC_TRACE(nlevel, fmt, args...) \
+	DPRINTK(nlevel, INFO, fmt, ## args)
+#else
+#define SMSC_TRACE(nlevel, fmt, args...) \
+	({ do {} while (0); 0; })
+#endif
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+#define SMSC_ASSERT_MAC_LOCK(pdata) \
+		WARN_ON(!spin_is_locked(&pdata->mac_lock))
+#else
+#define SMSC_ASSERT_MAC_LOCK(pdata) do {} while (0)
+#endif				/* CONFIG_DEBUG_SPINLOCK */
+
+#define FLOW_CTRL_TX		(1)
+#define FLOW_CTRL_RX		(2)
+
+/* SMSC911x registers and bitfields */
+#define RX_DATA_FIFO			0x00
+
+#define TX_DATA_FIFO			0x20
+#define TX_CMD_A_ON_COMP_		0x80000000
+#define TX_CMD_A_BUF_END_ALGN_		0x03000000
+#define TX_CMD_A_4_BYTE_ALGN_		0x00000000
+#define TX_CMD_A_16_BYTE_ALGN_		0x01000000
+#define TX_CMD_A_32_BYTE_ALGN_		0x02000000
+#define TX_CMD_A_DATA_OFFSET_		0x001F0000
+#define TX_CMD_A_FIRST_SEG_		0x00002000
+#define TX_CMD_A_LAST_SEG_		0x00001000
+#define TX_CMD_A_BUF_SIZE_		0x000007FF
+#define TX_CMD_B_PKT_TAG_		0xFFFF0000
+#define TX_CMD_B_ADD_CRC_DISABLE_	0x00002000
+#define TX_CMD_B_DISABLE_PADDING_	0x00001000
+#define TX_CMD_B_PKT_BYTE_LENGTH_	0x000007FF
+
+#define RX_STATUS_FIFO			0x40
+#define RX_STS_ES_			0x00008000
+#define RX_STS_MCAST_			0x00000400
+
+#define RX_STATUS_FIFO_PEEK		0x44
+
+#define TX_STATUS_FIFO			0x48
+#define TX_STS_ES_			0x00008000
+
+#define TX_STATUS_FIFO_PEEK		0x4C
+
+#define ID_REV				0x50
+#define ID_REV_CHIP_ID_			0xFFFF0000
+#define ID_REV_REV_ID_			0x0000FFFF
+
+#define INT_CFG				0x54
+#define INT_CFG_INT_DEAS_		0xFF000000
+#define INT_CFG_INT_DEAS_CLR_		0x00004000
+#define INT_CFG_INT_DEAS_STS_		0x00002000
+#define INT_CFG_IRQ_INT_		0x00001000
+#define INT_CFG_IRQ_EN_			0x00000100
+#define INT_CFG_IRQ_POL_		0x00000010
+#define INT_CFG_IRQ_TYPE_		0x00000001
+
+#define INT_STS				0x58
+#define INT_STS_SW_INT_			0x80000000
+#define INT_STS_TXSTOP_INT_		0x02000000
+#define INT_STS_RXSTOP_INT_		0x01000000
+#define INT_STS_RXDFH_INT_		0x00800000
+#define INT_STS_RXDF_INT_		0x00400000
+#define INT_STS_TX_IOC_			0x00200000
+#define INT_STS_RXD_INT_		0x00100000
+#define INT_STS_GPT_INT_		0x00080000
+#define INT_STS_PHY_INT_		0x00040000
+#define INT_STS_PME_INT_		0x00020000
+#define INT_STS_TXSO_			0x00010000
+#define INT_STS_RWT_			0x00008000
+#define INT_STS_RXE_			0x00004000
+#define INT_STS_TXE_			0x00002000
+#define INT_STS_TDFU_			0x00000800
+#define INT_STS_TDFO_			0x00000400
+#define INT_STS_TDFA_			0x00000200
+#define INT_STS_TSFF_			0x00000100
+#define INT_STS_TSFL_			0x00000080
+#define INT_STS_RXDF_			0x00000040
+#define INT_STS_RDFL_			0x00000020
+#define INT_STS_RSFF_			0x00000010
+#define INT_STS_RSFL_			0x00000008
+#define INT_STS_GPIO2_INT_		0x00000004
+#define INT_STS_GPIO1_INT_		0x00000002
+#define INT_STS_GPIO0_INT_		0x00000001
+
+#define INT_EN				0x5C
+#define INT_EN_SW_INT_EN_		0x80000000
+#define INT_EN_TXSTOP_INT_EN_		0x02000000
+#define INT_EN_RXSTOP_INT_EN_		0x01000000
+#define INT_EN_RXDFH_INT_EN_		0x00800000
+#define INT_EN_TIOC_INT_EN_		0x00200000
+#define INT_EN_RXD_INT_EN_		0x00100000
+#define INT_EN_GPT_INT_EN_		0x00080000
+#define INT_EN_PHY_INT_EN_		0x00040000
+#define INT_EN_PME_INT_EN_		0x00020000
+#define INT_EN_TXSO_EN_			0x00010000
+#define INT_EN_RWT_EN_			0x00008000
+#define INT_EN_RXE_EN_			0x00004000
+#define INT_EN_TXE_EN_			0x00002000
+#define INT_EN_TDFU_EN_			0x00000800
+#define INT_EN_TDFO_EN_			0x00000400
+#define INT_EN_TDFA_EN_			0x00000200
+#define INT_EN_TSFF_EN_			0x00000100
+#define INT_EN_TSFL_EN_			0x00000080
+#define INT_EN_RXDF_EN_			0x00000040
+#define INT_EN_RDFL_EN_			0x00000020
+#define INT_EN_RSFF_EN_			0x00000010
+#define INT_EN_RSFL_EN_			0x00000008
+#define INT_EN_GPIO2_INT_		0x00000004
+#define INT_EN_GPIO1_INT_		0x00000002
+#define INT_EN_GPIO0_INT_		0x00000001
+
+#define BYTE_TEST			0x64
+
+#define FIFO_INT			0x68
+#define FIFO_INT_TX_AVAIL_LEVEL_	0xFF000000
+#define FIFO_INT_TX_STS_LEVEL_		0x00FF0000
+#define FIFO_INT_RX_AVAIL_LEVEL_	0x0000FF00
+#define FIFO_INT_RX_STS_LEVEL_		0x000000FF
+
+#define RX_CFG				0x6C
+#define RX_CFG_RX_END_ALGN_		0xC0000000
+#define RX_CFG_RX_END_ALGN4_		0x00000000
+#define RX_CFG_RX_END_ALGN16_		0x40000000
+#define RX_CFG_RX_END_ALGN32_		0x80000000
+#define RX_CFG_RX_DMA_CNT_		0x0FFF0000
+#define RX_CFG_RX_DUMP_			0x00008000
+#define RX_CFG_RXDOFF_			0x00001F00
+
+#define TX_CFG				0x70
+#define TX_CFG_TXS_DUMP_		0x00008000
+#define TX_CFG_TXD_DUMP_		0x00004000
+#define TX_CFG_TXSAO_			0x00000004
+#define TX_CFG_TX_ON_			0x00000002
+#define TX_CFG_STOP_TX_			0x00000001
+
+#define HW_CFG				0x74
+#define HW_CFG_TTM_			0x00200000
+#define HW_CFG_SF_			0x00100000
+#define HW_CFG_TX_FIF_SZ_		0x000F0000
+#define HW_CFG_TR_			0x00003000
+#define HW_CFG_SRST_			0x00000001
+
+/* only available on 115/117 */
+#define HW_CFG_PHY_CLK_SEL_		0x00000060
+#define HW_CFG_PHY_CLK_SEL_INT_PHY_	0x00000000
+#define HW_CFG_PHY_CLK_SEL_EXT_PHY_	0x00000020
+#define HW_CFG_PHY_CLK_SEL_CLK_DIS_	0x00000040
+#define HW_CFG_SMI_SEL_		 	0x00000010
+#define HW_CFG_EXT_PHY_DET_		0x00000008
+#define HW_CFG_EXT_PHY_EN_		0x00000004
+#define HW_CFG_SRST_TO_			0x00000002
+
+/* only available  on 116/118 */
+#define HW_CFG_32_16_BIT_MODE_		0x00000004
+
+#define RX_DP_CTRL			0x78
+#define RX_DP_CTRL_RX_FFWD_		0x80000000
+
+#define RX_FIFO_INF			0x7C
+#define RX_FIFO_INF_RXSUSED_		0x00FF0000
+#define RX_FIFO_INF_RXDUSED_		0x0000FFFF
+
+#define TX_FIFO_INF			0x80
+#define TX_FIFO_INF_TSUSED_		0x00FF0000
+#define TX_FIFO_INF_TDFREE_		0x0000FFFF
+
+#define PMT_CTRL			0x84
+#define PMT_CTRL_PM_MODE_		0x00003000
+#define PMT_CTRL_PM_MODE_D0_		0x00000000
+#define PMT_CTRL_PM_MODE_D1_		0x00001000
+#define PMT_CTRL_PM_MODE_D2_		0x00002000
+#define PMT_CTRL_PM_MODE_D3_		0x00003000
+#define PMT_CTRL_PHY_RST_		0x00000400
+#define PMT_CTRL_WOL_EN_		0x00000200
+#define PMT_CTRL_ED_EN_			0x00000100
+#define PMT_CTRL_PME_TYPE_		0x00000040
+#define PMT_CTRL_WUPS_			0x00000030
+#define PMT_CTRL_WUPS_NOWAKE_		0x00000000
+#define PMT_CTRL_WUPS_ED_		0x00000010
+#define PMT_CTRL_WUPS_WOL_		0x00000020
+#define PMT_CTRL_WUPS_MULTI_		0x00000030
+#define PMT_CTRL_PME_IND_		0x00000008
+#define PMT_CTRL_PME_POL_		0x00000004
+#define PMT_CTRL_PME_EN_		0x00000002
+#define PMT_CTRL_READY_			0x00000001
+
+#define GPIO_CFG			0x88
+#define GPIO_CFG_LED3_EN_		0x40000000
+#define GPIO_CFG_LED2_EN_		0x20000000
+#define GPIO_CFG_LED1_EN_		0x10000000
+#define GPIO_CFG_GPIO2_INT_POL_		0x04000000
+#define GPIO_CFG_GPIO1_INT_POL_		0x02000000
+#define GPIO_CFG_GPIO0_INT_POL_		0x01000000
+#define GPIO_CFG_EEPR_EN_		0x00700000
+#define GPIO_CFG_GPIOBUF2_		0x00040000
+#define GPIO_CFG_GPIOBUF1_		0x00020000
+#define GPIO_CFG_GPIOBUF0_		0x00010000
+#define GPIO_CFG_GPIODIR2_		0x00000400
+#define GPIO_CFG_GPIODIR1_		0x00000200
+#define GPIO_CFG_GPIODIR0_		0x00000100
+#define GPIO_CFG_GPIOD4_		0x00000020
+#define GPIO_CFG_GPIOD3_		0x00000010
+#define GPIO_CFG_GPIOD2_		0x00000004
+#define GPIO_CFG_GPIOD1_		0x00000002
+#define GPIO_CFG_GPIOD0_		0x00000001
+
+#define GPT_CFG				0x8C
+#define GPT_CFG_TIMER_EN_		0x20000000
+#define GPT_CFG_GPT_LOAD_		0x0000FFFF
+
+#define GPT_CNT				0x90
+#define GPT_CNT_GPT_CNT_		0x0000FFFF
+
+#define WORD_SWAP			0x98
+
+#define FREE_RUN			0x9C
+
+#define RX_DROP				0xA0
+
+#define MAC_CSR_CMD			0xA4
+#define MAC_CSR_CMD_CSR_BUSY_		0x80000000
+#define MAC_CSR_CMD_R_NOT_W_		0x40000000
+#define MAC_CSR_CMD_CSR_ADDR_		0x000000FF
+
+#define MAC_CSR_DATA			0xA8
+
+#define AFC_CFG				0xAC
+#define AFC_CFG_AFC_HI_			0x00FF0000
+#define AFC_CFG_AFC_LO_			0x0000FF00
+#define AFC_CFG_BACK_DUR_		0x000000F0
+#define AFC_CFG_FCMULT_			0x00000008
+#define AFC_CFG_FCBRD_			0x00000004
+#define AFC_CFG_FCADD_			0x00000002
+#define AFC_CFG_FCANY_			0x00000001
+
+#define E2P_CMD				0xB0
+#define E2P_CMD_EPC_BUSY_		0x80000000
+#define E2P_CMD_EPC_CMD_		0x70000000
+#define E2P_CMD_EPC_CMD_READ_		0x00000000
+#define E2P_CMD_EPC_CMD_EWDS_		0x10000000
+#define E2P_CMD_EPC_CMD_EWEN_		0x20000000
+#define E2P_CMD_EPC_CMD_WRITE_		0x30000000
+#define E2P_CMD_EPC_CMD_WRAL_		0x40000000
+#define E2P_CMD_EPC_CMD_ERASE_		0x50000000
+#define E2P_CMD_EPC_CMD_ERAL_		0x60000000
+#define E2P_CMD_EPC_CMD_RELOAD_		0x70000000
+#define E2P_CMD_EPC_TIMEOUT_		0x00000200
+#define E2P_CMD_MAC_ADDR_LOADED_	0x00000100
+#define E2P_CMD_EPC_ADDR_		0x000000FF
+
+#define E2P_DATA			0xB4
+#define E2P_DATA_EEPROM_DATA_		0x000000FF
+#define LAN_REGISTER_EXTENT		0x00000100
+
+/*
+ * MAC Control and Status Register (Indirect Address)
+ * Offset (through the MAC_CSR CMD and DATA port)
+ */
+#define MAC_CR				0x01
+#define MAC_CR_RXALL_			0x80000000
+#define MAC_CR_HBDIS_			0x10000000
+#define MAC_CR_RCVOWN_			0x00800000
+#define MAC_CR_LOOPBK_			0x00200000
+#define MAC_CR_FDPX_			0x00100000
+#define MAC_CR_MCPAS_			0x00080000
+#define MAC_CR_PRMS_			0x00040000
+#define MAC_CR_INVFILT_			0x00020000
+#define MAC_CR_PASSBAD_			0x00010000
+#define MAC_CR_HFILT_			0x00008000
+#define MAC_CR_HPFILT_			0x00002000
+#define MAC_CR_LCOLL_			0x00001000
+#define MAC_CR_BCAST_			0x00000800
+#define MAC_CR_DISRTY_			0x00000400
+#define MAC_CR_PADSTR_			0x00000100
+#define MAC_CR_BOLMT_MASK_		0x000000C0
+#define MAC_CR_DFCHK_			0x00000020
+#define MAC_CR_TXEN_			0x00000008
+#define MAC_CR_RXEN_			0x00000004
+
+#define ADDRH				0x02
+
+#define ADDRL				0x03
+
+#define HASHH				0x04
+
+#define HASHL				0x05
+
+#define MII_ACC				0x06
+#define MII_ACC_PHY_ADDR_		0x0000F800
+#define MII_ACC_MIIRINDA_		0x000007C0
+#define MII_ACC_MII_WRITE_		0x00000002
+#define MII_ACC_MII_BUSY_		0x00000001
+
+#define MII_DATA			0x07
+
+#define FLOW				0x08
+#define FLOW_FCPT_			0xFFFF0000
+#define FLOW_FCPASS_			0x00000004
+#define FLOW_FCEN_			0x00000002
+#define FLOW_FCBSY_			0x00000001
+
+#define VLAN1				0x09
+
+#define VLAN2				0x0A
+
+#define WUFF				0x0B
+
+#define WUCSR				0x0C
+#define WUCSR_GUE_			0x00000200
+#define WUCSR_WUFR_			0x00000040
+#define WUCSR_MPR_			0x00000020
+#define WUCSR_WAKE_EN_			0x00000004
+#define WUCSR_MPEN_			0x00000002
+
+/*
+ * Phy definitions (vendor-specific)
+ */
+#define LAN9118_PHY_ID			0x00C0001C
+
+#define MII_INTSTS			0x1D
+
+#define MII_INTMSK			0x1E
+#define PHY_INTMSK_AN_RCV_		(1 << 1)
+#define PHY_INTMSK_PDFAULT_		(1 << 2)
+#define PHY_INTMSK_AN_ACK_		(1 << 3)
+#define PHY_INTMSK_LNKDOWN_		(1 << 4)
+#define PHY_INTMSK_RFAULT_		(1 << 5)
+#define PHY_INTMSK_AN_COMP_		(1 << 6)
+#define PHY_INTMSK_ENERGYON_		(1 << 7)
+#define PHY_INTMSK_DEFAULT_		(PHY_INTMSK_ENERGYON_ | \
+					 PHY_INTMSK_AN_COMP_ | \
+					 PHY_INTMSK_RFAULT_ | \
+					 PHY_INTMSK_LNKDOWN_)
+
+#define ADVERTISE_PAUSE_ALL		(ADVERTISE_PAUSE_CAP | \
+					 ADVERTISE_PAUSE_ASYM)
+
+#define LPA_PAUSE_ALL			(LPA_PAUSE_CAP | \
+					 LPA_PAUSE_ASYM)
+
+#endif				/* __SMSC911X_H__ */
diff --git a/include/linux/smsc911x.h b/include/linux/smsc911x.h
new file mode 100644
index 0000000..47c4ffd
--- /dev/null
+++ b/include/linux/smsc911x.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+ *
+ * Copyright (C) 2004-2008 SMSC
+ * Copyright (C) 2005-2008 ARM
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ ***************************************************************************/
+#ifndef __LINUX_SMSC911X_H__
+#define __LINUX_SMSC911X_H__
+
+#include <linux/phy.h>
+
+/* platform_device configuration data, should be assigned to
+ * the platform_device's dev.platform_data */
+struct smsc911x_platform_config {
+	unsigned int irq_polarity;
+	unsigned int irq_type;
+	phy_interface_t phy_interface;
+};
+
+/* Constants for platform_device irq polarity configuration */
+#define SMSC911X_IRQ_POLARITY_ACTIVE_LOW	0
+#define SMSC911X_IRQ_POLARITY_ACTIVE_HIGH	1
+
+/* Constants for platform_device irq type configuration */
+#define SMSC911X_IRQ_TYPE_OPEN_DRAIN		0
+#define SMSC911X_IRQ_TYPE_PUSH_PULL		1
+
+#endif /* __LINUX_SMSC911X_H__ */
-- 
1.5.5.1


^ permalink raw reply related	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2008-10-21 13:00 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-07-16 18:54 [PATCH] SMSC LAN911x and LAN921x vendor driver Steve Glendinning
2007-07-18 22:38 ` Jeff Garzik
2007-07-20 16:22   ` Steve.Glendinning
2007-07-29 20:53 ` Peter Korsgaard
2007-07-30 18:31   ` Steve.Glendinning
2007-08-01 22:27     ` Peter Korsgaard
2007-08-07 23:09       ` Peter Korsgaard
  -- strict thread matches above, loose matches on Subject: below --
2008-10-21 13:00 Steve Glendinning
     [not found] <20080515170728.GA3176@PTXN0038.genpitfi01.og.ge.com>
2008-05-19 12:34 ` Steve Glendinning
2008-05-19 16:24   ` Catalin Marinas
2008-05-19 19:06     ` Steve.Glendinning
2008-06-02 10:45   ` Steve Glendinning
2008-06-02 15:54     ` Ben Hutchings
2008-06-02 16:09       ` Bill Gatliff
2008-06-02 18:32         ` Peter Korsgaard
2008-06-02 18:30       ` Steve.Glendinning
2008-06-02 19:03         ` Ben Hutchings
2008-06-04 16:34       ` Steve.Glendinning
2008-06-02 18:47   ` Peter Korsgaard
2006-12-30 16:34 Steve Glendinning
2007-01-04 14:42 ` Pierre TARDY
2006-12-04 22:31 Steve Glendinning
2006-07-28 21:38 Francois Romieu
2006-08-01 15:12 ` [PATCH] " Steve Glendinning
2006-08-01 15:33   ` John W. Linville
2006-08-02 19:23     ` Steve.Glendinning
2006-08-02 19:51       ` John W. Linville
2006-08-01 18:28   ` Scott Murray
2006-08-01 19:27     ` Steve.Glendinning
2006-08-01 23:51       ` Scott Murray
2006-08-03 15:26         ` Steve.Glendinning
2006-08-03 21:07           ` Scott Murray
2006-08-01 21:40   ` Francois Romieu
2006-08-02 19:39     ` Steve.Glendinning
2006-08-02 21:07       ` Francois Romieu
2006-08-04 11:29         ` Steve Glendinning

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).