diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/net/ethernet/wiznet | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/net/ethernet/wiznet')
-rw-r--r-- | drivers/net/ethernet/wiznet/Kconfig | 87 | ||||
-rw-r--r-- | drivers/net/ethernet/wiznet/Makefile | 4 | ||||
-rw-r--r-- | drivers/net/ethernet/wiznet/w5100-spi.c | 491 | ||||
-rw-r--r-- | drivers/net/ethernet/wiznet/w5100.c | 1278 | ||||
-rw-r--r-- | drivers/net/ethernet/wiznet/w5100.h | 36 | ||||
-rw-r--r-- | drivers/net/ethernet/wiznet/w5300.c | 689 |
6 files changed, 2585 insertions, 0 deletions
diff --git a/drivers/net/ethernet/wiznet/Kconfig b/drivers/net/ethernet/wiznet/Kconfig new file mode 100644 index 000000000..4bac2ad2d --- /dev/null +++ b/drivers/net/ethernet/wiznet/Kconfig @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# WIZnet devices configuration +# + +config NET_VENDOR_WIZNET + bool "WIZnet devices" + depends on HAS_IOMEM + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + + 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_W5100 + tristate "WIZnet W5100 Ethernet support" + depends on HAS_IOMEM + help + Support for WIZnet W5100 chips. + + W5100 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 w5100. + +config WIZNET_W5300 + tristate "WIZnet W5300 Ethernet support" + depends on HAS_IOMEM + 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 WIZNET_W5100 || WIZNET_W5300 + default WIZNET_BUS_ANY + +config WIZNET_BUS_DIRECT + bool "Direct address bus mode" + help + In direct address mode host system can directly access all registers + after mapping to Memory-Mapped I/O space. + +config WIZNET_BUS_INDIRECT + bool "Indirect address bus mode" + help + In indirect address mode host system indirectly accesses registers + using Indirect Mode Address Register and Indirect Mode Data Register, + which are directly mapped to Memory-Mapped I/O space. + +config WIZNET_BUS_ANY + bool "Select interface mode in runtime" + help + If interface mode is unknown in compile time, it can be selected + in runtime from board/platform resources configuration. + + Performance may decrease compared to explicitly selected bus mode. +endchoice + +config WIZNET_W5100_SPI + tristate "WIZnet W5100/W5200/W5500 Ethernet support for SPI mode" + depends on WIZNET_BUS_ANY && WIZNET_W5100 + depends on SPI + help + In SPI mode host system accesses registers using SPI protocol + (mode 0) on the SPI bus. + + Performance decreases compared to other bus interface mode. + In W5100 SPI mode, burst READ/WRITE processing are not provided. + + To compile this driver as a module, choose M here: the module + will be called w5100-spi. + +endif # NET_VENDOR_WIZNET diff --git a/drivers/net/ethernet/wiznet/Makefile b/drivers/net/ethernet/wiznet/Makefile new file mode 100644 index 000000000..78104f0bf --- /dev/null +++ b/drivers/net/ethernet/wiznet/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_WIZNET_W5100) += w5100.o +obj-$(CONFIG_WIZNET_W5100_SPI) += w5100-spi.o +obj-$(CONFIG_WIZNET_W5300) += w5300.o diff --git a/drivers/net/ethernet/wiznet/w5100-spi.c b/drivers/net/ethernet/wiznet/w5100-spi.c new file mode 100644 index 000000000..7c5279627 --- /dev/null +++ b/drivers/net/ethernet/wiznet/w5100-spi.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Ethernet driver for the WIZnet W5100/W5200/W5500 chip. + * + * Copyright (C) 2016 Akinobu Mita <akinobu.mita@gmail.com> + * + * Datasheet: + * http://www.wiznet.co.kr/wp-content/uploads/wiznethome/Chip/W5100/Document/W5100_Datasheet_v1.2.6.pdf + * http://wiznethome.cafe24.com/wp-content/uploads/wiznethome/Chip/W5200/Documents/W5200_DS_V140E.pdf + * http://wizwiki.net/wiki/lib/exe/fetch.php?media=products:w5500:w5500_ds_v106e_141230.pdf + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/of_net.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> + +#include "w5100.h" + +#define W5100_SPI_WRITE_OPCODE 0xf0 +#define W5100_SPI_READ_OPCODE 0x0f + +static int w5100_spi_read(struct net_device *ndev, u32 addr) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[3] = { W5100_SPI_READ_OPCODE, addr >> 8, addr & 0xff }; + u8 data; + int ret; + + ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1); + + return ret ? ret : data; +} + +static int w5100_spi_write(struct net_device *ndev, u32 addr, u8 data) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[4] = { W5100_SPI_WRITE_OPCODE, addr >> 8, addr & 0xff, data}; + + return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0); +} + +static int w5100_spi_read16(struct net_device *ndev, u32 addr) +{ + u16 data; + int ret; + + ret = w5100_spi_read(ndev, addr); + if (ret < 0) + return ret; + data = ret << 8; + ret = w5100_spi_read(ndev, addr + 1); + + return ret < 0 ? ret : data | ret; +} + +static int w5100_spi_write16(struct net_device *ndev, u32 addr, u16 data) +{ + int ret; + + ret = w5100_spi_write(ndev, addr, data >> 8); + if (ret) + return ret; + + return w5100_spi_write(ndev, addr + 1, data & 0xff); +} + +static int w5100_spi_readbulk(struct net_device *ndev, u32 addr, u8 *buf, + int len) +{ + int i; + + for (i = 0; i < len; i++) { + int ret = w5100_spi_read(ndev, addr + i); + + if (ret < 0) + return ret; + buf[i] = ret; + } + + return 0; +} + +static int w5100_spi_writebulk(struct net_device *ndev, u32 addr, const u8 *buf, + int len) +{ + int i; + + for (i = 0; i < len; i++) { + int ret = w5100_spi_write(ndev, addr + i, buf[i]); + + if (ret) + return ret; + } + + return 0; +} + +static const struct w5100_ops w5100_spi_ops = { + .may_sleep = true, + .chip_id = W5100, + .read = w5100_spi_read, + .write = w5100_spi_write, + .read16 = w5100_spi_read16, + .write16 = w5100_spi_write16, + .readbulk = w5100_spi_readbulk, + .writebulk = w5100_spi_writebulk, +}; + +#define W5200_SPI_WRITE_OPCODE 0x80 + +struct w5200_spi_priv { + /* Serialize access to cmd_buf */ + struct mutex cmd_lock; + + /* DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 cmd_buf[4] ____cacheline_aligned; +}; + +static struct w5200_spi_priv *w5200_spi_priv(struct net_device *ndev) +{ + return w5100_ops_priv(ndev); +} + +static int w5200_spi_init(struct net_device *ndev) +{ + struct w5200_spi_priv *spi_priv = w5200_spi_priv(ndev); + + mutex_init(&spi_priv->cmd_lock); + + return 0; +} + +static int w5200_spi_read(struct net_device *ndev, u32 addr) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[4] = { addr >> 8, addr & 0xff, 0, 1 }; + u8 data; + int ret; + + ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1); + + return ret ? ret : data; +} + +static int w5200_spi_write(struct net_device *ndev, u32 addr, u8 data) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[5] = { addr >> 8, addr & 0xff, W5200_SPI_WRITE_OPCODE, 1, data }; + + return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0); +} + +static int w5200_spi_read16(struct net_device *ndev, u32 addr) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[4] = { addr >> 8, addr & 0xff, 0, 2 }; + __be16 data; + int ret; + + ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, sizeof(data)); + + return ret ? ret : be16_to_cpu(data); +} + +static int w5200_spi_write16(struct net_device *ndev, u32 addr, u16 data) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[6] = { + addr >> 8, addr & 0xff, + W5200_SPI_WRITE_OPCODE, 2, + data >> 8, data & 0xff + }; + + return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0); +} + +static int w5200_spi_readbulk(struct net_device *ndev, u32 addr, u8 *buf, + int len) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + struct w5200_spi_priv *spi_priv = w5200_spi_priv(ndev); + struct spi_transfer xfer[] = { + { + .tx_buf = spi_priv->cmd_buf, + .len = sizeof(spi_priv->cmd_buf), + }, + { + .rx_buf = buf, + .len = len, + }, + }; + int ret; + + mutex_lock(&spi_priv->cmd_lock); + + spi_priv->cmd_buf[0] = addr >> 8; + spi_priv->cmd_buf[1] = addr; + spi_priv->cmd_buf[2] = len >> 8; + spi_priv->cmd_buf[3] = len; + ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); + + mutex_unlock(&spi_priv->cmd_lock); + + return ret; +} + +static int w5200_spi_writebulk(struct net_device *ndev, u32 addr, const u8 *buf, + int len) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + struct w5200_spi_priv *spi_priv = w5200_spi_priv(ndev); + struct spi_transfer xfer[] = { + { + .tx_buf = spi_priv->cmd_buf, + .len = sizeof(spi_priv->cmd_buf), + }, + { + .tx_buf = buf, + .len = len, + }, + }; + int ret; + + mutex_lock(&spi_priv->cmd_lock); + + spi_priv->cmd_buf[0] = addr >> 8; + spi_priv->cmd_buf[1] = addr; + spi_priv->cmd_buf[2] = W5200_SPI_WRITE_OPCODE | (len >> 8); + spi_priv->cmd_buf[3] = len; + ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); + + mutex_unlock(&spi_priv->cmd_lock); + + return ret; +} + +static const struct w5100_ops w5200_ops = { + .may_sleep = true, + .chip_id = W5200, + .read = w5200_spi_read, + .write = w5200_spi_write, + .read16 = w5200_spi_read16, + .write16 = w5200_spi_write16, + .readbulk = w5200_spi_readbulk, + .writebulk = w5200_spi_writebulk, + .init = w5200_spi_init, +}; + +#define W5500_SPI_BLOCK_SELECT(addr) (((addr) >> 16) & 0x1f) +#define W5500_SPI_READ_CONTROL(addr) (W5500_SPI_BLOCK_SELECT(addr) << 3) +#define W5500_SPI_WRITE_CONTROL(addr) \ + ((W5500_SPI_BLOCK_SELECT(addr) << 3) | BIT(2)) + +struct w5500_spi_priv { + /* Serialize access to cmd_buf */ + struct mutex cmd_lock; + + /* DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 cmd_buf[3] ____cacheline_aligned; +}; + +static struct w5500_spi_priv *w5500_spi_priv(struct net_device *ndev) +{ + return w5100_ops_priv(ndev); +} + +static int w5500_spi_init(struct net_device *ndev) +{ + struct w5500_spi_priv *spi_priv = w5500_spi_priv(ndev); + + mutex_init(&spi_priv->cmd_lock); + + return 0; +} + +static int w5500_spi_read(struct net_device *ndev, u32 addr) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[3] = { + addr >> 8, + addr, + W5500_SPI_READ_CONTROL(addr) + }; + u8 data; + int ret; + + ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1); + + return ret ? ret : data; +} + +static int w5500_spi_write(struct net_device *ndev, u32 addr, u8 data) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[4] = { + addr >> 8, + addr, + W5500_SPI_WRITE_CONTROL(addr), + data + }; + + return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0); +} + +static int w5500_spi_read16(struct net_device *ndev, u32 addr) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[3] = { + addr >> 8, + addr, + W5500_SPI_READ_CONTROL(addr) + }; + __be16 data; + int ret; + + ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, sizeof(data)); + + return ret ? ret : be16_to_cpu(data); +} + +static int w5500_spi_write16(struct net_device *ndev, u32 addr, u16 data) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + u8 cmd[5] = { + addr >> 8, + addr, + W5500_SPI_WRITE_CONTROL(addr), + data >> 8, + data + }; + + return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0); +} + +static int w5500_spi_readbulk(struct net_device *ndev, u32 addr, u8 *buf, + int len) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + struct w5500_spi_priv *spi_priv = w5500_spi_priv(ndev); + struct spi_transfer xfer[] = { + { + .tx_buf = spi_priv->cmd_buf, + .len = sizeof(spi_priv->cmd_buf), + }, + { + .rx_buf = buf, + .len = len, + }, + }; + int ret; + + mutex_lock(&spi_priv->cmd_lock); + + spi_priv->cmd_buf[0] = addr >> 8; + spi_priv->cmd_buf[1] = addr; + spi_priv->cmd_buf[2] = W5500_SPI_READ_CONTROL(addr); + ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); + + mutex_unlock(&spi_priv->cmd_lock); + + return ret; +} + +static int w5500_spi_writebulk(struct net_device *ndev, u32 addr, const u8 *buf, + int len) +{ + struct spi_device *spi = to_spi_device(ndev->dev.parent); + struct w5500_spi_priv *spi_priv = w5500_spi_priv(ndev); + struct spi_transfer xfer[] = { + { + .tx_buf = spi_priv->cmd_buf, + .len = sizeof(spi_priv->cmd_buf), + }, + { + .tx_buf = buf, + .len = len, + }, + }; + int ret; + + mutex_lock(&spi_priv->cmd_lock); + + spi_priv->cmd_buf[0] = addr >> 8; + spi_priv->cmd_buf[1] = addr; + spi_priv->cmd_buf[2] = W5500_SPI_WRITE_CONTROL(addr); + ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); + + mutex_unlock(&spi_priv->cmd_lock); + + return ret; +} + +static const struct w5100_ops w5500_ops = { + .may_sleep = true, + .chip_id = W5500, + .read = w5500_spi_read, + .write = w5500_spi_write, + .read16 = w5500_spi_read16, + .write16 = w5500_spi_write16, + .readbulk = w5500_spi_readbulk, + .writebulk = w5500_spi_writebulk, + .init = w5500_spi_init, +}; + +static const struct of_device_id w5100_of_match[] = { + { .compatible = "wiznet,w5100", .data = (const void*)W5100, }, + { .compatible = "wiznet,w5200", .data = (const void*)W5200, }, + { .compatible = "wiznet,w5500", .data = (const void*)W5500, }, + { }, +}; +MODULE_DEVICE_TABLE(of, w5100_of_match); + +static int w5100_spi_probe(struct spi_device *spi) +{ + const struct of_device_id *of_id; + const struct w5100_ops *ops; + kernel_ulong_t driver_data; + const void *mac = NULL; + u8 tmpmac[ETH_ALEN]; + int priv_size; + int ret; + + ret = of_get_mac_address(spi->dev.of_node, tmpmac); + if (!ret) + mac = tmpmac; + + if (spi->dev.of_node) { + of_id = of_match_device(w5100_of_match, &spi->dev); + if (!of_id) + return -ENODEV; + driver_data = (kernel_ulong_t)of_id->data; + } else { + driver_data = spi_get_device_id(spi)->driver_data; + } + + switch (driver_data) { + case W5100: + ops = &w5100_spi_ops; + priv_size = 0; + break; + case W5200: + ops = &w5200_ops; + priv_size = sizeof(struct w5200_spi_priv); + break; + case W5500: + ops = &w5500_ops; + priv_size = sizeof(struct w5500_spi_priv); + break; + default: + return -EINVAL; + } + + return w5100_probe(&spi->dev, ops, priv_size, mac, spi->irq, -EINVAL); +} + +static void w5100_spi_remove(struct spi_device *spi) +{ + w5100_remove(&spi->dev); +} + +static const struct spi_device_id w5100_spi_ids[] = { + { "w5100", W5100 }, + { "w5200", W5200 }, + { "w5500", W5500 }, + {} +}; +MODULE_DEVICE_TABLE(spi, w5100_spi_ids); + +static struct spi_driver w5100_spi_driver = { + .driver = { + .name = "w5100", + .pm = &w5100_pm_ops, + .of_match_table = w5100_of_match, + }, + .probe = w5100_spi_probe, + .remove = w5100_spi_remove, + .id_table = w5100_spi_ids, +}; +module_spi_driver(w5100_spi_driver); + +MODULE_DESCRIPTION("WIZnet W5100/W5200/W5500 Ethernet driver for SPI mode"); +MODULE_AUTHOR("Akinobu Mita <akinobu.mita@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c new file mode 100644 index 000000000..634946e87 --- /dev/null +++ b/drivers/net/ethernet/wiznet/w5100.c @@ -0,0 +1,1278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Ethernet driver for the WIZnet W5100 chip. + * + * Copyright (C) 2006-2008 WIZnet Co.,Ltd. + * Copyright (C) 2012 Mike Sinkovsky <msink@permonline.ru> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/platform_device.h> +#include <linux/platform_data/wiznet.h> +#include <linux/ethtool.h> +#include <linux/skbuff.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> + +#include "w5100.h" + +#define DRV_NAME "w5100" +#define DRV_VERSION "2012-04-04" + +MODULE_DESCRIPTION("WIZnet W5100 Ethernet driver v"DRV_VERSION); +MODULE_AUTHOR("Mike Sinkovsky <msink@permonline.ru>"); +MODULE_ALIAS("platform:"DRV_NAME); +MODULE_LICENSE("GPL"); + +/* + * W5100/W5200/W5500 common registers + */ +#define W5100_COMMON_REGS 0x0000 +#define W5100_MR 0x0000 /* Mode Register */ +#define MR_RST 0x80 /* S/W reset */ +#define MR_PB 0x10 /* Ping block */ +#define MR_AI 0x02 /* Address Auto-Increment */ +#define MR_IND 0x01 /* Indirect mode */ +#define W5100_SHAR 0x0009 /* Source MAC address */ +#define W5100_IR 0x0015 /* Interrupt Register */ +#define W5100_COMMON_REGS_LEN 0x0040 + +#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) ((priv)->s0_regs) + +#define W5100_S0_MR(priv) (S0_REGS(priv) + W5100_Sn_MR) +#define S0_MR_MACRAW 0x04 /* MAC RAW mode */ +#define S0_MR_MF 0x40 /* MAC Filter for W5100 and W5200 */ +#define W5500_S0_MR_MF 0x80 /* MAC Filter for W5500 */ +#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(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(priv) (S0_REGS(priv) + W5100_Sn_SR) +#define S0_SR_MACRAW 0x42 /* mac raw mode */ +#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 and W5200 common registers + */ +#define W5100_IMR 0x0016 /* Interrupt Mask Register */ +#define IR_S0 0x01 /* S0 interrupt */ +#define W5100_RTR 0x0017 /* Retry Time-value Register */ +#define RTR_DEFAULT 2000 /* =0x07d0 (2000) */ + +/* + * W5100 specific register and memory + */ +#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_SIZE 0x2000 +#define W5100_RX_MEM_START 0x6000 +#define W5100_RX_MEM_SIZE 0x2000 + +/* + * W5200 specific register and memory + */ +#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_TX_MEM_START 0x8000 +#define W5200_TX_MEM_SIZE 0x4000 +#define W5200_RX_MEM_START 0xc000 +#define W5200_RX_MEM_SIZE 0x4000 + +/* + * W5500 specific register and memory + * + * W5500 register and memory are organized by multiple blocks. Each one is + * selected by 16bits offset address and 5bits block select bits. So we + * encode it into 32bits address. (lower 16bits is offset address and + * upper 16bits is block select bits) + */ +#define W5500_SIMR 0x0018 /* Socket Interrupt Mask Register */ +#define W5500_RTR 0x0019 /* Retry Time-value Register */ + +#define W5500_S0_REGS 0x10000 + +#define W5500_Sn_RXMEM_SIZE(n) \ + (0x1001e + (n) * 0x40000) /* Sn RX Memory Size */ +#define W5500_Sn_TXMEM_SIZE(n) \ + (0x1001f + (n) * 0x40000) /* Sn TX Memory Size */ + +#define W5500_TX_MEM_START 0x20000 +#define W5500_TX_MEM_SIZE 0x04000 +#define W5500_RX_MEM_START 0x30000 +#define W5500_RX_MEM_SIZE 0x04000 + +/* + * Device driver private data structure + */ + +struct w5100_priv { + const struct w5100_ops *ops; + + /* Socket 0 register offset address */ + u32 s0_regs; + /* Socket 0 TX buffer offset address and size */ + u32 s0_tx_buf; + u16 s0_tx_buf_size; + /* Socket 0 RX buffer offset address and size */ + u32 s0_rx_buf; + u16 s0_rx_buf_size; + + int irq; + int link_irq; + int link_gpio; + + struct napi_struct napi; + 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; +}; + +/************************************************************************ + * + * 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 int w5100_read_direct(struct net_device *ndev, u32 addr) +{ + return ioread8(w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT)); +} + +static inline int __w5100_write_direct(struct net_device *ndev, u32 addr, + u8 data) +{ + iowrite8(data, w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT)); + + return 0; +} + +static inline int w5100_write_direct(struct net_device *ndev, u32 addr, u8 data) +{ + __w5100_write_direct(ndev, addr, data); + + return 0; +} + +static int w5100_read16_direct(struct net_device *ndev, u32 addr) +{ + u16 data; + data = w5100_read_direct(ndev, addr) << 8; + data |= w5100_read_direct(ndev, addr + 1); + return data; +} + +static int w5100_write16_direct(struct net_device *ndev, u32 addr, u16 data) +{ + __w5100_write_direct(ndev, addr, data >> 8); + __w5100_write_direct(ndev, addr + 1, data); + + return 0; +} + +static int w5100_readbulk_direct(struct net_device *ndev, u32 addr, u8 *buf, + int len) +{ + int i; + + for (i = 0; i < len; i++, addr++) + *buf++ = w5100_read_direct(ndev, addr); + + return 0; +} + +static int w5100_writebulk_direct(struct net_device *ndev, u32 addr, + const u8 *buf, int len) +{ + int i; + + for (i = 0; i < len; i++, addr++) + __w5100_write_direct(ndev, addr, *buf++); + + return 0; +} + +static int w5100_mmio_init(struct net_device *ndev) +{ + struct platform_device *pdev = to_platform_device(ndev->dev.parent); + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + + spin_lock_init(&mmio_priv->reg_lock); + + mmio_priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(mmio_priv->base)) + return PTR_ERR(mmio_priv->base); + + 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 + * Register (IDM_DR), which are directly mapped to Memory-Mapped I/O space. + * Mode Register (MR) is directly accessible. + * + * Only 0x04 bytes are required for memory space. + */ +#define W5100_IDM_AR 0x01 /* Indirect Mode Address Register */ +#define W5100_IDM_DR 0x03 /* Indirect Mode Data Register */ + +static int w5100_read_indirect(struct net_device *ndev, u32 addr) +{ + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + unsigned long flags; + u8 data; + + 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 int w5100_write_indirect(struct net_device *ndev, u32 addr, u8 data) +{ + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + unsigned long 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 int w5100_read16_indirect(struct net_device *ndev, u32 addr) +{ + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + unsigned long flags; + u16 data; + + 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 int w5100_write16_indirect(struct net_device *ndev, u32 addr, u16 data) +{ + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + unsigned long 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 int w5100_readbulk_indirect(struct net_device *ndev, u32 addr, u8 *buf, + int len) +{ + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + unsigned long flags; + int i; + + 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); + + spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); + + return 0; +} + +static int w5100_writebulk_indirect(struct net_device *ndev, u32 addr, + const u8 *buf, int len) +{ + struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); + unsigned long flags; + int i; + + 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++); + + 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) + +static int w5100_read(struct w5100_priv *priv, u32 addr) +{ + return w5100_read_direct(priv->ndev, addr); +} + +static int w5100_write(struct w5100_priv *priv, u32 addr, u8 data) +{ + return w5100_write_direct(priv->ndev, addr, data); +} + +static int w5100_read16(struct w5100_priv *priv, u32 addr) +{ + return w5100_read16_direct(priv->ndev, addr); +} + +static int w5100_write16(struct w5100_priv *priv, u32 addr, u16 data) +{ + return w5100_write16_direct(priv->ndev, addr, data); +} + +static int w5100_readbulk(struct w5100_priv *priv, u32 addr, u8 *buf, int len) +{ + return w5100_readbulk_direct(priv->ndev, addr, buf, len); +} + +static int w5100_writebulk(struct w5100_priv *priv, u32 addr, const u8 *buf, + int len) +{ + return w5100_writebulk_direct(priv->ndev, addr, buf, len); +} + +#elif defined(CONFIG_WIZNET_BUS_INDIRECT) + +static int w5100_read(struct w5100_priv *priv, u32 addr) +{ + return w5100_read_indirect(priv->ndev, addr); +} + +static int w5100_write(struct w5100_priv *priv, u32 addr, u8 data) +{ + return w5100_write_indirect(priv->ndev, addr, data); +} + +static int w5100_read16(struct w5100_priv *priv, u32 addr) +{ + return w5100_read16_indirect(priv->ndev, addr); +} + +static int w5100_write16(struct w5100_priv *priv, u32 addr, u16 data) +{ + return w5100_write16_indirect(priv->ndev, addr, data); +} + +static int w5100_readbulk(struct w5100_priv *priv, u32 addr, u8 *buf, int len) +{ + return w5100_readbulk_indirect(priv->ndev, addr, buf, len); +} + +static int w5100_writebulk(struct w5100_priv *priv, u32 addr, const u8 *buf, + int len) +{ + return w5100_writebulk_indirect(priv->ndev, addr, buf, len); +} + +#else /* CONFIG_WIZNET_BUS_ANY */ + +static int w5100_read(struct w5100_priv *priv, u32 addr) +{ + return priv->ops->read(priv->ndev, addr); +} + +static int w5100_write(struct w5100_priv *priv, u32 addr, u8 data) +{ + return priv->ops->write(priv->ndev, addr, data); +} + +static int w5100_read16(struct w5100_priv *priv, u32 addr) +{ + return priv->ops->read16(priv->ndev, addr); +} + +static int w5100_write16(struct w5100_priv *priv, u32 addr, u16 data) +{ + return priv->ops->write16(priv->ndev, addr, data); +} + +static int w5100_readbulk(struct w5100_priv *priv, u32 addr, u8 *buf, int len) +{ + return priv->ops->readbulk(priv->ndev, addr, buf, len); +} + +static int w5100_writebulk(struct w5100_priv *priv, u32 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) +{ + u32 addr; + int remain = 0; + int ret; + const u32 mem_start = priv->s0_rx_buf; + const u16 mem_size = priv->s0_rx_buf_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) +{ + u32 addr; + int ret; + int remain = 0; + const u32 mem_start = priv->s0_tx_buf; + const u16 mem_size = priv->s0_tx_buf_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; + + w5100_write(priv, W5100_S0_CR(priv), cmd); + + timeout = jiffies + msecs_to_jiffies(100); + + while (w5100_read(priv, W5100_S0_CR(priv)) != 0) { + if (time_after(jiffies, timeout)) + return -EIO; + cpu_relax(); + } + + return 0; +} + +static void w5100_write_macaddr(struct w5100_priv *priv) +{ + struct net_device *ndev = priv->ndev; + + w5100_writebulk(priv, W5100_SHAR, ndev->dev_addr, ETH_ALEN); +} + +static void w5100_socket_intr_mask(struct w5100_priv *priv, u8 mask) +{ + u32 imr; + + if (priv->ops->chip_id == W5500) + imr = W5500_SIMR; + else + imr = W5100_IMR; + + w5100_write(priv, imr, mask); +} + +static void w5100_enable_intr(struct w5100_priv *priv) +{ + w5100_socket_intr_mask(priv, IR_S0); +} + +static void w5100_disable_intr(struct w5100_priv *priv) +{ + w5100_socket_intr_mask(priv, 0); +} + +static void w5100_memory_configure(struct w5100_priv *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); +} + +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 w5500_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, W5500_Sn_RXMEM_SIZE(0), 0x10); + w5100_write(priv, W5500_Sn_TXMEM_SIZE(0), 0x10); + + for (i = 1; i < 8; i++) { + w5100_write(priv, W5500_Sn_RXMEM_SIZE(i), 0); + w5100_write(priv, W5500_Sn_TXMEM_SIZE(i), 0); + } +} + +static int w5100_hw_reset(struct w5100_priv *priv) +{ + u32 rtr; + + w5100_reset(priv); + + w5100_disable_intr(priv); + w5100_write_macaddr(priv); + + switch (priv->ops->chip_id) { + case W5100: + w5100_memory_configure(priv); + rtr = W5100_RTR; + break; + case W5200: + w5200_memory_configure(priv); + rtr = W5100_RTR; + break; + case W5500: + w5500_memory_configure(priv); + rtr = W5500_RTR; + break; + default: + return -EINVAL; + } + + if (w5100_read16(priv, rtr) != RTR_DEFAULT) + return -ENODEV; + + return 0; +} + +static void w5100_hw_start(struct w5100_priv *priv) +{ + u8 mode = S0_MR_MACRAW; + + if (!priv->promisc) { + if (priv->ops->chip_id == W5500) + mode |= W5500_S0_MR_MF; + else + mode |= S0_MR_MF; + } + + w5100_write(priv, W5100_S0_MR(priv), mode); + w5100_command(priv, S0_CR_OPEN); + w5100_enable_intr(priv); +} + +static void w5100_hw_close(struct w5100_priv *priv) +{ + w5100_disable_intr(priv); + w5100_command(priv, S0_CR_CLOSE); +} + +/*********************************************************************** + * + * Device driver functions / callbacks + * + ***********************************************************************/ + +static void w5100_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, DRV_NAME, sizeof(info->driver)); + strscpy(info->version, DRV_VERSION, sizeof(info->version)); + strscpy(info->bus_info, dev_name(ndev->dev.parent), + sizeof(info->bus_info)); +} + +static u32 w5100_get_link(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + if (gpio_is_valid(priv->link_gpio)) + return !!gpio_get_value(priv->link_gpio); + + return 1; +} + +static u32 w5100_get_msglevel(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + return priv->msg_enable; +} + +static void w5100_set_msglevel(struct net_device *ndev, u32 value) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + priv->msg_enable = value; +} + +static int w5100_get_regs_len(struct net_device *ndev) +{ + return W5100_COMMON_REGS_LEN + W5100_S0_REGS_LEN; +} + +static void w5100_get_regs(struct net_device *ndev, + struct ethtool_regs *regs, void *buf) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + regs->version = 1; + 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_restart(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + w5100_hw_reset(priv); + w5100_hw_start(priv); + ndev->stats.tx_errors++; + netif_trans_update(ndev); + netif_wake_queue(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, unsigned int txqueue) +{ + struct w5100_priv *priv = netdev_priv(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(priv)); + w5100_writebuf(priv, offset, skb->data, skb->len); + 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 netdev_tx_t 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 struct sk_buff *w5100_rx_skb(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + struct sk_buff *skb; + u16 rx_len; + u16 offset; + u8 header[2]; + u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR(priv)); + + if (rx_buf_len == 0) + return NULL; + + offset = w5100_read16(priv, W5100_S0_RX_RD(priv)); + 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(priv), offset + rx_buf_len); + w5100_command(priv, S0_CR_RECV); + 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(skb); + + w5100_enable_intr(priv); +} + +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); + + if (skb) + netif_receive_skb(skb); + else + break; + } + + if (rx_count < budget) { + napi_complete_done(napi, rx_count); + w5100_enable_intr(priv); + } + + return rx_count; +} + +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(priv)); + if (!ir) + return IRQ_NONE; + w5100_write(priv, W5100_S0_IR(priv), ir); + + if (ir & S0_IR_SENDOK) { + netif_dbg(priv, tx_done, ndev, "tx done\n"); + netif_wake_queue(ndev); + } + + if (ir & S0_IR_RECV) { + w5100_disable_intr(priv); + + 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; +} + +static irqreturn_t w5100_detect_link(int irq, void *ndev_instance) +{ + struct net_device *ndev = ndev_instance; + struct w5100_priv *priv = netdev_priv(ndev); + + if (netif_running(ndev)) { + if (gpio_get_value(priv->link_gpio) != 0) { + netif_info(priv, link, ndev, "link is up\n"); + netif_carrier_on(ndev); + } else { + netif_info(priv, link, ndev, "link is down\n"); + netif_carrier_off(ndev); + } + } + + 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); + bool set_promisc = (ndev->flags & IFF_PROMISC) != 0; + + if (priv->promisc != set_promisc) { + priv->promisc = set_promisc; + + if (priv->ops->may_sleep) + schedule_work(&priv->setrx_work); + else + w5100_hw_start(priv); + } +} + +static int w5100_set_macaddr(struct net_device *ndev, void *addr) +{ + struct w5100_priv *priv = netdev_priv(ndev); + struct sockaddr *sock_addr = addr; + + if (!is_valid_ether_addr(sock_addr->sa_data)) + return -EADDRNOTAVAIL; + eth_hw_addr_set(ndev, sock_addr->sa_data); + w5100_write_macaddr(priv); + return 0; +} + +static int w5100_open(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + netif_info(priv, ifup, ndev, "enabling\n"); + w5100_hw_start(priv); + napi_enable(&priv->napi); + netif_start_queue(ndev); + if (!gpio_is_valid(priv->link_gpio) || + gpio_get_value(priv->link_gpio) != 0) + netif_carrier_on(ndev); + return 0; +} + +static int w5100_stop(struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + netif_info(priv, ifdown, ndev, "shutting down\n"); + w5100_hw_close(priv); + netif_carrier_off(ndev); + netif_stop_queue(ndev); + napi_disable(&priv->napi); + return 0; +} + +static const struct ethtool_ops w5100_ethtool_ops = { + .get_drvinfo = w5100_get_drvinfo, + .get_msglevel = w5100_get_msglevel, + .set_msglevel = w5100_set_msglevel, + .get_link = w5100_get_link, + .get_regs_len = w5100_get_regs_len, + .get_regs = w5100_get_regs, +}; + +static const struct net_device_ops w5100_netdev_ops = { + .ndo_open = w5100_open, + .ndo_stop = w5100_stop, + .ndo_start_xmit = w5100_start_tx, + .ndo_tx_timeout = w5100_tx_timeout, + .ndo_set_rx_mode = w5100_set_rx_mode, + .ndo_set_mac_address = w5100_set_macaddr, + .ndo_validate_addr = eth_validate_addr, +}; + +static int w5100_mmio_probe(struct platform_device *pdev) +{ + struct wiznet_platform_data *data = dev_get_platdata(&pdev->dev); + const void *mac_addr = NULL; + struct resource *mem; + const struct w5100_ops *ops; + int irq; + + if (data && is_valid_ether_addr(data->mac_addr)) + mac_addr = data->mac_addr; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -EINVAL; + 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; + + return w5100_probe(&pdev->dev, ops, sizeof(struct w5100_mmio_priv), + mac_addr, irq, data ? data->link_gpio : -EINVAL); +} + +static int w5100_mmio_remove(struct platform_device *pdev) +{ + w5100_remove(&pdev->dev); + + return 0; +} + +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, const void *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(alloc_size); + if (!ndev) + return -ENOMEM; + SET_NETDEV_DEV(ndev, dev); + dev_set_drvdata(dev, ndev); + priv = netdev_priv(ndev); + + switch (ops->chip_id) { + case W5100: + priv->s0_regs = W5100_S0_REGS; + priv->s0_tx_buf = W5100_TX_MEM_START; + priv->s0_tx_buf_size = W5100_TX_MEM_SIZE; + priv->s0_rx_buf = W5100_RX_MEM_START; + priv->s0_rx_buf_size = W5100_RX_MEM_SIZE; + break; + case W5200: + priv->s0_regs = W5200_S0_REGS; + priv->s0_tx_buf = W5200_TX_MEM_START; + priv->s0_tx_buf_size = W5200_TX_MEM_SIZE; + priv->s0_rx_buf = W5200_RX_MEM_START; + priv->s0_rx_buf_size = W5200_RX_MEM_SIZE; + break; + case W5500: + priv->s0_regs = W5500_S0_REGS; + priv->s0_tx_buf = W5500_TX_MEM_START; + priv->s0_tx_buf_size = W5500_TX_MEM_SIZE; + priv->s0_rx_buf = W5500_RX_MEM_START; + priv->s0_rx_buf_size = W5500_RX_MEM_SIZE; + break; + default: + err = -EINVAL; + goto err_register; + } + + 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; + netif_napi_add_weight(ndev, &priv->napi, w5100_napi_poll, 16); + + /* This chip doesn't support VLAN packets with normal MTU, + * so disable VLAN for this device. + */ + ndev->features |= NETIF_F_VLAN_CHALLENGED; + + err = register_netdev(ndev); + if (err < 0) + goto err_register; + + priv->xfer_wq = alloc_workqueue("%s", WQ_MEM_RECLAIM, 0, + 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) + eth_hw_addr_set(ndev, mac_addr); + else + eth_hw_addr_random(ndev); + + if (priv->ops->init) { + err = priv->ops->init(priv->ndev); + if (err) + goto err_hw; + } + + err = w5100_hw_reset(priv); + if (err) + 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_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); + +void w5100_remove(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct w5100_priv *priv = netdev_priv(ndev); + + w5100_hw_reset(priv); + free_irq(priv->irq, ndev); + if (gpio_is_valid(priv->link_gpio)) + free_irq(priv->link_irq, ndev); + + flush_work(&priv->setrx_work); + flush_work(&priv->restart_work); + destroy_workqueue(priv->xfer_wq); + + unregister_netdev(ndev); + free_netdev(ndev); +} +EXPORT_SYMBOL_GPL(w5100_remove); + +#ifdef CONFIG_PM_SLEEP +static int w5100_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct w5100_priv *priv = netdev_priv(ndev); + + if (netif_running(ndev)) { + netif_carrier_off(ndev); + netif_device_detach(ndev); + + w5100_hw_close(priv); + } + return 0; +} + +static int w5100_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct w5100_priv *priv = netdev_priv(ndev); + + if (netif_running(ndev)) { + w5100_hw_reset(priv); + w5100_hw_start(priv); + + netif_device_attach(ndev); + if (!gpio_is_valid(priv->link_gpio) || + gpio_get_value(priv->link_gpio) != 0) + netif_carrier_on(ndev); + } + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume); +EXPORT_SYMBOL_GPL(w5100_pm_ops); + +static struct platform_driver w5100_mmio_driver = { + .driver = { + .name = DRV_NAME, + .pm = &w5100_pm_ops, + }, + .probe = w5100_mmio_probe, + .remove = w5100_mmio_remove, +}; +module_platform_driver(w5100_mmio_driver); diff --git a/drivers/net/ethernet/wiznet/w5100.h b/drivers/net/ethernet/wiznet/w5100.h new file mode 100644 index 000000000..481af3b6d --- /dev/null +++ b/drivers/net/ethernet/wiznet/w5100.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Ethernet driver for the WIZnet W5100 chip. + * + * Copyright (C) 2006-2008 WIZnet Co.,Ltd. + * Copyright (C) 2012 Mike Sinkovsky <msink@permonline.ru> + */ + +enum { + W5100, + W5200, + W5500, +}; + +struct w5100_ops { + bool may_sleep; + int chip_id; + int (*read)(struct net_device *ndev, u32 addr); + int (*write)(struct net_device *ndev, u32 addr, u8 data); + int (*read16)(struct net_device *ndev, u32 addr); + int (*write16)(struct net_device *ndev, u32 addr, u16 data); + int (*readbulk)(struct net_device *ndev, u32 addr, u8 *buf, int len); + int (*writebulk)(struct net_device *ndev, u32 addr, const u8 *buf, + int len); + int (*reset)(struct net_device *ndev); + int (*init)(struct net_device *ndev); +}; + +void *w5100_ops_priv(const struct net_device *ndev); + +int w5100_probe(struct device *dev, const struct w5100_ops *ops, + int sizeof_ops_priv, const void *mac_addr, int irq, + int link_gpio); +void w5100_remove(struct device *dev); + +extern const struct dev_pm_ops w5100_pm_ops; diff --git a/drivers/net/ethernet/wiznet/w5300.c b/drivers/net/ethernet/wiznet/w5300.c new file mode 100644 index 000000000..b0958fe81 --- /dev/null +++ b/drivers/net/ethernet/wiznet/w5300.c @@ -0,0 +1,689 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/platform_device.h> +#include <linux/platform_data/wiznet.h> +#include <linux/ethtool.h> +#include <linux/skbuff.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> + +#define DRV_NAME "w5300" +#define DRV_VERSION "2012-04-04" + +MODULE_DESCRIPTION("WIZnet W5300 Ethernet driver v"DRV_VERSION); +MODULE_AUTHOR("Mike Sinkovsky <msink@permonline.ru>"); +MODULE_ALIAS("platform:"DRV_NAME); +MODULE_LICENSE("GPL"); + +/* + * Registers + */ +#define W5300_MR 0x0000 /* Mode Register */ +#define MR_DBW (1 << 15) /* Data bus width */ +#define MR_MPF (1 << 14) /* Mac layer pause frame */ +#define MR_WDF(n) (((n)&7)<<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 */ +#define W5300_IR 0x0002 /* Interrupt Register */ +#define W5300_IMR 0x0004 /* Interrupt Mask Register */ +#define IR_S0 0x0001 /* S0 interrupt */ +#define W5300_SHARL 0x0008 /* Source MAC address (0123) */ +#define W5300_SHARH 0x000c /* Source MAC address (45) */ +#define W5300_TMSRL 0x0020 /* Transmit Memory Size (0123) */ +#define W5300_TMSRH 0x0024 /* Transmit Memory Size (4567) */ +#define W5300_RMSRL 0x0028 /* Receive Memory Size (0123) */ +#define W5300_RMSRH 0x002c /* Receive Memory Size (4567) */ +#define W5300_MTYPE 0x0030 /* Memory Type */ +#define W5300_IDR 0x00fe /* Chip ID register */ +#define IDR_W5300 0x5300 /* =0x5300 for WIZnet W5300 */ +#define W5300_S0_MR 0x0200 /* S0 Mode Register */ +#define S0_MR_CLOSED 0x0000 /* Close mode */ +#define S0_MR_MACRAW 0x0004 /* MAC RAW mode (promiscuous) */ +#define S0_MR_MACRAW_MF 0x0044 /* MAC RAW mode (filtered) */ +#define W5300_S0_CR 0x0202 /* S0 Command Register */ +#define S0_CR_OPEN 0x0001 /* OPEN command */ +#define S0_CR_CLOSE 0x0010 /* CLOSE command */ +#define S0_CR_SEND 0x0020 /* SEND command */ +#define S0_CR_RECV 0x0040 /* RECV command */ +#define W5300_S0_IMR 0x0204 /* S0 Interrupt Mask Register */ +#define W5300_S0_IR 0x0206 /* S0 Interrupt Register */ +#define S0_IR_RECV 0x0004 /* Receive interrupt */ +#define S0_IR_SENDOK 0x0010 /* Send OK interrupt */ +#define W5300_S0_SSR 0x0208 /* S0 Socket Status Register */ +#define W5300_S0_TX_WRSR 0x0220 /* S0 TX Write Size Register */ +#define W5300_S0_TX_FSR 0x0224 /* S0 TX Free Size Register */ +#define W5300_S0_RX_RSR 0x0228 /* S0 Received data Size */ +#define W5300_S0_TX_FIFO 0x022e /* S0 Transmit FIFO */ +#define W5300_S0_RX_FIFO 0x0230 /* S0 Receive FIFO */ +#define W5300_REGS_LEN 0x0400 + +/* + * Device driver private data structure + */ +struct w5300_priv { + void __iomem *base; + spinlock_t reg_lock; + bool indirect; + u16 (*read) (struct w5300_priv *priv, u16 addr); + void (*write)(struct w5300_priv *priv, u16 addr, u16 data); + int irq; + int link_irq; + int link_gpio; + + struct napi_struct napi; + struct net_device *ndev; + bool promisc; + u32 msg_enable; +}; + +/************************************************************************ + * + * 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 w5300_read_direct(struct w5300_priv *priv, u16 addr) +{ + return ioread16(priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT)); +} + +static inline void w5300_write_direct(struct w5300_priv *priv, + u16 addr, u16 data) +{ + iowrite16(data, priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT)); +} + +/* + * 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_IDM_AR 0x0002 /* Indirect Mode Address */ +#define W5300_IDM_DR 0x0004 /* Indirect Mode Data */ + +static u16 w5300_read_indirect(struct w5300_priv *priv, u16 addr) +{ + unsigned long flags; + u16 data; + + spin_lock_irqsave(&priv->reg_lock, flags); + w5300_write_direct(priv, W5300_IDM_AR, addr); + data = w5300_read_direct(priv, W5300_IDM_DR); + spin_unlock_irqrestore(&priv->reg_lock, flags); + + return data; +} + +static void w5300_write_indirect(struct w5300_priv *priv, u16 addr, u16 data) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->reg_lock, flags); + w5300_write_direct(priv, W5300_IDM_AR, addr); + w5300_write_direct(priv, W5300_IDM_DR, data); + spin_unlock_irqrestore(&priv->reg_lock, flags); +} + +#if defined(CONFIG_WIZNET_BUS_DIRECT) +#define w5300_read w5300_read_direct +#define w5300_write w5300_write_direct + +#elif defined(CONFIG_WIZNET_BUS_INDIRECT) +#define w5300_read w5300_read_indirect +#define w5300_write w5300_write_indirect + +#else /* CONFIG_WIZNET_BUS_ANY */ +#define w5300_read priv->read +#define w5300_write priv->write +#endif + +static u32 w5300_read32(struct w5300_priv *priv, u16 addr) +{ + u32 data; + data = w5300_read(priv, addr) << 16; + data |= w5300_read(priv, addr + 2); + return data; +} + +static void w5300_write32(struct w5300_priv *priv, u16 addr, u32 data) +{ + w5300_write(priv, addr, data >> 16); + w5300_write(priv, addr + 2, data); +} + +static int w5300_command(struct w5300_priv *priv, u16 cmd) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(100); + + w5300_write(priv, W5300_S0_CR, cmd); + + while (w5300_read(priv, W5300_S0_CR) != 0) { + if (time_after(jiffies, timeout)) + return -EIO; + cpu_relax(); + } + + return 0; +} + +static void w5300_read_frame(struct w5300_priv *priv, u8 *buf, int len) +{ + u16 fifo; + int i; + + for (i = 0; i < len; i += 2) { + fifo = w5300_read(priv, W5300_S0_RX_FIFO); + *buf++ = fifo >> 8; + *buf++ = fifo; + } + fifo = w5300_read(priv, W5300_S0_RX_FIFO); + fifo = w5300_read(priv, W5300_S0_RX_FIFO); +} + +static void w5300_write_frame(struct w5300_priv *priv, u8 *buf, int len) +{ + u16 fifo; + int i; + + for (i = 0; i < len; i += 2) { + fifo = *buf++ << 8; + fifo |= *buf++; + w5300_write(priv, W5300_S0_TX_FIFO, fifo); + } + w5300_write32(priv, W5300_S0_TX_WRSR, len); +} + +static void w5300_write_macaddr(struct w5300_priv *priv) +{ + struct net_device *ndev = priv->ndev; + w5300_write32(priv, W5300_SHARL, + ndev->dev_addr[0] << 24 | + ndev->dev_addr[1] << 16 | + ndev->dev_addr[2] << 8 | + ndev->dev_addr[3]); + w5300_write(priv, W5300_SHARH, + ndev->dev_addr[4] << 8 | + ndev->dev_addr[5]); +} + +static void w5300_hw_reset(struct w5300_priv *priv) +{ + w5300_write_direct(priv, W5300_MR, MR_RST); + mdelay(5); + w5300_write_direct(priv, W5300_MR, priv->indirect ? + MR_WDF(7) | MR_PB | MR_IND : + MR_WDF(7) | MR_PB); + w5300_write(priv, W5300_IMR, 0); + w5300_write_macaddr(priv); + + /* Configure 128K of internal memory + * as 64K RX fifo and 64K TX fifo + */ + w5300_write32(priv, W5300_RMSRL, 64 << 24); + w5300_write32(priv, W5300_RMSRH, 0); + w5300_write32(priv, W5300_TMSRL, 64 << 24); + w5300_write32(priv, W5300_TMSRH, 0); + w5300_write(priv, W5300_MTYPE, 0x00ff); +} + +static void w5300_hw_start(struct w5300_priv *priv) +{ + w5300_write(priv, W5300_S0_MR, priv->promisc ? + S0_MR_MACRAW : S0_MR_MACRAW_MF); + w5300_command(priv, S0_CR_OPEN); + w5300_write(priv, W5300_S0_IMR, S0_IR_RECV | S0_IR_SENDOK); + w5300_write(priv, W5300_IMR, IR_S0); +} + +static void w5300_hw_close(struct w5300_priv *priv) +{ + w5300_write(priv, W5300_IMR, 0); + w5300_command(priv, S0_CR_CLOSE); +} + +/*********************************************************************** + * + * Device driver functions / callbacks + * + ***********************************************************************/ + +static void w5300_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, DRV_NAME, sizeof(info->driver)); + strscpy(info->version, DRV_VERSION, sizeof(info->version)); + strscpy(info->bus_info, dev_name(ndev->dev.parent), + sizeof(info->bus_info)); +} + +static u32 w5300_get_link(struct net_device *ndev) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + if (gpio_is_valid(priv->link_gpio)) + return !!gpio_get_value(priv->link_gpio); + + return 1; +} + +static u32 w5300_get_msglevel(struct net_device *ndev) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + return priv->msg_enable; +} + +static void w5300_set_msglevel(struct net_device *ndev, u32 value) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + priv->msg_enable = value; +} + +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_priv *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 W5300_S0_TX_FIFO: /* cannot read TX_FIFO */ + case W5300_S0_RX_FIFO: /* cannot read RX_FIFO */ + data = 0xffff; + break; + default: + data = w5300_read(priv, addr); + break; + } + *buf++ = data >> 8; + *buf++ = data; + } +} + +static void w5300_tx_timeout(struct net_device *ndev, unsigned int txqueue) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + w5300_hw_reset(priv); + w5300_hw_start(priv); + ndev->stats.tx_errors++; + netif_trans_update(ndev); + netif_wake_queue(ndev); +} + +static netdev_tx_t w5300_start_tx(struct sk_buff *skb, struct net_device *ndev) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + + w5300_write_frame(priv, skb->data, skb->len); + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + dev_kfree_skb(skb); + netif_dbg(priv, tx_queued, ndev, "tx queued\n"); + + w5300_command(priv, S0_CR_SEND); + + return NETDEV_TX_OK; +} + +static int w5300_napi_poll(struct napi_struct *napi, int budget) +{ + struct w5300_priv *priv = container_of(napi, struct w5300_priv, napi); + struct net_device *ndev = priv->ndev; + struct sk_buff *skb; + int rx_count; + u16 rx_len; + + for (rx_count = 0; rx_count < budget; rx_count++) { + u32 rx_fifo_len = w5300_read32(priv, W5300_S0_RX_RSR); + if (rx_fifo_len == 0) + break; + + rx_len = w5300_read(priv, W5300_S0_RX_FIFO); + + skb = netdev_alloc_skb_ip_align(ndev, roundup(rx_len, 2)); + if (unlikely(!skb)) { + u32 i; + for (i = 0; i < rx_fifo_len; i += 2) + w5300_read(priv, W5300_S0_RX_FIFO); + ndev->stats.rx_dropped++; + return -ENOMEM; + } + + skb_put(skb, rx_len); + w5300_read_frame(priv, skb->data, rx_len); + skb->protocol = eth_type_trans(skb, ndev); + + netif_receive_skb(skb); + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += rx_len; + } + + if (rx_count < budget) { + napi_complete_done(napi, rx_count); + w5300_write(priv, W5300_IMR, IR_S0); + } + + return rx_count; +} + +static irqreturn_t w5300_interrupt(int irq, void *ndev_instance) +{ + struct net_device *ndev = ndev_instance; + struct w5300_priv *priv = netdev_priv(ndev); + + int ir = w5300_read(priv, W5300_S0_IR); + if (!ir) + return IRQ_NONE; + w5300_write(priv, W5300_S0_IR, ir); + + if (ir & S0_IR_SENDOK) { + netif_dbg(priv, tx_done, ndev, "tx done\n"); + netif_wake_queue(ndev); + } + + if (ir & S0_IR_RECV) { + if (napi_schedule_prep(&priv->napi)) { + w5300_write(priv, W5300_IMR, 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_priv *priv = netdev_priv(ndev); + + if (netif_running(ndev)) { + if (gpio_get_value(priv->link_gpio) != 0) { + netif_info(priv, link, ndev, "link is up\n"); + netif_carrier_on(ndev); + } else { + netif_info(priv, link, ndev, "link is down\n"); + netif_carrier_off(ndev); + } + } + + return IRQ_HANDLED; +} + +static void w5300_set_rx_mode(struct net_device *ndev) +{ + struct w5300_priv *priv = netdev_priv(ndev); + bool set_promisc = (ndev->flags & IFF_PROMISC) != 0; + + if (priv->promisc != set_promisc) { + priv->promisc = set_promisc; + w5300_hw_start(priv); + } +} + +static int w5300_set_macaddr(struct net_device *ndev, void *addr) +{ + struct w5300_priv *priv = netdev_priv(ndev); + struct sockaddr *sock_addr = addr; + + if (!is_valid_ether_addr(sock_addr->sa_data)) + return -EADDRNOTAVAIL; + eth_hw_addr_set(ndev, sock_addr->sa_data); + w5300_write_macaddr(priv); + return 0; +} + +static int w5300_open(struct net_device *ndev) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + netif_info(priv, ifup, ndev, "enabling\n"); + w5300_hw_start(priv); + napi_enable(&priv->napi); + netif_start_queue(ndev); + if (!gpio_is_valid(priv->link_gpio) || + gpio_get_value(priv->link_gpio) != 0) + netif_carrier_on(ndev); + return 0; +} + +static int w5300_stop(struct net_device *ndev) +{ + struct w5300_priv *priv = netdev_priv(ndev); + + netif_info(priv, ifdown, ndev, "shutting down\n"); + w5300_hw_close(priv); + 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_msglevel = w5300_get_msglevel, + .set_msglevel = w5300_set_msglevel, + .get_link = w5300_get_link, + .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, +}; + +static int w5300_hw_probe(struct platform_device *pdev) +{ + struct wiznet_platform_data *data = dev_get_platdata(&pdev->dev); + struct net_device *ndev = platform_get_drvdata(pdev); + struct w5300_priv *priv = netdev_priv(ndev); + const char *name = netdev_name(ndev); + struct resource *mem; + int mem_size; + int irq; + int ret; + + if (data && is_valid_ether_addr(data->mac_addr)) { + eth_hw_addr_set(ndev, data->mac_addr); + } else { + eth_hw_addr_random(ndev); + } + + 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 < W5300_BUS_DIRECT_SIZE; + if (priv->indirect) { + priv->read = w5300_read_indirect; + priv->write = w5300_write_indirect; + } else { + priv->read = w5300_read_direct; + priv->write = w5300_write_direct; + } + + w5300_hw_reset(priv); + if (w5300_read(priv, W5300_IDR) != IDR_W5300) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = request_irq(irq, w5300_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, w5300_detect_link, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + link_name, priv->ndev) < 0) + priv->link_gpio = -EINVAL; + } + + netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, irq); + return 0; +} + +static int w5300_probe(struct platform_device *pdev) +{ + struct w5300_priv *priv; + struct net_device *ndev; + int err; + + 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; + + ndev->netdev_ops = &w5300_netdev_ops; + ndev->ethtool_ops = &w5300_ethtool_ops; + ndev->watchdog_timeo = HZ; + netif_napi_add_weight(ndev, &priv->napi, w5300_napi_poll, 16); + + /* This chip doesn't support VLAN packets with normal MTU, + * so disable VLAN for this device. + */ + ndev->features |= NETIF_F_VLAN_CHALLENGED; + + err = register_netdev(ndev); + if (err < 0) + goto err_register; + + err = w5300_hw_probe(pdev); + if (err < 0) + goto err_hw_probe; + + return 0; + +err_hw_probe: + unregister_netdev(ndev); +err_register: + free_netdev(ndev); + return err; +} + +static int w5300_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct w5300_priv *priv = netdev_priv(ndev); + + w5300_hw_reset(priv); + free_irq(priv->irq, ndev); + if (gpio_is_valid(priv->link_gpio)) + free_irq(priv->link_irq, ndev); + + unregister_netdev(ndev); + free_netdev(ndev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int w5300_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct w5300_priv *priv = netdev_priv(ndev); + + if (netif_running(ndev)) { + netif_carrier_off(ndev); + netif_device_detach(ndev); + + w5300_hw_close(priv); + } + return 0; +} + +static int w5300_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct w5300_priv *priv = netdev_priv(ndev); + + if (!netif_running(ndev)) { + w5300_hw_reset(priv); + w5300_hw_start(priv); + + netif_device_attach(ndev); + if (!gpio_is_valid(priv->link_gpio) || + gpio_get_value(priv->link_gpio) != 0) + netif_carrier_on(ndev); + } + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(w5300_pm_ops, w5300_suspend, w5300_resume); + +static struct platform_driver w5300_driver = { + .driver = { + .name = DRV_NAME, + .pm = &w5300_pm_ops, + }, + .probe = w5300_probe, + .remove = w5300_remove, +}; + +module_platform_driver(w5300_driver); |