diff mbox

[v2] ethernet driver for the WIZnet W5300 chip

Message ID 1332149037-12025-1-git-send-email-msink@permonline.ru
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Mike Sinkovsky March 19, 2012, 9:23 a.m. UTC
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@permonline.ru>
---
 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


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Ben Hutchings March 20, 2012, 6:08 p.m. UTC | #1
On Mon, 2012-03-19 at 14:23 +0500, Mike Sinkovsky wrote:
> 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.
[...]
> +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));
[...]

Nitpick: if you don't have firmware, don't set fw_version at all.

Ben.
Mike Sinkovsky March 21, 2012, 6:20 a.m. UTC | #2
21.03.2012 0:08, Ben Hutchings написал:
> +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));
> [...]
>
> Nitpick: if you don't have firmware, don't set fw_version at all.
>
> Ben.

Then ethtool prints empty string as firmware version, I think "N/A" is 
more intuitive.
Can will remove, if it is preferred way.


But I have a question to networking guru's:

This chip have FRAME_SIZE limited to 1514, including eth header.
So, for 802.1Q vlan packets mtu must be set to 1496.
For now we handle this from userspace, but I think this is wrong in general
- handling hardware bugs and limitations is kernels job.

Can driver somehow limit mtu for slave vlan devices to 1496, but still 
use 1500
for plain ethernet?
->ndo_change_mtu is called for master device only, not for it's slaves.

(and sorry for my russian english, just in case)

---
Mike



--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Florian Fainelli March 21, 2012, 2:49 p.m. UTC | #3
Hi,

Le 03/19/12 10:23, Mike Sinkovsky a écrit :
> 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@permonline.ru>
> ---
>   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@permonline.ru>
> + *
> + * 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@permonline.ru>");
> +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

Looks like you don't have to make such decisions at compile-time. Since 
it is a platform driver, better supply this through platform_data instead.

> +
> +#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;
> +	}

Please implement phylib to properly report the link state to the 
networking stack and ethtool.

> +
> +	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;

Allow platform_data to pass a valid MAC address to this driver instead 
of defaulting to random unconditionnaly.

> +	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@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mike Sinkovsky March 22, 2012, 6:58 a.m. UTC | #4
21.03.2012 20:49, Florian Fainelli wrote:
>> +#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
> Looks like you don't have to make such decisions at compile-time. 
> Since it is a platform driver, better supply this through 
> platform_data instead.
Interface bus mode can be selected in .config (make menuconfig etc.)
If don't selected explicitly in .config, platform_device.resource[] is 
used in runtime to setup callbacks.
This hack was done for performance reasons, but may be not necessary, 
I'm not sure.


> Please implement phylib to properly report the link state to the 
> networking stack and ethtool.
This chip don't allow any access to phy interface, it completely hidden 
for host processor.
The only way I found - to wire pin LINK from W5300 to some GPIO, then 
ise RIGING/FALLING interrupt for tthat GPIO:
> +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;
> +}
Isn't it enough for networking stack?


> Allow platform_data to pass a valid MAC address to this driver instead 
> of defaulting to random unconditionnaly.
>
Do you mean - create .h file in /include/linux/platform_data/ ?
Ok, will do in next version.

Thanks.

---
Mike


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ben Hutchings March 22, 2012, 4:57 p.m. UTC | #5
On Wed, 2012-03-21 at 12:20 +0600, Mike Sinkovsky wrote:
> 21.03.2012 0:08, Ben Hutchings написал:
> > +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));
> > [...]
> >
> > Nitpick: if you don't have firmware, don't set fw_version at all.
> >
> > Ben.
> 
> Then ethtool prints empty string as firmware version, I think "N/A" is 
> more intuitive.

Well, we can replace it with "N/A" in ethtool if that's what people want
to see.  But it should not be necessary for every driver that can't
report a firmware version to write this special string.

> Can will remove, if it is preferred way.

It is, and the various other drivers that used this string have recently
been changed.

> But I have a question to networking guru's:
> 
> This chip have FRAME_SIZE limited to 1514, including eth header.
> So, for 802.1Q vlan packets mtu must be set to 1496.
>
> For now we handle this from userspace, but I think this is wrong in general
> - handling hardware bugs and limitations is kernels job.
> 
> Can driver somehow limit mtu for slave vlan devices to 1496, but still 
> use 1500
> for plain ethernet?
> ->ndo_change_mtu is called for master device only, not for it's slaves.

I don't think it's possible to do this at the moment.  You can only set
NETIF_F_VLAN_CHALLENGED, which unfortunately disables VLAN sub-devices
entirely.

> (and sorry for my russian english, just in case)

Not to worry - your writing is quite clear.

Ben.
diff mbox

Patch

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@permonline.ru>
+ *
+ * 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@permonline.ru>");
+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);