b43: N-PHY: partly implement SPUR workaround
[safe/jmp/linux-2.6] / drivers / net / smsc911x.c
index fe51788..4d0d5c5 100644 (file)
 #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/swab.h>
 #include <linux/phy.h>
 #include <linux/smsc911x.h>
+#include <linux/device.h>
 #include "smsc911x.h"
 
 #define SMSC_CHIPNAME          "smsc911x"
@@ -77,19 +78,16 @@ struct smsc911x_data {
        unsigned int generation;
 
        /* device configuration (copied from platform_data during probe) */
-       unsigned int irq_polarity;
-       unsigned int irq_type;
-       phy_interface_t phy_interface;
+       struct smsc911x_platform_config config;
 
        /* 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 to ensure 16-bit accesses are serialised.
+        * unused with a 32-bit bus */
        spinlock_t dev_lock;
-#endif
 
        struct phy_device *phy_dev;
        struct mii_bus *mii_bus;
@@ -121,70 +119,57 @@ struct smsc911x_data {
        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
+/* The 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);
+       if (pdata->config.flags & SMSC911X_USE_32BIT)
+               return readl(pdata->ioaddr + reg);
+
+       if (pdata->config.flags & SMSC911X_USE_16BIT) {
+               u32 data;
+               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) */
+               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;
+       }
 
-       return data;
+       BUG();
+       return 0;
 }
 
 static inline void smsc911x_reg_write(struct smsc911x_data *pdata, u32 reg,
                                      u32 val)
 {
-       unsigned long flags;
+       if (pdata->config.flags & SMSC911X_USE_32BIT) {
+               writel(val, pdata->ioaddr + reg);
+               return;
+       }
+
+       if (pdata->config.flags & SMSC911X_USE_16BIT) {
+               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);
+               /* 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);
+               return;
+       }
+
+       BUG();
 }
 
 /* Writes a packet to the TX_DATA_FIFO */
@@ -192,8 +177,24 @@ 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++);
+       if (pdata->config.flags & SMSC911X_SWAP_FIFO) {
+               while (wordcount--)
+                       smsc911x_reg_write(pdata, TX_DATA_FIFO, swab32(*buf++));
+               return;
+       }
+
+       if (pdata->config.flags & SMSC911X_USE_32BIT) {
+               writesl(pdata->ioaddr + TX_DATA_FIFO, buf, wordcount);
+               return;
+       }
+
+       if (pdata->config.flags & SMSC911X_USE_16BIT) {
+               while (wordcount--)
+                       smsc911x_reg_write(pdata, TX_DATA_FIFO, *buf++);
+               return;
+       }
+
+       BUG();
 }
 
 /* Reads a packet out of the RX_DATA_FIFO */
@@ -201,11 +202,25 @@ 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);
-}
+       if (pdata->config.flags & SMSC911X_SWAP_FIFO) {
+               while (wordcount--)
+                       *buf++ = swab32(smsc911x_reg_read(pdata, RX_DATA_FIFO));
+               return;
+       }
 
-#endif                         /* SMSC_CAN_USE_32BIT */
+       if (pdata->config.flags & SMSC911X_USE_32BIT) {
+               readsl(pdata->ioaddr + RX_DATA_FIFO, buf, wordcount);
+               return;
+       }
+
+       if (pdata->config.flags & SMSC911X_USE_16BIT) {
+               while (wordcount--)
+                       *buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO);
+               return;
+       }
+
+       BUG();
+}
 
 /* waits for MAC not busy, with timeout.  Only called by smsc911x_mac_read
  * and smsc911x_mac_write, so assumes mac_lock is held */
@@ -316,7 +331,7 @@ static int smsc911x_mii_read(struct mii_bus *bus, int phyaddr, int regidx)
                        goto out;
                }
 
-       SMSC_WARNING(HW, "Timed out waiting for MII write to finish");
+       SMSC_WARNING(HW, "Timed out waiting for MII read to finish");
        reg = -EIO;
 
 out:
@@ -366,48 +381,53 @@ out:
        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)
+/* Switch to external phy. Assumes tx and rx are stopped. */
+static void smsc911x_phy_enable_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_) {
+       /* 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. Assuming tx and rx are stopped
-                * because smsc911x_phy_initialise is called before
-                * smsc911x_rx_initialise and tx_initialise. */
+       /* Switch to external phy */
+       hwcfg |= HW_CFG_EXT_PHY_EN_;
+       smsc911x_reg_write(pdata, HW_CFG, hwcfg);
 
-               /* 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 */
+       /* 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 */
 
-               /* 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);
+}
 
-               hwcfg |= HW_CFG_SMI_SEL_;
-               smsc911x_reg_write(pdata, HW_CFG, hwcfg);
+/* Autodetects and enables external phy if present on supported chips.
+ * autodetection can be overridden by specifying SMSC911X_FORCE_INTERNAL_PHY
+ * or SMSC911X_FORCE_EXTERNAL_PHY in the platform_data flags. */
+static void smsc911x_phy_initialise_external(struct smsc911x_data *pdata)
+{
+       unsigned int hwcfg = smsc911x_reg_read(pdata, HW_CFG);
 
-               SMSC_TRACE(HW, "Successfully switched to external PHY");
+       if (pdata->config.flags & SMSC911X_FORCE_INTERNAL_PHY) {
+               SMSC_TRACE(HW, "Forcing internal PHY");
+               pdata->using_extphy = 0;
+       } else if (pdata->config.flags & SMSC911X_FORCE_EXTERNAL_PHY) {
+               SMSC_TRACE(HW, "Forcing external PHY");
+               smsc911x_phy_enable_external(pdata);
+               pdata->using_extphy = 1;
+       } else if (hwcfg & HW_CFG_EXT_PHY_DET_) {
+               SMSC_TRACE(HW, "HW_CFG EXT_PHY_DET set, using external PHY");
+               smsc911x_phy_enable_external(pdata);
                pdata->using_extphy = 1;
        } else {
-               SMSC_WARNING(HW, "No external PHY detected, "
-                       "Using internal PHY instead.");
-               /* Use internal phy */
-               return -ENODEV;
+               SMSC_TRACE(HW, "HW_CFG EXT_PHY_DET clear, using internal PHY");
+               pdata->using_extphy = 0;
        }
-       return 0;
 }
 
 /* Fetches a tx status out of the status fifo */
@@ -641,28 +661,6 @@ static int smsc911x_phy_loopbacktest(struct net_device *dev)
 }
 #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;
@@ -673,7 +671,7 @@ static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata)
        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);
+               u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv);
 
                if (cap & FLOW_CTRL_RX)
                        flow = 0xFFFF0002;
@@ -750,8 +748,8 @@ static void smsc911x_phy_adjust_link(struct net_device *dev)
                         * 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)) {
+                       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;
@@ -789,8 +787,8 @@ static int smsc911x_mii_probe(struct net_device *dev)
                return -ENODEV;
        }
 
-       phydev = phy_connect(dev, phydev->dev.bus_id,
-               &smsc911x_phy_adjust_link, 0, pdata->phy_interface);
+       phydev = phy_connect(dev, dev_name(&phydev->dev),
+               &smsc911x_phy_adjust_link, 0, pdata->config.phy_interface);
 
        if (IS_ERR(phydev)) {
                pr_err("%s: Could not attach to PHY\n", dev->name);
@@ -798,7 +796,8 @@ static int smsc911x_mii_probe(struct net_device *dev)
        }
 
        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);
+               dev->name, phydev->drv->name,
+               dev_name(&phydev->dev), phydev->irq);
 
        /* mask with MAC supported features */
        phydev->supported &= (PHY_BASIC_FEATURES | SUPPORTED_Pause |
@@ -843,9 +842,6 @@ static int __devinit smsc911x_mii_init(struct platform_device *pdev,
                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:
@@ -853,14 +849,12 @@ static int __devinit smsc911x_mii_init(struct platform_device *pdev,
        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");
-               }
+               smsc911x_phy_initialise_external(pdata);
                break;
        default:
                SMSC_TRACE(HW, "External PHY is not supported, "
                        "using internal PHY");
+               pdata->using_extphy = 0;
                break;
        }
 
@@ -914,22 +908,22 @@ static void smsc911x_tx_update_txcounters(struct net_device *dev)
                        SMSC_WARNING(HW,
                                "Packet tag reserved bit is high");
                } else {
-                       if (unlikely(tx_stat & 0x00008000)) {
+                       if (unlikely(tx_stat & TX_STS_ES_)) {
                                dev->stats.tx_errors++;
                        } else {
                                dev->stats.tx_packets++;
                                dev->stats.tx_bytes += (tx_stat >> 16);
                        }
-                       if (unlikely(tx_stat & 0x00000100)) {
+                       if (unlikely(tx_stat & TX_STS_EXCESS_COL_)) {
                                dev->stats.collisions += 16;
                                dev->stats.tx_aborted_errors += 1;
                        } else {
                                dev->stats.collisions +=
                                    ((tx_stat >> 3) & 0xF);
                        }
-                       if (unlikely(tx_stat & 0x00000800))
+                       if (unlikely(tx_stat & TX_STS_LOST_CARRIER_))
                                dev->stats.tx_carrier_errors += 1;
-                       if (unlikely(tx_stat & 0x00000200)) {
+                       if (unlikely(tx_stat & TX_STS_LATE_COL_)) {
                                dev->stats.collisions++;
                                dev->stats.tx_aborted_errors++;
                        }
@@ -943,19 +937,17 @@ smsc911x_rx_counterrors(struct net_device *dev, unsigned int rxstat)
 {
        int crc_err = 0;
 
-       if (unlikely(rxstat & 0x00008000)) {
+       if (unlikely(rxstat & RX_STS_ES_)) {
                dev->stats.rx_errors++;
-               if (unlikely(rxstat & 0x00000002)) {
+               if (unlikely(rxstat & RX_STS_CRC_ERR_)) {
                        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 */
+               if (unlikely((rxstat & RX_STS_FRAME_TYPE_) &&
+                            (rxstat & RX_STS_LENGTH_ERR_)))
                        dev->stats.rx_length_errors++;
-               }
                if (rxstat & RX_STS_MCAST_)
                        dev->stats.multicast++;
        }
@@ -974,7 +966,7 @@ smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes)
                do {
                        udelay(1);
                        val = smsc911x_reg_read(pdata, RX_DP_CTRL);
-               } while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_));
+               } while ((val & RX_DP_CTRL_RX_FFWD_) && --timeout);
 
                if (unlikely(timeout == 0))
                        SMSC_WARNING(HW, "Timed out waiting for "
@@ -994,7 +986,7 @@ static int smsc911x_poll(struct napi_struct *napi, int budget)
        struct net_device *dev = pdata->dev;
        int npackets = 0;
 
-       while (likely(netif_running(dev)) && (npackets < budget)) {
+       while (npackets < budget) {
                unsigned int pktlength;
                unsigned int pktwords;
                struct sk_buff *skb;
@@ -1005,7 +997,7 @@ static int smsc911x_poll(struct napi_struct *napi, int budget)
                        /* 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);
+                       napi_complete(napi);
                        temp = smsc911x_reg_read(pdata, INT_EN);
                        temp |= INT_EN_RSFL_EN_;
                        smsc911x_reg_write(pdata, INT_EN, temp);
@@ -1055,7 +1047,6 @@ static int smsc911x_poll(struct napi_struct *napi, int budget)
                /* Update counters */
                dev->stats.rx_packets++;
                dev->stats.rx_bytes += (pktlength - 4);
-               dev->last_rx = jiffies;
        }
 
        /* Return total received packets */
@@ -1140,7 +1131,7 @@ static int smsc911x_soft_reset(struct smsc911x_data *pdata)
 
 /* 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])
+smsc911x_set_hw_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) |
@@ -1181,8 +1172,8 @@ static int smsc911x_open(struct net_device *dev)
 
        /* Make sure EEPROM has finished loading before setting GPIO_CFG */
        timeout = 50;
-       while ((timeout--) &&
-              (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) {
+       while ((smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) &&
+              --timeout) {
                udelay(10);
        }
 
@@ -1195,7 +1186,7 @@ static int smsc911x_open(struct net_device *dev)
        /* 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);
+       smsc911x_set_hw_mac_address(pdata, dev->dev_addr);
        spin_unlock_irq(&pdata->mac_lock);
 
        /* Initialise irqs, but leave all sources disabled */
@@ -1205,14 +1196,14 @@ static int smsc911x_open(struct net_device *dev)
        /* Set interrupt deassertion to 100uS */
        intcfg = ((10 << 24) | INT_CFG_IRQ_EN_);
 
-       if (pdata->irq_polarity) {
+       if (pdata->config.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) {
+       if (pdata->config.irq_type) {
                SMSC_TRACE(IFUP, "irq type: push-pull");
                intcfg |= INT_CFG_IRQ_TYPE_;
        } else {
@@ -1246,6 +1237,10 @@ static int smsc911x_open(struct net_device *dev)
        dev_info(&dev->dev, "SMSC911x/921x identified at %#08lx, IRQ: %d\n",
                 (unsigned long)pdata->ioaddr, dev->irq);
 
+       /* Reset the last known duplex and carrier */
+       pdata->last_duplex = -1;
+       pdata->last_carrier = -1;
+
        /* Bring the PHY up */
        phy_start(pdata->phy_dev);
 
@@ -1267,7 +1262,7 @@ static int smsc911x_open(struct net_device *dev)
        napi_enable(&pdata->napi);
 
        temp = smsc911x_reg_read(pdata, INT_EN);
-       temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_);
+       temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_RXSTOP_INT_EN_);
        smsc911x_reg_write(pdata, INT_EN, temp);
 
        spin_lock_irq(&pdata->mac_lock);
@@ -1288,8 +1283,6 @@ 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_;
@@ -1304,7 +1297,8 @@ static int smsc911x_stop(struct net_device *dev)
        smsc911x_tx_update_txcounters(dev);
 
        /* Bring the PHY down */
-       phy_stop(pdata->phy_dev);
+       if (pdata->phy_dev)
+               phy_stop(pdata->phy_dev);
 
        SMSC_TRACE(IFDOWN, "Interface stopped");
        return 0;
@@ -1440,11 +1434,6 @@ static void smsc911x_set_multicast_list(struct net_device *dev)
 
                        /* 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);
@@ -1483,11 +1472,9 @@ static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
                /* 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);
+               if (pdata->multicast_update_pending)
+                       smsc911x_rx_multicast_update_workaround(pdata);
                serviced = IRQ_HANDLED;
        }
 
@@ -1507,16 +1494,16 @@ static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
        }
 
        if (likely(intsts & inten & INT_STS_RSFL_)) {
-               if (likely(netif_rx_schedule_prep(dev, &pdata->napi))) {
+               if (likely(napi_schedule_prep(&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);
+                       __napi_schedule(&pdata->napi);
                } else {
                        SMSC_WARNING(RX_ERR,
-                               "netif_rx_schedule_prep failed");
+                               "napi_schedule_prep failed");
                }
                serviced = IRQ_HANDLED;
        }
@@ -1525,7 +1512,7 @@ static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id)
 }
 
 #ifdef CONFIG_NET_POLL_CONTROLLER
-void smsc911x_poll_controller(struct net_device *dev)
+static void smsc911x_poll_controller(struct net_device *dev)
 {
        disable_irq(dev->irq);
        smsc911x_irqhandler(0, dev);
@@ -1533,6 +1520,31 @@ void smsc911x_poll_controller(struct net_device *dev)
 }
 #endif                         /* CONFIG_NET_POLL_CONTROLLER */
 
+static int smsc911x_set_mac_address(struct net_device *dev, void *p)
+{
+       struct smsc911x_data *pdata = netdev_priv(dev);
+       struct sockaddr *addr = p;
+
+       /* On older hardware revisions we cannot change the mac address
+        * registers while receiving data.  Newer devices can safely change
+        * this at any time. */
+       if (pdata->generation <= 1 && netif_running(dev))
+               return -EBUSY;
+
+       if (!is_valid_ether_addr(addr->sa_data))
+               return -EADDRNOTAVAIL;
+
+       memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
+
+       spin_lock_irq(&pdata->mac_lock);
+       smsc911x_set_hw_mac_address(pdata, dev->dev_addr);
+       spin_unlock_irq(&pdata->mac_lock);
+
+       dev_info(&dev->dev, "MAC Address: %pM\n", dev->dev_addr);
+
+       return 0;
+}
+
 /* Standard ioctls for mii-tool */
 static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
@@ -1567,7 +1579,7 @@ static void smsc911x_ethtool_getdrvinfo(struct net_device *dev,
 {
        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,
+       strlcpy(info->bus_info, dev_name(dev->dev.parent),
                sizeof(info->bus_info));
 }
 
@@ -1646,7 +1658,7 @@ static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op)
        do {
                msleep(1);
                e2cmd = smsc911x_reg_read(pdata, E2P_CMD);
-       } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--));
+       } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (--timeout));
 
        if (!timeout) {
                SMSC_TRACE(DRV, "TIMED OUT");
@@ -1680,6 +1692,7 @@ static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
                                          u8 address, u8 data)
 {
        u32 op = E2P_CMD_EPC_CMD_ERASE_ | address;
+       u32 temp;
        int ret;
 
        SMSC_TRACE(DRV, "address 0x%x, data 0x%x", address, data);
@@ -1688,6 +1701,10 @@ static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata,
        if (!ret) {
                op = E2P_CMD_EPC_CMD_WRITE_ | address;
                smsc911x_reg_write(pdata, E2P_DATA, (u32)data);
+
+               /* Workaround for hardware read-after-write restriction */
+               temp = smsc911x_reg_read(pdata, BYTE_TEST);
+
                ret = smsc911x_eeprom_send_cmd(pdata, op);
        }
 
@@ -1740,7 +1757,7 @@ static int smsc911x_ethtool_set_eeprom(struct net_device *dev,
        return ret;
 }
 
-static struct ethtool_ops smsc911x_ethtool_ops = {
+static const struct ethtool_ops smsc911x_ethtool_ops = {
        .get_settings = smsc911x_ethtool_getsettings,
        .set_settings = smsc911x_ethtool_setsettings,
        .get_link = ethtool_op_get_link,
@@ -1755,6 +1772,36 @@ static struct ethtool_ops smsc911x_ethtool_ops = {
        .set_eeprom = smsc911x_ethtool_set_eeprom,
 };
 
+static const struct net_device_ops smsc911x_netdev_ops = {
+       .ndo_open               = smsc911x_open,
+       .ndo_stop               = smsc911x_stop,
+       .ndo_start_xmit         = smsc911x_hard_start_xmit,
+       .ndo_get_stats          = smsc911x_get_stats,
+       .ndo_set_multicast_list = smsc911x_set_multicast_list,
+       .ndo_do_ioctl           = smsc911x_do_ioctl,
+       .ndo_change_mtu         = eth_change_mtu,
+       .ndo_validate_addr      = eth_validate_addr,
+       .ndo_set_mac_address    = smsc911x_set_mac_address,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+       .ndo_poll_controller    = smsc911x_poll_controller,
+#endif
+};
+
+/* copies the current mac address from hardware to dev->dev_addr */
+static void __devinit smsc911x_read_mac_address(struct net_device *dev)
+{
+       struct smsc911x_data *pdata = netdev_priv(dev);
+       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);
+}
+
 /* Initializing private device structures, only called from probe */
 static int __devinit smsc911x_init(struct net_device *dev)
 {
@@ -1767,9 +1814,7 @@ static int __devinit smsc911x_init(struct net_device *dev)
        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");
@@ -1844,6 +1889,12 @@ static int __devinit smsc911x_init(struct net_device *dev)
                SMSC_WARNING(PROBE,
                        "This driver is not intended for this chip revision");
 
+       /* workaround for platforms without an eeprom, where the mac address
+        * is stored elsewhere and set by the bootloader.  This saves the
+        * mac address before resetting the device */
+       if (pdata->config.flags & SMSC911X_SAVE_MAC_ADDRESS)
+               smsc911x_read_mac_address(dev);
+
        /* Reset the LAN911x */
        if (smsc911x_soft_reset(pdata))
                return -ENODEV;
@@ -1852,20 +1903,11 @@ static int __devinit smsc911x_init(struct net_device *dev)
        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->netdev_ops = &smsc911x_netdev_ops;
        dev->ethtool_ops = &smsc911x_ethtool_ops;
 
-#ifdef CONFIG_NET_POLL_CONTROLLER
-       dev->poll_controller = smsc911x_poll_controller;
-#endif                         /* CONFIG_NET_POLL_CONTROLLER */
-
        return 0;
 }
 
@@ -1895,9 +1937,9 @@ static int __devexit smsc911x_drv_remove(struct platform_device *pdev)
        res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
                                           "smsc911x-memory");
        if (!res)
-               platform_get_resource(pdev, IORESOURCE_MEM, 0);
+               res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
-       release_mem_region(res->start, res->end - res->start);
+       release_mem_region(res->start, resource_size(res));
 
        iounmap(pdata->ioaddr);
 
@@ -1910,14 +1952,21 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
 {
        struct net_device *dev;
        struct smsc911x_data *pdata;
-       struct resource *res;
+       struct smsc911x_platform_config *config = pdev->dev.platform_data;
+       struct resource *res, *irq_res;
        unsigned int intcfg = 0;
-       int res_size;
+       int res_size, irq_flags;
        int retval;
-       DECLARE_MAC_BUF(mac);
 
        pr_info("%s: Driver version %s.\n", SMSC_CHIPNAME, SMSC_DRV_VERSION);
 
+       /* platform data specifies irq & dynamic bus configuration */
+       if (!pdev->dev.platform_data) {
+               pr_warning("%s: platform_data not provided\n", SMSC_CHIPNAME);
+               retval = -ENODEV;
+               goto out_0;
+       }
+
        res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
                                           "smsc911x-memory");
        if (!res)
@@ -1928,7 +1977,15 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
                retval = -ENODEV;
                goto out_0;
        }
-       res_size = res->end - res->start;
+       res_size = resource_size(res);
+
+       irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!irq_res) {
+               pr_warning("%s: Could not allocate irq resource.\n",
+                       SMSC_CHIPNAME);
+               retval = -ENODEV;
+               goto out_0;
+       }
 
        if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) {
                retval = -EBUSY;
@@ -1946,18 +2003,12 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
 
        pdata = netdev_priv(dev);
 
-       dev->irq = platform_get_irq(pdev, 0);
+       dev->irq = irq_res->start;
+       irq_flags = irq_res->flags & IRQF_TRIGGER_MASK;
        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;
-       }
+       /* copy config parameters across to pdata */
+       memcpy(&pdata->config, config, sizeof(pdata->config));
 
        pdata->dev = dev;
        pdata->msg_enable = ((1 << debug) - 1);
@@ -1974,10 +2025,10 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
                goto out_unmap_io_3;
 
        /* configure irq polarity and type before connecting isr */
-       if (pdata->irq_polarity == SMSC911X_IRQ_POLARITY_ACTIVE_HIGH)
+       if (pdata->config.irq_polarity == SMSC911X_IRQ_POLARITY_ACTIVE_HIGH)
                intcfg |= INT_CFG_IRQ_POL_;
 
-       if (pdata->irq_type == SMSC911X_IRQ_TYPE_PUSH_PULL)
+       if (pdata->config.irq_type == SMSC911X_IRQ_TYPE_PUSH_PULL)
                intcfg |= INT_CFG_IRQ_TYPE_;
 
        smsc911x_reg_write(pdata, INT_CFG, intcfg);
@@ -1986,8 +2037,8 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
        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);
+       retval = request_irq(dev->irq, smsc911x_irqhandler,
+                            irq_flags | IRQF_SHARED, dev->name, dev);
        if (retval) {
                SMSC_WARNING(PROBE,
                        "Unable to claim requested irq: %d", dev->irq);
@@ -2018,19 +2069,15 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
 
        /* 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);
+               smsc911x_set_hw_mac_address(pdata, dev->dev_addr);
                SMSC_TRACE(PROBE, "MAC Address is specified by configuration");
+       } else if (is_valid_ether_addr(pdata->config.mac)) {
+               memcpy(dev->dev_addr, pdata->config.mac, 6);
+               SMSC_TRACE(PROBE, "MAC Address specified by platform data");
        } 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);
+               smsc911x_read_mac_address(dev);
 
                if (is_valid_ether_addr(dev->dev_addr)) {
                        /* eeprom values are valid  so use them */
@@ -2039,7 +2086,7 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
                } else {
                        /* eeprom values are invalid, generate random MAC */
                        random_ether_addr(dev->dev_addr);
-                       smsc911x_set_mac_address(pdata, dev->dev_addr);
+                       smsc911x_set_hw_mac_address(pdata, dev->dev_addr);
                        SMSC_TRACE(PROBE,
                                "MAC Address is set to random_ether_addr");
                }
@@ -2047,8 +2094,7 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev)
 
        spin_unlock_irq(&pdata->mac_lock);
 
-       dev_info(&dev->dev, "MAC Address: %s\n",
-                print_mac(mac, dev->dev_addr));
+       dev_info(&dev->dev, "MAC Address: %pM\n", dev->dev_addr);
 
        return 0;
 
@@ -2062,16 +2108,70 @@ out_unmap_io_3:
 out_free_netdev_2:
        free_netdev(dev);
 out_release_io_1:
-       release_mem_region(res->start, res->end - res->start);
+       release_mem_region(res->start, resource_size(res));
 out_0:
        return retval;
 }
 
+#ifdef CONFIG_PM
+/* This implementation assumes the devices remains powered on its VDDVARIO
+ * pins during suspend. */
+
+/* TODO: implement freeze/thaw callbacks for hibernation.*/
+
+static int smsc911x_suspend(struct device *dev)
+{
+       struct net_device *ndev = dev_get_drvdata(dev);
+       struct smsc911x_data *pdata = netdev_priv(ndev);
+
+       /* enable wake on LAN, energy detection and the external PME
+        * signal. */
+       smsc911x_reg_write(pdata, PMT_CTRL,
+               PMT_CTRL_PM_MODE_D1_ | PMT_CTRL_WOL_EN_ |
+               PMT_CTRL_ED_EN_ | PMT_CTRL_PME_EN_);
+
+       return 0;
+}
+
+static int smsc911x_resume(struct device *dev)
+{
+       struct net_device *ndev = dev_get_drvdata(dev);
+       struct smsc911x_data *pdata = netdev_priv(ndev);
+       unsigned int to = 100;
+
+       /* Note 3.11 from the datasheet:
+        *      "When the LAN9220 is in a power saving state, a write of any
+        *       data to the BYTE_TEST register will wake-up the device."
+        */
+       smsc911x_reg_write(pdata, BYTE_TEST, 0);
+
+       /* poll the READY bit in PMT_CTRL. Any other access to the device is
+        * forbidden while this bit isn't set. Try for 100ms and return -EIO
+        * if it failed. */
+       while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_) && --to)
+               udelay(1000);
+
+       return (to == 0) ? -EIO : 0;
+}
+
+static struct dev_pm_ops smsc911x_pm_ops = {
+       .suspend        = smsc911x_suspend,
+       .resume         = smsc911x_resume,
+};
+
+#define SMSC911X_PM_OPS (&smsc911x_pm_ops)
+
+#else
+#define SMSC911X_PM_OPS NULL
+#endif
+
 static struct platform_driver smsc911x_driver = {
        .probe = smsc911x_drv_probe,
-       .remove = smsc911x_drv_remove,
+       .remove = __devexit_p(smsc911x_drv_remove),
        .driver = {
-               .name = SMSC_CHIPNAME,
+               .name   = SMSC_CHIPNAME,
+               .owner  = THIS_MODULE,
+               .pm     = SMSC911X_PM_OPS,
        },
 };