[PATCH v2] ethernet driver for the WIZnet W5300 chip
From: Mike Sinkovsky
Date: Mon Mar 19 2012 - 05:24:56 EST
Based on original driver from chip manufacturer, but with many cleanups.
Hope now it is near to mainline kernel quality.
Tested and used in production with Blackfin BF531 embedded processor.
Signed-off-by: Mike Sinkovsky <msink@xxxxxxxxxxxxx>
---
v2:
- corrected handling of NET_ADDR_RANDOM flag
- support for WIZNET_BUS_ANY mode
- link detection using gpio
- registers read using ethtool
- more cleanups
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/wiznet/Kconfig | 61 +++
drivers/net/ethernet/wiznet/Makefile | 1 +
drivers/net/ethernet/wiznet/w5300.c | 699 ++++++++++++++++++++++++++++++++++
5 files changed, 763 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ethernet/wiznet/Kconfig
create mode 100644 drivers/net/ethernet/wiznet/Makefile
create mode 100644 drivers/net/ethernet/wiznet/w5300.c
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 3474a61..e87313f 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -173,6 +173,7 @@ source "drivers/net/ethernet/tile/Kconfig"
source "drivers/net/ethernet/toshiba/Kconfig"
source "drivers/net/ethernet/tundra/Kconfig"
source "drivers/net/ethernet/via/Kconfig"
+source "drivers/net/ethernet/wiznet/Kconfig"
source "drivers/net/ethernet/xilinx/Kconfig"
source "drivers/net/ethernet/xircom/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 08d5f03..d24db66 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -72,5 +72,6 @@ obj-$(CONFIG_TILE_NET) += tile/
obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/
obj-$(CONFIG_NET_VENDOR_TUNDRA) += tundra/
obj-$(CONFIG_NET_VENDOR_VIA) += via/
+obj-$(CONFIG_NET_VENDOR_WIZNET) += wiznet/
obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/
obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/
diff --git a/drivers/net/ethernet/wiznet/Kconfig b/drivers/net/ethernet/wiznet/Kconfig
new file mode 100644
index 0000000..748fa3b
--- /dev/null
+++ b/drivers/net/ethernet/wiznet/Kconfig
@@ -0,0 +1,61 @@
+#
+# WIZnet device configuration
+#
+
+config NET_VENDOR_WIZNET
+ bool "WIZnet devices"
+ default y
+ ---help---
+ If you have a network (Ethernet) card belonging to this class, say Y
+ and read the Ethernet-HOWTO, available from
+ <http://www.tldp.org/docs.html#howto>.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about WIZnet devices. If you say Y, you will be asked
+ for your specific card in the following questions.
+
+if NET_VENDOR_WIZNET
+
+config WIZNET_W5300
+ tristate "WIZnet W5300 Ethernet support"
+ depends on ARM || BLACKFIN
+ ---help---
+ Support for WIZnet W5300 chips.
+
+ W5300 is a single chip with integrated 10/100 Ethernet MAC,
+ PHY and hardware TCP/IP stack, but this driver is limited to
+ the MAC and PHY functions only, onchip TCP/IP is unused.
+
+ To compile this driver as a module, choose M here: the module
+ will be called w5300.
+
+choice
+ prompt "WIZnet interface mode"
+ depends on NET_VENDOR_WIZNET
+ default WIZNET_BUS_ANY
+
+config WIZNET_BUS_DIRECT
+ bool "Direct address bus mode"
+ ---help---
+ In direct address mode host system can directly access W5300 registers
+ after mapping to Memory-mapped I/O Space.
+ 0x400 bytes are required for memory space.
+
+config WIZNET_BUS_INDIRECT
+ bool "Indirect address bus mode"
+ ---help---
+ In indirect address mode host system indirectly accesses registers by
+ using Indirect Mode Address Register (IDM_AR) and Indirect Mode Data
+ Register (IDM_DR), which are directly mapped to Memory-mapped I/O Space.
+ Only 0x06 bytes are required for memory space.
+
+config WIZNET_BUS_ANY
+ bool "Select interface mode in runtime"
+ ---help---
+ If interface mode is unknown in compile time, you can selectied it
+ in runtime.
+ Performance may decrease compared to explicitly selected bus mode.
+endchoice
+
+endif # NET_VENDOR_WIZNET
diff --git a/drivers/net/ethernet/wiznet/Makefile b/drivers/net/ethernet/wiznet/Makefile
new file mode 100644
index 0000000..88e0a3e
--- /dev/null
+++ b/drivers/net/ethernet/wiznet/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_WIZNET_W5300) += w5300.o
diff --git a/drivers/net/ethernet/wiznet/w5300.c b/drivers/net/ethernet/wiznet/w5300.c
new file mode 100644
index 0000000..8f7adfa
--- /dev/null
+++ b/drivers/net/ethernet/wiznet/w5300.c
@@ -0,0 +1,699 @@
+/*
+ * Ethernet driver for the WIZnet W5300 chip.
+ *
+ * Copyright (C) 2008-2009 WIZnet Co.,Ltd.
+ * Copyright (C) 2011 Taehun Kim <kth3321 <at> gmail.com>
+ * Copyright (C) 2012 Mike Sinkovsky <msink@xxxxxxxxxxxxx>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/skbuff.h>
+
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+
+#define DRV_NAME "WIZnet W5300"
+#define DRV_VERSION "2012-03-19"
+
+MODULE_DESCRIPTION(DRV_NAME "Ethernet driver v" DRV_VERSION);
+MODULE_AUTHOR("Mike Sinkovsky <msink@xxxxxxxxxxxxx>");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_LICENSE("GPL");
+
+/*
+ * Frame size is hardwired to 1514 bytes,
+ * and MTU for 802.1Q frames must me set to 1496
+ */
+#define W5300_FRAME_SIZE 1514
+
+/*
+ * Device driver private data structure
+ */
+struct w5300_private {
+ void __iomem *base;
+ int irq;
+ int link;
+
+ spinlock_t reg_lock;
+ bool promisc_mode;
+ u16 (*read_u16) (struct w5300_private *priv, u16 addr);
+ void (*write_u16)(struct w5300_private *priv, u16 addr, u16 data);
+
+ struct napi_struct napi;
+ struct net_device *ndev;
+};
+
+/************************************************************************
+ *
+ * Lowlevel I/O functions
+ *
+ ***********************************************************************/
+
+/*
+ * In direct address mode host system can directly access W5300 registers
+ * after mapping to Memory-mapped I/O Space.
+ *
+ * 0x400 bytes are required for memory space.
+ */
+static inline u16
+read_u16_direct(struct w5300_private *priv, u16 addr)
+{
+ return ioread16(priv->base + addr);
+}
+
+static inline void
+write_u16_direct(struct w5300_private *priv, u16 addr, u16 data)
+{
+ iowrite16(data, priv->base + addr);
+ mmiowb();
+}
+
+/*
+ * In indirect address mode host system indirectly accesses registers by
+ * using Indirect Mode Address Register (IDM_AR) and Indirect Mode Data
+ * Register (IDM_DR), which are directly mapped to Memory-mapped I/O Space.
+ * Mode Register (MR) is directly accessible.
+ *
+ * Only 0x06 bytes are required for memory space.
+ */
+#define W5300_MR 0x00 /* Mode Register offset */
+#define W5300_IDM_AR 0x02 /* Indirect Mode Address Register offset */
+#define W5300_IDM_DR 0x04 /* Indirect Mode Data Register offset */
+
+static inline u16
+read_u16_indirect(struct w5300_private *priv, u16 addr)
+{
+ unsigned long flags;
+ u16 data;
+
+ spin_lock_irqsave(&priv->reg_lock, flags);
+ write_u16_direct(priv, W5300_IDM_AR, addr);
+ data = read_u16_direct(priv, W5300_IDM_DR);
+ spin_unlock_irqrestore(&priv->reg_lock, flags);
+
+ return data;
+}
+
+static inline void
+write_u16_indirect(struct w5300_private *priv, u16 addr, u16 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->reg_lock, flags);
+ write_u16_direct(priv, W5300_IDM_AR, addr);
+ write_u16_direct(priv, W5300_IDM_DR, data);
+ spin_unlock_irqrestore(&priv->reg_lock, flags);
+}
+
+#if defined(CONFIG_WIZNET_BUS_DIRECT)
+#define detect_bus_mode(priv, mem_size) do {} while(0)
+#define read_reg_u16 read_u16_direct
+#define write_reg_u16 write_u16_direct
+
+#elif defined(CONFIG_WIZNET_BUS_INDIRECT)
+#define detect_bus_mode(priv, mem_size) do {} while(0)
+#define read_reg_u16 read_u16_indirect
+#define write_reg_u16 write_u16_indirect
+
+#else /* CONFIG_WIZNET_BUS_ANY */
+static inline void
+detect_bus_mode(struct w5300_private *priv, u16 mem_size)
+{
+ if (mem_size < 0x400) {
+ netdev_info(priv->ndev, "bus mode: indirect\n");
+ priv->read_u16 = read_u16_indirect;
+ priv->write_u16 = write_u16_indirect;
+ } else {
+ netdev_info(priv->ndev, "bus mode: direct\n");
+ priv->read_u16 = read_u16_direct;
+ priv->write_u16 = write_u16_direct;
+ }
+}
+
+static inline u16
+read_reg_u16(struct w5300_private *priv, u16 addr)
+{
+ return priv->read_u16(priv, addr);
+}
+
+static inline void
+write_reg_u16(struct w5300_private *priv, u16 addr, u16 data)
+{
+ priv->write_u16(priv, addr, data);
+}
+#endif
+
+static inline u32
+read_reg_u32(struct w5300_private *priv, u16 addr)
+{
+ u32 data;
+ data = read_reg_u16(priv, addr) << 16;
+ data |= read_reg_u16(priv, addr + 2);
+ return data;
+}
+
+static inline void
+write_reg_u32(struct w5300_private *priv, u16 addr, u32 data)
+{
+ write_reg_u16(priv, addr, data >> 16);
+ write_reg_u16(priv, addr + 2, data);
+}
+
+static inline void write_MR(struct w5300_private *priv, u16 data)
+{
+ write_u16_direct(priv, W5300_MR, data);
+}
+
+#define DEFINE_REG_RD(ADDR, TYPE, NAME) \
+static inline TYPE read_##NAME(struct w5300_private *priv) \
+{ \
+ return read_reg_##TYPE(priv, ADDR); \
+}
+#define DEFINE_REG_WR(ADDR, TYPE, NAME) \
+static inline void write_##NAME(struct w5300_private *priv, TYPE data) \
+{ \
+ write_reg_##TYPE(priv, ADDR, data); \
+}
+#define DEFINE_REG_RW(ADDR, TYPE, NAME) \
+ DEFINE_REG_RD(ADDR, TYPE, NAME) \
+ DEFINE_REG_WR(ADDR, TYPE, NAME)
+
+DEFINE_REG_RW(0x002, u16, IR) /* Interrupt Register */
+DEFINE_REG_WR(0x004, u16, IMR) /* Interrupt Mask Register */
+DEFINE_REG_WR(0x008, u32, SHARL) /* Source MAC address (0123) */
+DEFINE_REG_WR(0x00c, u16, SHARH) /* Source MAC address (45) */
+DEFINE_REG_WR(0x020, u32, TMSRL) /* Transmit Memory Size (0123) */
+DEFINE_REG_WR(0x024, u32, TMSRH) /* Transmit Memory Size (4567) */
+DEFINE_REG_WR(0x028, u32, RMSRL) /* Receive Memory Size (0123) */
+DEFINE_REG_WR(0x02c, u32, RMSRH) /* Receive Memory Size (4567) */
+DEFINE_REG_WR(0x030, u16, MTYPE) /* Memory Type */
+DEFINE_REG_RD(0x0fe, u16, IDR) /* Chip ID register (=0x5300) */
+DEFINE_REG_WR(0x200, u16, S0_MR) /* S0 Mode Register */
+DEFINE_REG_RW(0x202, u16, S0_CR) /* S0 Command Register */
+DEFINE_REG_WR(0x204, u16, S0_IMR) /* S0 Interrupt Mask Register */
+DEFINE_REG_RW(0x206, u16, S0_IR) /* S0 Interrupt Register */
+DEFINE_REG_RD(0x208, u16, S0_SSR) /* S0 Socket Status Register */
+DEFINE_REG_WR(0x220, u32, S0_TX_WRSR) /* S0 TX Write Size Register */
+DEFINE_REG_RD(0x224, u32, S0_TX_FSR) /* S0 TX Free Size Register */
+DEFINE_REG_RD(0x228, u32, S0_RX_RSR) /* S0 Received data Size */
+DEFINE_REG_WR(0x22e, u16, S0_TX_FIFO) /* S0 Transmit FIFO */
+DEFINE_REG_RD(0x230, u16, S0_RX_FIFO) /* S0 Receive FIFO */
+
+/* Mode Register values */
+#define MR_DBW (1 << 15) /* Data bus width */
+#define MR_MPF (1 << 14) /* Mac layer pause frame */
+#define MR_WDF(n) (n << 11) /* Write data fetch time */
+#define MR_RDH (1 << 10) /* Read data hold time */
+#define MR_FS (1 << 8) /* FIFO swap */
+#define MR_RST (1 << 7) /* S/W reset */
+#define MR_PB (1 << 4) /* Ping block */
+#define MR_DBS (1 << 2) /* Data bus swap */
+#define MR_IND (1 << 0) /* Indirect mode */
+
+#ifdef CONFIG_WIZNET_BUS_INDIRECT
+#define MR_VALUE (MR_WDF(7) | MR_PB | MR_IND)
+#else
+#define MR_VALUE (MR_WDF(7) | MR_PB)
+#endif
+
+/* IR/IMR register values */
+#define IR_S0 0x01 /* S0 interrupt */
+
+/* S0_MR register values */
+#define S0_MR_CLOSE 0x00 /* Close mode */
+#define S0_MR_MACRAW 0x04 /* MAC RAW mode (promiscous) */
+#define S0_MR_MACRAW_MF 0x44 /* MAC RAW mode (filtered) */
+
+/* S0_CR register values */
+#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 */
+
+/* S0_IR/S0_IMR register values */
+#define S0_IR_RECV 0x04 /* Receive interrupt */
+
+static void read_fifo(struct w5300_private *priv, u8 *data, int len)
+{
+ for (; len > 0; len -= 2) {
+ u16 fifo = read_S0_RX_FIFO(priv);
+ *data++ = fifo >> 8;
+ *data++ = fifo;
+ }
+}
+
+static void write_fifo(struct w5300_private *priv, u8 *data, int len)
+{
+ for (; len > 0; len -= 2) {
+ u16 fifo = *data++ << 8;
+ fifo |= *data++;
+ write_S0_TX_FIFO(priv, fifo);
+ }
+}
+
+static inline int send_command(struct w5300_private *priv, u16 cmd)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+ write_S0_CR(priv, cmd);
+
+ while (read_S0_CR(priv) != 0) {
+ if (time_after(jiffies, timeout))
+ return -EIO;
+ cpu_relax();
+ }
+
+ return 0;
+}
+
+static void write_macaddr(struct w5300_private *priv)
+{
+ struct net_device *ndev = priv->ndev;
+ write_SHARL(priv, ndev->dev_addr[0] << 24 |
+ ndev->dev_addr[1] << 16 |
+ ndev->dev_addr[2] << 8 |
+ ndev->dev_addr[3]);
+ write_SHARH(priv, ndev->dev_addr[4] << 8 |
+ ndev->dev_addr[5]);
+}
+
+static void reset_chip(struct w5300_private *priv)
+{
+ write_MR(priv, MR_RST);
+ mdelay(5);
+ write_MR(priv, MR_VALUE);
+
+ write_IMR(priv, 0);
+
+ /*
+ * Configure 128K of internal memory
+ * as 64K RX fifo and 64K TX fifo
+ */
+ write_RMSRL(priv, 64 << 24);
+ write_RMSRH(priv, 0);
+ write_TMSRL(priv, 64 << 24);
+ write_TMSRH(priv, 0);
+ write_MTYPE(priv, 0x00ff);
+
+ write_macaddr(priv);
+}
+
+/***********************************************************************
+ *
+ * Device driver functions / callbacks
+ *
+ ***********************************************************************/
+
+static void w5300_get_drvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *info)
+{
+ strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
+ strlcpy(info->version, DRV_VERSION, sizeof(info->version));
+ strlcpy(info->fw_version, "N/A", sizeof(info->fw_version));
+ strlcpy(info->bus_info, dev_name(ndev->dev.parent),
+ sizeof(info->bus_info));
+}
+
+#define W5300_REGS_LEN 0x400
+
+static int w5300_get_regs_len(struct net_device *ndev)
+{
+ return W5300_REGS_LEN;
+}
+
+static void w5300_get_regs(struct net_device *ndev,
+ struct ethtool_regs *regs, void *_buf)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+ u8 *buf = _buf;
+ u16 addr;
+ u16 data;
+
+ regs->version = 1;
+ for (addr = 0; addr < W5300_REGS_LEN; addr += 2) {
+ switch (addr & 0x23f) {
+ case 0x22e: /* don't read TX_FIFO register! */
+ case 0x230: /* don't read RX_FIFO register! */
+ data = 0xffff;
+ break;
+ default:
+ data = read_reg_u16(priv, addr);
+ break;
+ }
+ *buf++ = data >> 8;
+ *buf++ = data;
+ }
+}
+
+static void w5300_tx_timeout(struct net_device *ndev)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+
+ netdev_err(ndev, "Transmit timeout!\n");
+
+ ndev->stats.tx_errors++;
+ reset_chip(priv);
+ netif_wake_queue(ndev);
+}
+
+static int w5300_start_tx(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+
+ if (unlikely(read_S0_TX_FSR(priv) < skb->len)) {
+ ndev->stats.tx_dropped++;
+ return NETDEV_TX_BUSY;
+ }
+
+ write_fifo(priv, skb->data, skb->len);
+ write_S0_TX_WRSR(priv, skb->len);
+ send_command(priv, S0_CR_SEND);
+
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += skb->len;
+ dev_kfree_skb(skb);
+
+ return NETDEV_TX_OK;
+}
+
+static int w5300_napi_poll(struct napi_struct *napi, int budget)
+{
+ struct w5300_private *priv =
+ container_of(napi, struct w5300_private, napi);
+ struct net_device *ndev = priv->ndev;
+ struct sk_buff *skb;
+ u16 rx_frame_size;
+ int rx_count;
+
+ for (rx_count = 0; rx_count < budget; rx_count++) {
+ u32 rx_fifo_size = read_S0_RX_RSR(priv);
+ if (rx_fifo_size == 0)
+ break;
+
+ rx_frame_size = read_S0_RX_FIFO(priv);
+
+ skb = netdev_alloc_skb(ndev, NET_IP_ALIGN +
+ roundup(rx_frame_size, 2));
+ if (unlikely(!skb)) {
+ int len = rx_frame_size + 4;
+ for (; len > 0; len -= 2)
+ read_S0_RX_FIFO(priv);
+ ndev->stats.rx_dropped++;
+ return -ENOMEM;
+ }
+
+ skb_reserve(skb, NET_IP_ALIGN);
+ skb_put(skb, rx_frame_size);
+ read_fifo(priv, skb->data, rx_frame_size);
+ read_S0_RX_FIFO(priv);
+ read_S0_RX_FIFO(priv);
+
+ skb->protocol = eth_type_trans(skb, ndev);
+ netif_receive_skb(skb);
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += rx_frame_size;
+ }
+
+ if (rx_count < budget) {
+ write_IMR(priv, IR_S0);
+ napi_complete(napi);
+ }
+
+ return rx_count;
+}
+
+static irqreturn_t w5300_start_rx(int irq, void *ndev_instance)
+{
+ struct net_device *ndev = ndev_instance;
+ struct w5300_private *priv = netdev_priv(ndev);
+
+ write_S0_IR(priv, S0_IR_RECV);
+
+ if (napi_schedule_prep(&priv->napi)) {
+ write_IMR(priv, 0);
+ __napi_schedule(&priv->napi);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t w5300_detect_link(int irq, void *ndev_instance)
+{
+ struct net_device *ndev = ndev_instance;
+ struct w5300_private *priv = netdev_priv(ndev);
+
+ if (netif_running(ndev)) {
+ if (gpio_get_value(priv->link) == 0)
+ netif_carrier_off(ndev);
+ else
+ netif_carrier_on(ndev);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void w5300_set_rx_mode(struct net_device *ndev)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+ bool set_promisc = (ndev->flags & IFF_PROMISC) != 0;
+ int mode = set_promisc ? S0_MR_MACRAW : S0_MR_MACRAW_MF;
+
+ if (priv->promisc_mode != set_promisc) {
+ priv->promisc_mode = set_promisc;
+ write_S0_MR(priv, mode);
+ send_command(priv, S0_CR_OPEN);
+ }
+}
+
+static int w5300_set_macaddr(struct net_device *ndev, void *addr)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+ struct sockaddr *sock_addr = addr;
+
+ if (!is_valid_ether_addr(sock_addr->sa_data))
+ return -EADDRNOTAVAIL;
+ memcpy(ndev->dev_addr, sock_addr->sa_data, ETH_ALEN);
+ ndev->addr_assign_type &= ~NET_ADDR_RANDOM;
+ write_macaddr(priv);
+ return 0;
+}
+
+static int w5300_open(struct net_device *ndev)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+ int mode = priv->promisc_mode ? S0_MR_MACRAW : S0_MR_MACRAW_MF;
+
+ if (!is_valid_ether_addr(ndev->dev_addr))
+ return -EINVAL;
+
+ write_S0_IMR(priv, S0_IR_RECV);
+ write_S0_MR(priv, mode);
+ send_command(priv, S0_CR_OPEN);
+ write_IMR(priv, IR_S0);
+
+ napi_enable(&priv->napi);
+ netif_start_queue(ndev);
+ if (priv->link < 0 || gpio_get_value(priv->link))
+ netif_carrier_on(ndev);
+ return 0;
+}
+
+static int w5300_stop(struct net_device *ndev)
+{
+ struct w5300_private *priv = netdev_priv(ndev);
+
+ write_IMR(priv, 0);
+ write_S0_CR(priv, S0_CR_CLOSE);
+
+ netif_carrier_off(ndev);
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+ return 0;
+}
+
+static const struct ethtool_ops w5300_ethtool_ops = {
+ .get_drvinfo = w5300_get_drvinfo,
+ .get_regs_len = w5300_get_regs_len,
+ .get_regs = w5300_get_regs,
+};
+
+static const struct net_device_ops w5300_netdev_ops = {
+ .ndo_open = w5300_open,
+ .ndo_stop = w5300_stop,
+ .ndo_start_xmit = w5300_start_tx,
+ .ndo_tx_timeout = w5300_tx_timeout,
+ .ndo_set_rx_mode = w5300_set_rx_mode,
+ .ndo_set_mac_address = w5300_set_macaddr,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_change_mtu = eth_change_mtu,
+};
+
+static int __devinit w5300_hw_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct w5300_private *priv = netdev_priv(ndev);
+ const char *name = netdev_name(ndev);
+ struct resource *link;
+ struct resource *mem;
+ int mem_size;
+ int irq;
+ int ret;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem)
+ return -ENXIO;
+ mem_size = resource_size(mem);
+ if (!devm_request_mem_region(dev, mem->start, mem_size, name))
+ return -EBUSY;
+ priv->base = devm_ioremap(dev, mem->start, mem_size);
+ if (!priv->base)
+ return -EBUSY;
+
+ spin_lock_init(&priv->reg_lock);
+ detect_bus_mode(priv, mem_size);
+ reset_chip(priv);
+ if (read_IDR(priv) != 0x5300)
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ ret = devm_request_irq(dev, irq, w5300_start_rx,
+ IRQ_TYPE_LEVEL_LOW, name, ndev);
+ if (ret < 0)
+ return ret;
+ priv->irq = irq;
+
+ link = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!link) {
+ priv->link = -1;
+ } else {
+ char *link_name = devm_kzalloc(dev, 16, GFP_KERNEL);
+ snprintf(link_name, 16, "%s-link", name);
+ priv->link = link->start;
+ if (request_irq(gpio_to_irq(priv->link), w5300_detect_link,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ link_name, priv->ndev) < 0)
+ priv->link = -1;
+ }
+
+ netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, irq);
+ return 0;
+}
+
+static int __devinit w5300_probe(struct platform_device *pdev)
+{
+ struct net_device *ndev;
+ struct w5300_private *priv;
+ int ret;
+
+ ndev = alloc_etherdev(sizeof(*priv));
+ if (!ndev)
+ return -ENOMEM;
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+ platform_set_drvdata(pdev, ndev);
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+
+ ether_setup(ndev);
+ ndev->netdev_ops = &w5300_netdev_ops;
+ ndev->ethtool_ops = &w5300_ethtool_ops;
+ ndev->watchdog_timeo = 2 * HZ;
+ netif_napi_add(ndev, &priv->napi, w5300_napi_poll, 16);
+ ret = register_netdev(ndev);
+ if (ret < 0)
+ goto fail;
+
+ random_ether_addr(ndev->dev_addr);
+ ndev->addr_assign_type |= NET_ADDR_RANDOM;
+ ret = w5300_hw_probe(pdev);
+ if (ret < 0)
+ goto fail;
+
+ return 0;
+
+fail: netdev_info(ndev, "probe failed (%d)\n", ret);
+ unregister_netdev(ndev);
+ free_netdev(ndev);
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+}
+
+static int __devexit w5300_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+
+ unregister_netdev(ndev);
+ free_netdev(ndev);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int w5300_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct w5300_private *priv = netdev_priv(ndev);
+
+ if (netif_running(ndev)) {
+ netif_carrier_off(ndev);
+ netif_device_detach(ndev);
+
+ write_IMR(priv, 0);
+ send_command(priv, S0_CR_CLOSE);
+ }
+ return 0;
+}
+
+static int w5300_resume(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct w5300_private *priv = netdev_priv(ndev);
+ int mode = priv->promisc_mode ? S0_MR_MACRAW : S0_MR_MACRAW_MF;
+
+ if (netif_running(ndev)) {
+ reset_chip(priv);
+ write_S0_MR(priv, mode);
+ send_command(priv, S0_CR_OPEN);
+ write_IMR(priv, IR_S0);
+
+ netif_device_attach(ndev);
+ if (priv->link < 0 || gpio_get_value(priv->link))
+ netif_carrier_on(ndev);
+ }
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct platform_driver wiznet_w5300_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = w5300_probe,
+ .remove = __devexit_p(w5300_remove),
+#ifdef CONFIG_PM
+ .suspend = w5300_suspend,
+ .resume = w5300_resume,
+#endif
+};
+
+module_platform_driver(wiznet_w5300_driver);
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/