]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/net/ethernet/wiznet/w5100.c
net: w5100: support W5200
[karo-tx-linux.git] / drivers / net / ethernet / wiznet / w5100.c
index 8b282d0b169c4a06e82479b215dbb02fcceb4c1e..09149c9ebeff0819f6e1864fa578a4c4eae43e3a 100644 (file)
@@ -27,6 +27,8 @@
 #include <linux/irq.h>
 #include <linux/gpio.h>
 
+#include "w5100.h"
+
 #define DRV_NAME       "w5100"
 #define DRV_VERSION    "2012-04-04"
 
@@ -36,7 +38,7 @@ MODULE_ALIAS("platform:"DRV_NAME);
 MODULE_LICENSE("GPL");
 
 /*
- * Registers
+ * W5100 and W5100 common registers
  */
 #define W5100_COMMON_REGS      0x0000
 #define W5100_MR               0x0000 /* Mode Register */
@@ -50,51 +52,74 @@ MODULE_LICENSE("GPL");
 #define   IR_S0                          0x01 /* S0 interrupt */
 #define W5100_RTR              0x0017 /* Retry Time-value Register */
 #define   RTR_DEFAULT            2000 /* =0x07d0 (2000) */
-#define W5100_RMSR             0x001a /* Receive Memory Size */
-#define W5100_TMSR             0x001b /* Transmit Memory Size */
 #define W5100_COMMON_REGS_LEN  0x0040
 
-#define W5100_S0_REGS          0x0400
-#define W5100_S0_MR            0x0400 /* S0 Mode Register */
+#define W5100_Sn_MR            0x0000 /* Sn Mode Register */
+#define W5100_Sn_CR            0x0001 /* Sn Command Register */
+#define W5100_Sn_IR            0x0002 /* Sn Interrupt Register */
+#define W5100_Sn_SR            0x0003 /* Sn Status Register */
+#define W5100_Sn_TX_FSR                0x0020 /* Sn Transmit free memory size */
+#define W5100_Sn_TX_RD         0x0022 /* Sn Transmit memory read pointer */
+#define W5100_Sn_TX_WR         0x0024 /* Sn Transmit memory write pointer */
+#define W5100_Sn_RX_RSR                0x0026 /* Sn Receive free memory size */
+#define W5100_Sn_RX_RD         0x0028 /* Sn Receive memory read pointer */
+
+#define S0_REGS(priv)          (is_w5200(priv) ? W5200_S0_REGS : W5100_S0_REGS)
+
+#define W5100_S0_MR(priv)      (S0_REGS(priv) + W5100_Sn_MR)
 #define   S0_MR_MACRAW           0x04 /* MAC RAW mode (promiscuous) */
 #define   S0_MR_MACRAW_MF        0x44 /* MAC RAW mode (filtered) */
-#define W5100_S0_CR            0x0401 /* S0 Command Register */
+#define W5100_S0_CR(priv)      (S0_REGS(priv) + W5100_Sn_CR)
 #define   S0_CR_OPEN             0x01 /* OPEN command */
 #define   S0_CR_CLOSE            0x10 /* CLOSE command */
 #define   S0_CR_SEND             0x20 /* SEND command */
 #define   S0_CR_RECV             0x40 /* RECV command */
-#define W5100_S0_IR            0x0402 /* S0 Interrupt Register */
+#define W5100_S0_IR(priv)      (S0_REGS(priv) + W5100_Sn_IR)
 #define   S0_IR_SENDOK           0x10 /* complete sending */
 #define   S0_IR_RECV             0x04 /* receiving data */
-#define W5100_S0_SR            0x0403 /* S0 Status Register */
+#define W5100_S0_SR(priv)      (S0_REGS(priv) + W5100_Sn_SR)
 #define   S0_SR_MACRAW           0x42 /* mac raw mode */
-#define W5100_S0_TX_FSR                0x0420 /* S0 Transmit free memory size */
-#define W5100_S0_TX_RD         0x0422 /* S0 Transmit memory read pointer */
-#define W5100_S0_TX_WR         0x0424 /* S0 Transmit memory write pointer */
-#define W5100_S0_RX_RSR                0x0426 /* S0 Receive free memory size */
-#define W5100_S0_RX_RD         0x0428 /* S0 Receive memory read pointer */
+#define W5100_S0_TX_FSR(priv)  (S0_REGS(priv) + W5100_Sn_TX_FSR)
+#define W5100_S0_TX_RD(priv)   (S0_REGS(priv) + W5100_Sn_TX_RD)
+#define W5100_S0_TX_WR(priv)   (S0_REGS(priv) + W5100_Sn_TX_WR)
+#define W5100_S0_RX_RSR(priv)  (S0_REGS(priv) + W5100_Sn_RX_RSR)
+#define W5100_S0_RX_RD(priv)   (S0_REGS(priv) + W5100_Sn_RX_RD)
+
 #define W5100_S0_REGS_LEN      0x0040
 
+/*
+ * W5100 specific registers
+ */
+#define W5100_RMSR             0x001a /* Receive Memory Size */
+#define W5100_TMSR             0x001b /* Transmit Memory Size */
+
+#define W5100_S0_REGS          0x0400
+
 #define W5100_TX_MEM_START     0x4000
-#define W5100_TX_MEM_END       0x5fff
-#define W5100_TX_MEM_MASK      0x1fff
+#define W5100_TX_MEM_SIZE      0x2000
 #define W5100_RX_MEM_START     0x6000
-#define W5100_RX_MEM_END       0x7fff
-#define W5100_RX_MEM_MASK      0x1fff
+#define W5100_RX_MEM_SIZE      0x2000
+
+/*
+ * W5200 specific registers
+ */
+#define W5200_S0_REGS          0x4000
+
+#define W5200_Sn_RXMEM_SIZE(n) (0x401e + (n) * 0x0100) /* Sn RX Memory Size */
+#define W5200_Sn_TXMEM_SIZE(n) (0x401f + (n) * 0x0100) /* Sn TX Memory Size */
+#define W5200_S0_IMR           0x402c /* S0 Interrupt Mask Register */
+
+#define W5200_TX_MEM_START     0x8000
+#define W5200_TX_MEM_SIZE      0x4000
+#define W5200_RX_MEM_START     0xc000
+#define W5200_RX_MEM_SIZE      0x4000
 
 /*
  * Device driver private data structure
  */
+
 struct w5100_priv {
-       void __iomem *base;
-       spinlock_t reg_lock;
-       bool indirect;
-       u8   (*read)(struct w5100_priv *priv, u16 addr);
-       void (*write)(struct w5100_priv *priv, u16 addr, u8 data);
-       u16  (*read16)(struct w5100_priv *priv, u16 addr);
-       void (*write16)(struct w5100_priv *priv, u16 addr, u16 data);
-       void (*readbuf)(struct w5100_priv *priv, u16 addr, u8 *buf, int len);
-       void (*writebuf)(struct w5100_priv *priv, u16 addr, u8 *buf, int len);
+       const struct w5100_ops *ops;
        int irq;
        int link_irq;
        int link_gpio;
@@ -103,71 +128,142 @@ struct w5100_priv {
        struct net_device *ndev;
        bool promisc;
        u32 msg_enable;
+
+       struct workqueue_struct *xfer_wq;
+       struct work_struct rx_work;
+       struct sk_buff *tx_skb;
+       struct work_struct tx_work;
+       struct work_struct setrx_work;
+       struct work_struct restart_work;
 };
 
+static inline bool is_w5200(struct w5100_priv *priv)
+{
+       return priv->ops->chip_id == W5200;
+}
+
 /************************************************************************
  *
  *  Lowlevel I/O functions
  *
  ***********************************************************************/
 
+struct w5100_mmio_priv {
+       void __iomem *base;
+       /* Serialize access in indirect address mode */
+       spinlock_t reg_lock;
+};
+
+static inline struct w5100_mmio_priv *w5100_mmio_priv(struct net_device *dev)
+{
+       return w5100_ops_priv(dev);
+}
+
+static inline void __iomem *w5100_mmio(struct net_device *ndev)
+{
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
+
+       return mmio_priv->base;
+}
+
 /*
  * In direct address mode host system can directly access W5100 registers
  * after mapping to Memory-Mapped I/O space.
  *
  * 0x8000 bytes are required for memory space.
  */
-static inline u8 w5100_read_direct(struct w5100_priv *priv, u16 addr)
+static inline int w5100_read_direct(struct net_device *ndev, u16 addr)
 {
-       return ioread8(priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
+       return ioread8(w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT));
+}
+
+static inline int __w5100_write_direct(struct net_device *ndev, u16 addr,
+                                      u8 data)
+{
+       iowrite8(data, w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT));
+
+       return 0;
 }
 
-static inline void w5100_write_direct(struct w5100_priv *priv,
-                                     u16 addr, u8 data)
+static inline int w5100_write_direct(struct net_device *ndev, u16 addr, u8 data)
 {
-       iowrite8(data, priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
+       __w5100_write_direct(ndev, addr, data);
+       mmiowb();
+
+       return 0;
 }
 
-static u16 w5100_read16_direct(struct w5100_priv *priv, u16 addr)
+static int w5100_read16_direct(struct net_device *ndev, u16 addr)
 {
        u16 data;
-       data  = w5100_read_direct(priv, addr) << 8;
-       data |= w5100_read_direct(priv, addr + 1);
+       data  = w5100_read_direct(ndev, addr) << 8;
+       data |= w5100_read_direct(ndev, addr + 1);
        return data;
 }
 
-static void w5100_write16_direct(struct w5100_priv *priv, u16 addr, u16 data)
+static int w5100_write16_direct(struct net_device *ndev, u16 addr, u16 data)
 {
-       w5100_write_direct(priv, addr, data >> 8);
-       w5100_write_direct(priv, addr + 1, data);
+       __w5100_write_direct(ndev, addr, data >> 8);
+       __w5100_write_direct(ndev, addr + 1, data);
+       mmiowb();
+
+       return 0;
 }
 
-static void w5100_readbuf_direct(struct w5100_priv *priv,
-                                u16 offset, u8 *buf, int len)
+static int w5100_readbulk_direct(struct net_device *ndev, u16 addr, u8 *buf,
+                                int len)
 {
-       u16 addr = W5100_RX_MEM_START + (offset & W5100_RX_MEM_MASK);
        int i;
 
-       for (i = 0; i < len; i++, addr++) {
-               if (unlikely(addr > W5100_RX_MEM_END))
-                       addr = W5100_RX_MEM_START;
-               *buf++ = w5100_read_direct(priv, addr);
-       }
+       for (i = 0; i < len; i++, addr++)
+               *buf++ = w5100_read_direct(ndev, addr);
+
+       return 0;
 }
 
-static void w5100_writebuf_direct(struct w5100_priv *priv,
-                                 u16 offset, u8 *buf, int len)
+static int w5100_writebulk_direct(struct net_device *ndev, u16 addr,
+                                 const u8 *buf, int len)
 {
-       u16 addr = W5100_TX_MEM_START + (offset & W5100_TX_MEM_MASK);
        int i;
 
-       for (i = 0; i < len; i++, addr++) {
-               if (unlikely(addr > W5100_TX_MEM_END))
-                       addr = W5100_TX_MEM_START;
-               w5100_write_direct(priv, addr, *buf++);
-       }
+       for (i = 0; i < len; i++, addr++)
+               __w5100_write_direct(ndev, addr, *buf++);
+
+       mmiowb();
+
+       return 0;
 }
 
+static int w5100_mmio_init(struct net_device *ndev)
+{
+       struct platform_device *pdev = to_platform_device(ndev->dev.parent);
+       struct w5100_priv *priv = netdev_priv(ndev);
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
+       struct resource *mem;
+
+       spin_lock_init(&mmio_priv->reg_lock);
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       mmio_priv->base = devm_ioremap_resource(&pdev->dev, mem);
+       if (IS_ERR(mmio_priv->base))
+               return PTR_ERR(mmio_priv->base);
+
+       netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, priv->irq);
+
+       return 0;
+}
+
+static const struct w5100_ops w5100_mmio_direct_ops = {
+       .chip_id = W5100,
+       .read = w5100_read_direct,
+       .write = w5100_write_direct,
+       .read16 = w5100_read16_direct,
+       .write16 = w5100_write16_direct,
+       .readbulk = w5100_readbulk_direct,
+       .writebulk = w5100_writebulk_direct,
+       .init = w5100_mmio_init,
+};
+
 /*
  * In indirect address mode host system indirectly accesses registers by
  * using Indirect Mode Address Register (IDM_AR) and Indirect Mode Data
@@ -179,139 +275,294 @@ static void w5100_writebuf_direct(struct w5100_priv *priv,
 #define W5100_IDM_AR           0x01   /* Indirect Mode Address Register */
 #define W5100_IDM_DR           0x03   /* Indirect Mode Data Register */
 
-static u8 w5100_read_indirect(struct w5100_priv *priv, u16 addr)
+static int w5100_read_indirect(struct net_device *ndev, u16 addr)
 {
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
        unsigned long flags;
        u8 data;
 
-       spin_lock_irqsave(&priv->reg_lock, flags);
-       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-       mmiowb();
-       data = w5100_read_direct(priv, W5100_IDM_DR);
-       spin_unlock_irqrestore(&priv->reg_lock, flags);
+       spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+       w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+       data = w5100_read_direct(ndev, W5100_IDM_DR);
+       spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
 
        return data;
 }
 
-static void w5100_write_indirect(struct w5100_priv *priv, u16 addr, u8 data)
+static int w5100_write_indirect(struct net_device *ndev, u16 addr, u8 data)
 {
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
        unsigned long flags;
 
-       spin_lock_irqsave(&priv->reg_lock, flags);
-       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-       mmiowb();
-       w5100_write_direct(priv, W5100_IDM_DR, data);
-       mmiowb();
-       spin_unlock_irqrestore(&priv->reg_lock, flags);
+       spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+       w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+       w5100_write_direct(ndev, W5100_IDM_DR, data);
+       spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+       return 0;
 }
 
-static u16 w5100_read16_indirect(struct w5100_priv *priv, u16 addr)
+static int w5100_read16_indirect(struct net_device *ndev, u16 addr)
 {
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
        unsigned long flags;
        u16 data;
 
-       spin_lock_irqsave(&priv->reg_lock, flags);
-       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-       mmiowb();
-       data  = w5100_read_direct(priv, W5100_IDM_DR) << 8;
-       data |= w5100_read_direct(priv, W5100_IDM_DR);
-       spin_unlock_irqrestore(&priv->reg_lock, flags);
+       spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+       w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+       data  = w5100_read_direct(ndev, W5100_IDM_DR) << 8;
+       data |= w5100_read_direct(ndev, W5100_IDM_DR);
+       spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
 
        return data;
 }
 
-static void w5100_write16_indirect(struct w5100_priv *priv, u16 addr, u16 data)
+static int w5100_write16_indirect(struct net_device *ndev, u16 addr, u16 data)
 {
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
        unsigned long flags;
 
-       spin_lock_irqsave(&priv->reg_lock, flags);
-       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-       mmiowb();
-       w5100_write_direct(priv, W5100_IDM_DR, data >> 8);
-       w5100_write_direct(priv, W5100_IDM_DR, data);
-       mmiowb();
-       spin_unlock_irqrestore(&priv->reg_lock, flags);
+       spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+       w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+       __w5100_write_direct(ndev, W5100_IDM_DR, data >> 8);
+       w5100_write_direct(ndev, W5100_IDM_DR, data);
+       spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+       return 0;
 }
 
-static void w5100_readbuf_indirect(struct w5100_priv *priv,
-                                  u16 offset, u8 *buf, int len)
+static int w5100_readbulk_indirect(struct net_device *ndev, u16 addr, u8 *buf,
+                                  int len)
 {
-       u16 addr = W5100_RX_MEM_START + (offset & W5100_RX_MEM_MASK);
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
        unsigned long flags;
        int i;
 
-       spin_lock_irqsave(&priv->reg_lock, flags);
-       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-       mmiowb();
+       spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+       w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+
+       for (i = 0; i < len; i++)
+               *buf++ = w5100_read_direct(ndev, W5100_IDM_DR);
 
-       for (i = 0; i < len; i++, addr++) {
-               if (unlikely(addr > W5100_RX_MEM_END)) {
-                       addr = W5100_RX_MEM_START;
-                       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-                       mmiowb();
-               }
-               *buf++ = w5100_read_direct(priv, W5100_IDM_DR);
-       }
        mmiowb();
-       spin_unlock_irqrestore(&priv->reg_lock, flags);
+       spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+       return 0;
 }
 
-static void w5100_writebuf_indirect(struct w5100_priv *priv,
-                                   u16 offset, u8 *buf, int len)
+static int w5100_writebulk_indirect(struct net_device *ndev, u16 addr,
+                                   const u8 *buf, int len)
 {
-       u16 addr = W5100_TX_MEM_START + (offset & W5100_TX_MEM_MASK);
+       struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
        unsigned long flags;
        int i;
 
-       spin_lock_irqsave(&priv->reg_lock, flags);
-       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-       mmiowb();
+       spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+       w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+
+       for (i = 0; i < len; i++)
+               __w5100_write_direct(ndev, W5100_IDM_DR, *buf++);
 
-       for (i = 0; i < len; i++, addr++) {
-               if (unlikely(addr > W5100_TX_MEM_END)) {
-                       addr = W5100_TX_MEM_START;
-                       w5100_write16_direct(priv, W5100_IDM_AR, addr);
-                       mmiowb();
-               }
-               w5100_write_direct(priv, W5100_IDM_DR, *buf++);
-       }
        mmiowb();
-       spin_unlock_irqrestore(&priv->reg_lock, flags);
+       spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+       return 0;
 }
 
+static int w5100_reset_indirect(struct net_device *ndev)
+{
+       w5100_write_direct(ndev, W5100_MR, MR_RST);
+       mdelay(5);
+       w5100_write_direct(ndev, W5100_MR, MR_PB | MR_AI | MR_IND);
+
+       return 0;
+}
+
+static const struct w5100_ops w5100_mmio_indirect_ops = {
+       .chip_id = W5100,
+       .read = w5100_read_indirect,
+       .write = w5100_write_indirect,
+       .read16 = w5100_read16_indirect,
+       .write16 = w5100_write16_indirect,
+       .readbulk = w5100_readbulk_indirect,
+       .writebulk = w5100_writebulk_indirect,
+       .init = w5100_mmio_init,
+       .reset = w5100_reset_indirect,
+};
+
 #if defined(CONFIG_WIZNET_BUS_DIRECT)
-#define w5100_read     w5100_read_direct
-#define w5100_write    w5100_write_direct
-#define w5100_read16   w5100_read16_direct
-#define w5100_write16  w5100_write16_direct
-#define w5100_readbuf  w5100_readbuf_direct
-#define w5100_writebuf w5100_writebuf_direct
+
+static int w5100_read(struct w5100_priv *priv, u16 addr)
+{
+       return w5100_read_direct(priv->ndev, addr);
+}
+
+static int w5100_write(struct w5100_priv *priv, u16 addr, u8 data)
+{
+       return w5100_write_direct(priv->ndev, addr, data);
+}
+
+static int w5100_read16(struct w5100_priv *priv, u16 addr)
+{
+       return w5100_read16_direct(priv->ndev, addr);
+}
+
+static int w5100_write16(struct w5100_priv *priv, u16 addr, u16 data)
+{
+       return w5100_write16_direct(priv->ndev, addr, data);
+}
+
+static int w5100_readbulk(struct w5100_priv *priv, u16 addr, u8 *buf, int len)
+{
+       return w5100_readbulk_direct(priv->ndev, addr, buf, len);
+}
+
+static int w5100_writebulk(struct w5100_priv *priv, u16 addr, const u8 *buf,
+                          int len)
+{
+       return w5100_writebulk_direct(priv->ndev, addr, buf, len);
+}
 
 #elif defined(CONFIG_WIZNET_BUS_INDIRECT)
-#define w5100_read     w5100_read_indirect
-#define w5100_write    w5100_write_indirect
-#define w5100_read16   w5100_read16_indirect
-#define w5100_write16  w5100_write16_indirect
-#define w5100_readbuf  w5100_readbuf_indirect
-#define w5100_writebuf w5100_writebuf_indirect
+
+static int w5100_read(struct w5100_priv *priv, u16 addr)
+{
+       return w5100_read_indirect(priv->ndev, addr);
+}
+
+static int w5100_write(struct w5100_priv *priv, u16 addr, u8 data)
+{
+       return w5100_write_indirect(priv->ndev, addr, data);
+}
+
+static int w5100_read16(struct w5100_priv *priv, u16 addr)
+{
+       return w5100_read16_indirect(priv->ndev, addr);
+}
+
+static int w5100_write16(struct w5100_priv *priv, u16 addr, u16 data)
+{
+       return w5100_write16_indirect(priv->ndev, addr, data);
+}
+
+static int w5100_readbulk(struct w5100_priv *priv, u16 addr, u8 *buf, int len)
+{
+       return w5100_readbulk_indirect(priv->ndev, addr, buf, len);
+}
+
+static int w5100_writebulk(struct w5100_priv *priv, u16 addr, const u8 *buf,
+                          int len)
+{
+       return w5100_writebulk_indirect(priv->ndev, addr, buf, len);
+}
 
 #else /* CONFIG_WIZNET_BUS_ANY */
-#define w5100_read     priv->read
-#define w5100_write    priv->write
-#define w5100_read16   priv->read16
-#define w5100_write16  priv->write16
-#define w5100_readbuf  priv->readbuf
-#define w5100_writebuf priv->writebuf
+
+static int w5100_read(struct w5100_priv *priv, u16 addr)
+{
+       return priv->ops->read(priv->ndev, addr);
+}
+
+static int w5100_write(struct w5100_priv *priv, u16 addr, u8 data)
+{
+       return priv->ops->write(priv->ndev, addr, data);
+}
+
+static int w5100_read16(struct w5100_priv *priv, u16 addr)
+{
+       return priv->ops->read16(priv->ndev, addr);
+}
+
+static int w5100_write16(struct w5100_priv *priv, u16 addr, u16 data)
+{
+       return priv->ops->write16(priv->ndev, addr, data);
+}
+
+static int w5100_readbulk(struct w5100_priv *priv, u16 addr, u8 *buf, int len)
+{
+       return priv->ops->readbulk(priv->ndev, addr, buf, len);
+}
+
+static int w5100_writebulk(struct w5100_priv *priv, u16 addr, const u8 *buf,
+                          int len)
+{
+       return priv->ops->writebulk(priv->ndev, addr, buf, len);
+}
+
 #endif
 
+static int w5100_readbuf(struct w5100_priv *priv, u16 offset, u8 *buf, int len)
+{
+       u16 addr;
+       int remain = 0;
+       int ret;
+       const u16 mem_start =
+               is_w5200(priv) ? W5200_RX_MEM_START : W5100_RX_MEM_START;
+       const u16 mem_size =
+               is_w5200(priv) ? W5200_RX_MEM_SIZE : W5100_RX_MEM_SIZE;
+
+       offset %= mem_size;
+       addr = mem_start + offset;
+
+       if (offset + len > mem_size) {
+               remain = (offset + len) % mem_size;
+               len = mem_size - offset;
+       }
+
+       ret = w5100_readbulk(priv, addr, buf, len);
+       if (ret || !remain)
+               return ret;
+
+       return w5100_readbulk(priv, mem_start, buf + len, remain);
+}
+
+static int w5100_writebuf(struct w5100_priv *priv, u16 offset, const u8 *buf,
+                         int len)
+{
+       u16 addr;
+       int ret;
+       int remain = 0;
+       const u16 mem_start =
+               is_w5200(priv) ? W5200_TX_MEM_START : W5100_TX_MEM_START;
+       const u16 mem_size =
+               is_w5200(priv) ? W5200_TX_MEM_SIZE : W5100_TX_MEM_SIZE;
+
+       offset %= mem_size;
+       addr = mem_start + offset;
+
+       if (offset + len > mem_size) {
+               remain = (offset + len) % mem_size;
+               len = mem_size - offset;
+       }
+
+       ret = w5100_writebulk(priv, addr, buf, len);
+       if (ret || !remain)
+               return ret;
+
+       return w5100_writebulk(priv, mem_start, buf + len, remain);
+}
+
+static int w5100_reset(struct w5100_priv *priv)
+{
+       if (priv->ops->reset)
+               return priv->ops->reset(priv->ndev);
+
+       w5100_write(priv, W5100_MR, MR_RST);
+       mdelay(5);
+       w5100_write(priv, W5100_MR, MR_PB);
+
+       return 0;
+}
+
 static int w5100_command(struct w5100_priv *priv, u16 cmd)
 {
-       unsigned long timeout = jiffies + msecs_to_jiffies(100);
+       unsigned long timeout;
 
-       w5100_write(priv, W5100_S0_CR, cmd);
-       mmiowb();
+       w5100_write(priv, W5100_S0_CR(priv), cmd);
 
-       while (w5100_read(priv, W5100_S0_CR) != 0) {
+       timeout = jiffies + msecs_to_jiffies(100);
+
+       while (w5100_read(priv, W5100_S0_CR(priv)) != 0) {
                if (time_after(jiffies, timeout))
                        return -EIO;
                cpu_relax();
@@ -323,47 +574,59 @@ static int w5100_command(struct w5100_priv *priv, u16 cmd)
 static void w5100_write_macaddr(struct w5100_priv *priv)
 {
        struct net_device *ndev = priv->ndev;
-       int i;
 
-       for (i = 0; i < ETH_ALEN; i++)
-               w5100_write(priv, W5100_SHAR + i, ndev->dev_addr[i]);
-       mmiowb();
+       w5100_writebulk(priv, W5100_SHAR, ndev->dev_addr, ETH_ALEN);
 }
 
-static void w5100_hw_reset(struct w5100_priv *priv)
+static void w5100_memory_configure(struct w5100_priv *priv)
 {
-       w5100_write_direct(priv, W5100_MR, MR_RST);
-       mmiowb();
-       mdelay(5);
-       w5100_write_direct(priv, W5100_MR, priv->indirect ?
-                                 MR_PB | MR_AI | MR_IND :
-                                 MR_PB);
-       mmiowb();
-       w5100_write(priv, W5100_IMR, 0);
-       w5100_write_macaddr(priv);
-
        /* Configure 16K of internal memory
         * as 8K RX buffer and 8K TX buffer
         */
        w5100_write(priv, W5100_RMSR, 0x03);
        w5100_write(priv, W5100_TMSR, 0x03);
-       mmiowb();
+}
+
+static void w5200_memory_configure(struct w5100_priv *priv)
+{
+       int i;
+
+       /* Configure internal RX memory as 16K RX buffer and
+        * internal TX memory as 16K TX buffer
+        */
+       w5100_write(priv, W5200_Sn_RXMEM_SIZE(0), 0x10);
+       w5100_write(priv, W5200_Sn_TXMEM_SIZE(0), 0x10);
+
+       for (i = 1; i < 8; i++) {
+               w5100_write(priv, W5200_Sn_RXMEM_SIZE(i), 0);
+               w5100_write(priv, W5200_Sn_TXMEM_SIZE(i), 0);
+       }
+}
+
+static void w5100_hw_reset(struct w5100_priv *priv)
+{
+       w5100_reset(priv);
+
+       w5100_write(priv, W5100_IMR, 0);
+       w5100_write_macaddr(priv);
+
+       if (is_w5200(priv))
+               w5200_memory_configure(priv);
+       else
+               w5100_memory_configure(priv);
 }
 
 static void w5100_hw_start(struct w5100_priv *priv)
 {
-       w5100_write(priv, W5100_S0_MR, priv->promisc ?
+       w5100_write(priv, W5100_S0_MR(priv), priv->promisc ?
                          S0_MR_MACRAW : S0_MR_MACRAW_MF);
-       mmiowb();
        w5100_command(priv, S0_CR_OPEN);
        w5100_write(priv, W5100_IMR, IR_S0);
-       mmiowb();
 }
 
 static void w5100_hw_close(struct w5100_priv *priv)
 {
        w5100_write(priv, W5100_IMR, 0);
-       mmiowb();
        w5100_command(priv, S0_CR_CLOSE);
 }
 
@@ -412,20 +675,17 @@ static int w5100_get_regs_len(struct net_device *ndev)
 }
 
 static void w5100_get_regs(struct net_device *ndev,
-                          struct ethtool_regs *regs, void *_buf)
+                          struct ethtool_regs *regs, void *buf)
 {
        struct w5100_priv *priv = netdev_priv(ndev);
-       u8 *buf = _buf;
-       u16 i;
 
        regs->version = 1;
-       for (i = 0; i < W5100_COMMON_REGS_LEN; i++)
-               *buf++ = w5100_read(priv, W5100_COMMON_REGS + i);
-       for (i = 0; i < W5100_S0_REGS_LEN; i++)
-               *buf++ = w5100_read(priv, W5100_S0_REGS + i);
+       w5100_readbulk(priv, W5100_COMMON_REGS, buf, W5100_COMMON_REGS_LEN);
+       buf += W5100_COMMON_REGS_LEN;
+       w5100_readbulk(priv, S0_REGS(priv), buf, W5100_S0_REGS_LEN);
 }
 
-static void w5100_tx_timeout(struct net_device *ndev)
+static void w5100_restart(struct net_device *ndev)
 {
        struct w5100_priv *priv = netdev_priv(ndev);
 
@@ -437,70 +697,134 @@ static void w5100_tx_timeout(struct net_device *ndev)
        netif_wake_queue(ndev);
 }
 
-static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
+static void w5100_restart_work(struct work_struct *work)
+{
+       struct w5100_priv *priv = container_of(work, struct w5100_priv,
+                                              restart_work);
+
+       w5100_restart(priv->ndev);
+}
+
+static void w5100_tx_timeout(struct net_device *ndev)
 {
        struct w5100_priv *priv = netdev_priv(ndev);
-       u16 offset;
 
-       netif_stop_queue(ndev);
+       if (priv->ops->may_sleep)
+               schedule_work(&priv->restart_work);
+       else
+               w5100_restart(ndev);
+}
+
+static void w5100_tx_skb(struct net_device *ndev, struct sk_buff *skb)
+{
+       struct w5100_priv *priv = netdev_priv(ndev);
+       u16 offset;
 
-       offset = w5100_read16(priv, W5100_S0_TX_WR);
+       offset = w5100_read16(priv, W5100_S0_TX_WR(priv));
        w5100_writebuf(priv, offset, skb->data, skb->len);
-       w5100_write16(priv, W5100_S0_TX_WR, offset + skb->len);
-       mmiowb();
+       w5100_write16(priv, W5100_S0_TX_WR(priv), offset + skb->len);
        ndev->stats.tx_bytes += skb->len;
        ndev->stats.tx_packets++;
        dev_kfree_skb(skb);
 
        w5100_command(priv, S0_CR_SEND);
+}
+
+static void w5100_tx_work(struct work_struct *work)
+{
+       struct w5100_priv *priv = container_of(work, struct w5100_priv,
+                                              tx_work);
+       struct sk_buff *skb = priv->tx_skb;
+
+       priv->tx_skb = NULL;
+
+       if (WARN_ON(!skb))
+               return;
+       w5100_tx_skb(priv->ndev, skb);
+}
+
+static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
+{
+       struct w5100_priv *priv = netdev_priv(ndev);
+
+       netif_stop_queue(ndev);
+
+       if (priv->ops->may_sleep) {
+               WARN_ON(priv->tx_skb);
+               priv->tx_skb = skb;
+               queue_work(priv->xfer_wq, &priv->tx_work);
+       } else {
+               w5100_tx_skb(ndev, skb);
+       }
 
        return NETDEV_TX_OK;
 }
 
-static int w5100_napi_poll(struct napi_struct *napi, int budget)
+static struct sk_buff *w5100_rx_skb(struct net_device *ndev)
 {
-       struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi);
-       struct net_device *ndev = priv->ndev;
+       struct w5100_priv *priv = netdev_priv(ndev);
        struct sk_buff *skb;
-       int rx_count;
        u16 rx_len;
        u16 offset;
        u8 header[2];
+       u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR(priv));
 
-       for (rx_count = 0; rx_count < budget; rx_count++) {
-               u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR);
-               if (rx_buf_len == 0)
-                       break;
+       if (rx_buf_len == 0)
+               return NULL;
 
-               offset = w5100_read16(priv, W5100_S0_RX_RD);
-               w5100_readbuf(priv, offset, header, 2);
-               rx_len = get_unaligned_be16(header) - 2;
-
-               skb = netdev_alloc_skb_ip_align(ndev, rx_len);
-               if (unlikely(!skb)) {
-                       w5100_write16(priv, W5100_S0_RX_RD,
-                                           offset + rx_buf_len);
-                       w5100_command(priv, S0_CR_RECV);
-                       ndev->stats.rx_dropped++;
-                       return -ENOMEM;
-               }
+       offset = w5100_read16(priv, W5100_S0_RX_RD(priv));
+       w5100_readbuf(priv, offset, header, 2);
+       rx_len = get_unaligned_be16(header) - 2;
 
-               skb_put(skb, rx_len);
-               w5100_readbuf(priv, offset + 2, skb->data, rx_len);
-               w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len);
-               mmiowb();
+       skb = netdev_alloc_skb_ip_align(ndev, rx_len);
+       if (unlikely(!skb)) {
+               w5100_write16(priv, W5100_S0_RX_RD(priv), offset + rx_buf_len);
                w5100_command(priv, S0_CR_RECV);
-               skb->protocol = eth_type_trans(skb, ndev);
+               ndev->stats.rx_dropped++;
+               return NULL;
+       }
+
+       skb_put(skb, rx_len);
+       w5100_readbuf(priv, offset + 2, skb->data, rx_len);
+       w5100_write16(priv, W5100_S0_RX_RD(priv), offset + 2 + rx_len);
+       w5100_command(priv, S0_CR_RECV);
+       skb->protocol = eth_type_trans(skb, ndev);
+
+       ndev->stats.rx_packets++;
+       ndev->stats.rx_bytes += rx_len;
+
+       return skb;
+}
+
+static void w5100_rx_work(struct work_struct *work)
+{
+       struct w5100_priv *priv = container_of(work, struct w5100_priv,
+                                              rx_work);
+       struct sk_buff *skb;
+
+       while ((skb = w5100_rx_skb(priv->ndev)))
+               netif_rx_ni(skb);
+
+       w5100_write(priv, W5100_IMR, IR_S0);
+}
+
+static int w5100_napi_poll(struct napi_struct *napi, int budget)
+{
+       struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi);
+       int rx_count;
+
+       for (rx_count = 0; rx_count < budget; rx_count++) {
+               struct sk_buff *skb = w5100_rx_skb(priv->ndev);
 
-               netif_receive_skb(skb);
-               ndev->stats.rx_packets++;
-               ndev->stats.rx_bytes += rx_len;
+               if (skb)
+                       netif_receive_skb(skb);
+               else
+                       break;
        }
 
        if (rx_count < budget) {
                napi_complete(napi);
                w5100_write(priv, W5100_IMR, IR_S0);
-               mmiowb();
        }
 
        return rx_count;
@@ -511,11 +835,10 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
        struct net_device *ndev = ndev_instance;
        struct w5100_priv *priv = netdev_priv(ndev);
 
-       int ir = w5100_read(priv, W5100_S0_IR);
+       int ir = w5100_read(priv, W5100_S0_IR(priv));
        if (!ir)
                return IRQ_NONE;
-       w5100_write(priv, W5100_S0_IR, ir);
-       mmiowb();
+       w5100_write(priv, W5100_S0_IR(priv), ir);
 
        if (ir & S0_IR_SENDOK) {
                netif_dbg(priv, tx_done, ndev, "tx done\n");
@@ -523,11 +846,12 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
        }
 
        if (ir & S0_IR_RECV) {
-               if (napi_schedule_prep(&priv->napi)) {
-                       w5100_write(priv, W5100_IMR, 0);
-                       mmiowb();
+               w5100_write(priv, W5100_IMR, 0);
+
+               if (priv->ops->may_sleep)
+                       queue_work(priv->xfer_wq, &priv->rx_work);
+               else if (napi_schedule_prep(&priv->napi))
                        __napi_schedule(&priv->napi);
-               }
        }
 
        return IRQ_HANDLED;
@@ -551,6 +875,14 @@ static irqreturn_t w5100_detect_link(int irq, void *ndev_instance)
        return IRQ_HANDLED;
 }
 
+static void w5100_setrx_work(struct work_struct *work)
+{
+       struct w5100_priv *priv = container_of(work, struct w5100_priv,
+                                              setrx_work);
+
+       w5100_hw_start(priv);
+}
+
 static void w5100_set_rx_mode(struct net_device *ndev)
 {
        struct w5100_priv *priv = netdev_priv(ndev);
@@ -558,7 +890,11 @@ static void w5100_set_rx_mode(struct net_device *ndev)
 
        if (priv->promisc != set_promisc) {
                priv->promisc = set_promisc;
-               w5100_hw_start(priv);
+
+               if (priv->ops->may_sleep)
+                       schedule_work(&priv->setrx_work);
+               else
+                       w5100_hw_start(priv);
        }
 }
 
@@ -620,91 +956,68 @@ static const struct net_device_ops w5100_netdev_ops = {
        .ndo_change_mtu         = eth_change_mtu,
 };
 
-static int w5100_hw_probe(struct platform_device *pdev)
+static int w5100_mmio_probe(struct platform_device *pdev)
 {
        struct wiznet_platform_data *data = dev_get_platdata(&pdev->dev);
-       struct net_device *ndev = platform_get_drvdata(pdev);
-       struct w5100_priv *priv = netdev_priv(ndev);
-       const char *name = netdev_name(ndev);
+       u8 *mac_addr = NULL;
        struct resource *mem;
-       int mem_size;
+       const struct w5100_ops *ops;
        int irq;
-       int ret;
 
-       if (data && is_valid_ether_addr(data->mac_addr)) {
-               memcpy(ndev->dev_addr, data->mac_addr, ETH_ALEN);
-       } else {
-               eth_hw_addr_random(ndev);
-       }
+       if (data && is_valid_ether_addr(data->mac_addr))
+               mac_addr = data->mac_addr;
 
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       priv->base = devm_ioremap_resource(&pdev->dev, mem);
-       if (IS_ERR(priv->base))
-               return PTR_ERR(priv->base);
-
-       mem_size = resource_size(mem);
-
-       spin_lock_init(&priv->reg_lock);
-       priv->indirect = mem_size < W5100_BUS_DIRECT_SIZE;
-       if (priv->indirect) {
-               priv->read     = w5100_read_indirect;
-               priv->write    = w5100_write_indirect;
-               priv->read16   = w5100_read16_indirect;
-               priv->write16  = w5100_write16_indirect;
-               priv->readbuf  = w5100_readbuf_indirect;
-               priv->writebuf = w5100_writebuf_indirect;
-       } else {
-               priv->read     = w5100_read_direct;
-               priv->write    = w5100_write_direct;
-               priv->read16   = w5100_read16_direct;
-               priv->write16  = w5100_write16_direct;
-               priv->readbuf  = w5100_readbuf_direct;
-               priv->writebuf = w5100_writebuf_direct;
-       }
-
-       w5100_hw_reset(priv);
-       if (w5100_read16(priv, W5100_RTR) != RTR_DEFAULT)
-               return -ENODEV;
+       if (resource_size(mem) < W5100_BUS_DIRECT_SIZE)
+               ops = &w5100_mmio_indirect_ops;
+       else
+               ops = &w5100_mmio_direct_ops;
 
        irq = platform_get_irq(pdev, 0);
        if (irq < 0)
                return irq;
-       ret = request_irq(irq, w5100_interrupt,
-                         IRQ_TYPE_LEVEL_LOW, name, ndev);
-       if (ret < 0)
-               return ret;
-       priv->irq = irq;
 
-       priv->link_gpio = data ? data->link_gpio : -EINVAL;
-       if (gpio_is_valid(priv->link_gpio)) {
-               char *link_name = devm_kzalloc(&pdev->dev, 16, GFP_KERNEL);
-               if (!link_name)
-                       return -ENOMEM;
-               snprintf(link_name, 16, "%s-link", name);
-               priv->link_irq = gpio_to_irq(priv->link_gpio);
-               if (request_any_context_irq(priv->link_irq, w5100_detect_link,
-                               IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
-                               link_name, priv->ndev) < 0)
-                       priv->link_gpio = -EINVAL;
-       }
+       return w5100_probe(&pdev->dev, ops, sizeof(struct w5100_mmio_priv),
+                          mac_addr, irq, data ? data->link_gpio : -EINVAL);
+}
 
-       netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, irq);
-       return 0;
+static int w5100_mmio_remove(struct platform_device *pdev)
+{
+       return w5100_remove(&pdev->dev);
 }
 
-static int w5100_probe(struct platform_device *pdev)
+void *w5100_ops_priv(const struct net_device *ndev)
+{
+       return netdev_priv(ndev) +
+              ALIGN(sizeof(struct w5100_priv), NETDEV_ALIGN);
+}
+EXPORT_SYMBOL_GPL(w5100_ops_priv);
+
+int w5100_probe(struct device *dev, const struct w5100_ops *ops,
+               int sizeof_ops_priv, u8 *mac_addr, int irq, int link_gpio)
 {
        struct w5100_priv *priv;
        struct net_device *ndev;
        int err;
+       size_t alloc_size;
+
+       alloc_size = sizeof(*priv);
+       if (sizeof_ops_priv) {
+               alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
+               alloc_size += sizeof_ops_priv;
+       }
+       alloc_size += NETDEV_ALIGN - 1;
 
-       ndev = alloc_etherdev(sizeof(*priv));
+       ndev = alloc_etherdev(alloc_size);
        if (!ndev)
                return -ENOMEM;
-       SET_NETDEV_DEV(ndev, &pdev->dev);
-       platform_set_drvdata(pdev, ndev);
+       SET_NETDEV_DEV(ndev, dev);
+       dev_set_drvdata(dev, ndev);
        priv = netdev_priv(ndev);
        priv->ndev = ndev;
+       priv->ops = ops;
+       priv->irq = irq;
+       priv->link_gpio = link_gpio;
 
        ndev->netdev_ops = &w5100_netdev_ops;
        ndev->ethtool_ops = &w5100_ethtool_ops;
@@ -720,22 +1033,78 @@ static int w5100_probe(struct platform_device *pdev)
        if (err < 0)
                goto err_register;
 
-       err = w5100_hw_probe(pdev);
-       if (err < 0)
-               goto err_hw_probe;
+       priv->xfer_wq = create_workqueue(netdev_name(ndev));
+       if (!priv->xfer_wq) {
+               err = -ENOMEM;
+               goto err_wq;
+       }
+
+       INIT_WORK(&priv->rx_work, w5100_rx_work);
+       INIT_WORK(&priv->tx_work, w5100_tx_work);
+       INIT_WORK(&priv->setrx_work, w5100_setrx_work);
+       INIT_WORK(&priv->restart_work, w5100_restart_work);
+
+       if (mac_addr)
+               memcpy(ndev->dev_addr, mac_addr, ETH_ALEN);
+       else
+               eth_hw_addr_random(ndev);
+
+       if (priv->ops->init) {
+               err = priv->ops->init(priv->ndev);
+               if (err)
+                       goto err_hw;
+       }
+
+       w5100_hw_reset(priv);
+       if (w5100_read16(priv, W5100_RTR) != RTR_DEFAULT) {
+               err = -ENODEV;
+               goto err_hw;
+       }
+
+       if (ops->may_sleep) {
+               err = request_threaded_irq(priv->irq, NULL, w5100_interrupt,
+                                          IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                                          netdev_name(ndev), ndev);
+       } else {
+               err = request_irq(priv->irq, w5100_interrupt,
+                                 IRQF_TRIGGER_LOW, netdev_name(ndev), ndev);
+       }
+       if (err)
+               goto err_hw;
+
+       if (gpio_is_valid(priv->link_gpio)) {
+               char *link_name = devm_kzalloc(dev, 16, GFP_KERNEL);
+
+               if (!link_name) {
+                       err = -ENOMEM;
+                       goto err_gpio;
+               }
+               snprintf(link_name, 16, "%s-link", netdev_name(ndev));
+               priv->link_irq = gpio_to_irq(priv->link_gpio);
+               if (request_any_context_irq(priv->link_irq, w5100_detect_link,
+                                           IRQF_TRIGGER_RISING |
+                                           IRQF_TRIGGER_FALLING,
+                                           link_name, priv->ndev) < 0)
+                       priv->link_gpio = -EINVAL;
+       }
 
        return 0;
 
-err_hw_probe:
+err_gpio:
+       free_irq(priv->irq, ndev);
+err_hw:
+       destroy_workqueue(priv->xfer_wq);
+err_wq:
        unregister_netdev(ndev);
 err_register:
        free_netdev(ndev);
        return err;
 }
+EXPORT_SYMBOL_GPL(w5100_probe);
 
-static int w5100_remove(struct platform_device *pdev)
+int w5100_remove(struct device *dev)
 {
-       struct net_device *ndev = platform_get_drvdata(pdev);
+       struct net_device *ndev = dev_get_drvdata(dev);
        struct w5100_priv *priv = netdev_priv(ndev);
 
        w5100_hw_reset(priv);
@@ -743,16 +1112,21 @@ static int w5100_remove(struct platform_device *pdev)
        if (gpio_is_valid(priv->link_gpio))
                free_irq(priv->link_irq, ndev);
 
+       flush_work(&priv->setrx_work);
+       flush_work(&priv->restart_work);
+       flush_workqueue(priv->xfer_wq);
+       destroy_workqueue(priv->xfer_wq);
+
        unregister_netdev(ndev);
        free_netdev(ndev);
        return 0;
 }
+EXPORT_SYMBOL_GPL(w5100_remove);
 
 #ifdef CONFIG_PM_SLEEP
 static int w5100_suspend(struct device *dev)
 {
-       struct platform_device *pdev = to_platform_device(dev);
-       struct net_device *ndev = platform_get_drvdata(pdev);
+       struct net_device *ndev = dev_get_drvdata(dev);
        struct w5100_priv *priv = netdev_priv(ndev);
 
        if (netif_running(ndev)) {
@@ -766,8 +1140,7 @@ static int w5100_suspend(struct device *dev)
 
 static int w5100_resume(struct device *dev)
 {
-       struct platform_device *pdev = to_platform_device(dev);
-       struct net_device *ndev = platform_get_drvdata(pdev);
+       struct net_device *ndev = dev_get_drvdata(dev);
        struct w5100_priv *priv = netdev_priv(ndev);
 
        if (netif_running(ndev)) {
@@ -783,15 +1156,15 @@ static int w5100_resume(struct device *dev)
 }
 #endif /* CONFIG_PM_SLEEP */
 
-static SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume);
+SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume);
+EXPORT_SYMBOL_GPL(w5100_pm_ops);
 
-static struct platform_driver w5100_driver = {
+static struct platform_driver w5100_mmio_driver = {
        .driver         = {
                .name   = DRV_NAME,
                .pm     = &w5100_pm_ops,
        },
-       .probe          = w5100_probe,
-       .remove         = w5100_remove,
+       .probe          = w5100_mmio_probe,
+       .remove         = w5100_mmio_remove,
 };
-
-module_platform_driver(w5100_driver);
+module_platform_driver(w5100_mmio_driver);