Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# WIZnet devices configuration
#
config NET_VENDOR_WIZNET
bool "WIZnet devices"
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/W5200/W5500 Ethernet support for SPI mode"
depends on SPI
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.
endif # NET_VENDOR_WIZNET
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_WIZNET_W5100) += w5100.o w5100-spi.o
@@ -0,0 +1,483 @@
// 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.h>
#include <linux/of_net.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 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;
driver_data = (uintptr_t)spi_get_device_match_data(spi);
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);
}
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");
@@ -0,0 +1,866 @@
// 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/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 "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;
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;
};
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);
}
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_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
napi_schedule(&priv->napi);
}
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);
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_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,
};
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)
{
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;
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 | WQ_PERCPU, 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;
return 0;
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);
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);
}
return 0;
}
#endif /* CONFIG_PM_SLEEP */
SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume);
EXPORT_SYMBOL_GPL(w5100_pm_ops);
@@ -0,0 +1,35 @@
/* 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);
void w5100_remove(struct device *dev);
extern const struct dev_pm_ops w5100_pm_ops;