diff options
| author | Sang-Heon Jeon <ekffu200098@gmail.com> | 2026-08-24 01:18:14 +0900 |
|---|---|---|
| committer | Julia Lawall <Julia.Lawall@inria.fr> | 2026-08-23 22:12:11 +0200 |
| commit | 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c (patch) | |
| tree | dcc4da1d99f17541c5590ac63c77da4bf372a4ab /drivers/net/ethernet/adi | |
| download | linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.tar.gz linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.zip | |
coccinelle: alloc_cast: drop removed allocatorsgrafted
- pci_alloc_consistent() and pci_zalloc_consistent() were removed by
commit 7968778914e5
("PCI: Remove the deprecated "pci-dma-compat.h" API")
- kmem_alloc() was removed by commit f078d4ea8276
("xfs: convert kmem_alloc() to kmalloc()")
- kmem_zalloc() was removed by commit 10634530f7ba
("xfs: convert kmem_zalloc() to kzalloc()")
- kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit
bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers")
So drop them from the rules.
No functional change.
Signed-off-by: Sang-Heon Jeon <ekffu200098@gmail.com>
Signed-off-by: Julia Lawall <Julia.Lawall@inria.fr>
Diffstat (limited to 'drivers/net/ethernet/adi')
| -rw-r--r-- | drivers/net/ethernet/adi/Kconfig | 41 | ||||
| -rw-r--r-- | drivers/net/ethernet/adi/Makefile | 7 | ||||
| -rw-r--r-- | drivers/net/ethernet/adi/adin1110.c | 1740 | ||||
| -rw-r--r-- | drivers/net/ethernet/adi/adin1140.c | 791 |
4 files changed, 2579 insertions, 0 deletions
diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig new file mode 100644 index 000000000..bdb8ff7d1 --- /dev/null +++ b/drivers/net/ethernet/adi/Kconfig @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +# +# Analog Devices device configuration +# + +config NET_VENDOR_ADI + bool "Analog Devices devices" + default y + depends on SPI + 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 ADI devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_ADI + +config ADIN1110 + tristate "Analog Devices ADIN1110 MAC-PHY" + depends on SPI && NET_SWITCHDEV + select CRC8 + select PHYLIB + help + Say yes here to build support for Analog Devices ADIN1110 + Low Power 10BASE-T1L Ethernet MAC-PHY. + +config ADIN1140 + tristate "Analog Devices ADIN1140 MAC-PHY" + depends on SPI + select ADIN1140_PHY + select OA_TC6 + help + Say yes here to build support for Analog Devices, Inc. ADIN1140 + 10BASE-T1S Ethernet MAC-PHY. + + To compile this driver as a module, choose M here. The module will be + called adin1140. + +endif # NET_VENDOR_ADI diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile new file mode 100644 index 000000000..0390ca8cc --- /dev/null +++ b/drivers/net/ethernet/adi/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +# +# Makefile for the Analog Devices network device drivers. +# + +obj-$(CONFIG_ADIN1110) += adin1110.o +obj-$(CONFIG_ADIN1140) += adin1140.o diff --git a/drivers/net/ethernet/adi/adin1110.c b/drivers/net/ethernet/adi/adin1110.c new file mode 100644 index 000000000..53fac3dc8 --- /dev/null +++ b/drivers/net/ethernet/adi/adin1110.c @@ -0,0 +1,1740 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* ADIN1110 Low Power 10BASE-T1L Ethernet MAC-PHY + * ADIN2111 2-Port Ethernet Switch with Integrated 10BASE-T1L PHY + * + * Copyright 2021 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cache.h> +#include <linux/crc8.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/gpio/consumer.h> +#include <linux/if_bridge.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/phy.h> +#include <linux/property.h> +#include <linux/spi/spi.h> + +#include <net/switchdev.h> + +#include <linux/unaligned.h> + +#define ADIN1110_PHY_ID 0x1 + +#define ADIN1110_RESET 0x03 +#define ADIN1110_SWRESET BIT(0) + +#define ADIN1110_CONFIG1 0x04 +#define ADIN1110_CONFIG1_SYNC BIT(15) + +#define ADIN1110_CONFIG2 0x06 +#define ADIN2111_P2_FWD_UNK2HOST BIT(12) +#define ADIN2111_PORT_CUT_THRU_EN BIT(11) +#define ADIN1110_CRC_APPEND BIT(5) +#define ADIN1110_FWD_UNK2HOST BIT(2) + +#define ADIN1110_STATUS0 0x08 + +#define ADIN1110_STATUS1 0x09 +#define ADIN2111_P2_RX_RDY BIT(17) +#define ADIN1110_SPI_ERR BIT(10) +#define ADIN1110_RX_RDY BIT(4) + +#define ADIN1110_IMASK1 0x0D +#define ADIN2111_RX_RDY_IRQ BIT(17) +#define ADIN1110_SPI_ERR_IRQ BIT(10) +#define ADIN1110_RX_RDY_IRQ BIT(4) +#define ADIN1110_TX_RDY_IRQ BIT(3) + +#define ADIN1110_MDIOACC 0x20 +#define ADIN1110_MDIO_TRDONE BIT(31) +#define ADIN1110_MDIO_ST GENMASK(29, 28) +#define ADIN1110_MDIO_OP GENMASK(27, 26) +#define ADIN1110_MDIO_PRTAD GENMASK(25, 21) +#define ADIN1110_MDIO_DEVAD GENMASK(20, 16) +#define ADIN1110_MDIO_DATA GENMASK(15, 0) + +#define ADIN1110_TX_FSIZE 0x30 +#define ADIN1110_TX 0x31 +#define ADIN1110_TX_SPACE 0x32 + +#define ADIN1110_MAC_ADDR_FILTER_UPR 0x50 +#define ADIN2111_MAC_ADDR_APPLY2PORT2 BIT(31) +#define ADIN1110_MAC_ADDR_APPLY2PORT BIT(30) +#define ADIN2111_MAC_ADDR_TO_OTHER_PORT BIT(17) +#define ADIN1110_MAC_ADDR_TO_HOST BIT(16) + +#define ADIN1110_MAC_ADDR_FILTER_LWR 0x51 + +#define ADIN1110_MAC_ADDR_MASK_UPR 0x70 +#define ADIN1110_MAC_ADDR_MASK_LWR 0x71 + +#define ADIN1110_RX_FSIZE 0x90 +#define ADIN1110_RX 0x91 + +#define ADIN2111_RX_P2_FSIZE 0xC0 +#define ADIN2111_RX_P2 0xC1 + +#define ADIN1110_CLEAR_STATUS0 0xFFF + +/* MDIO_OP codes */ +#define ADIN1110_MDIO_OP_WR 0x1 +#define ADIN1110_MDIO_OP_RD 0x3 + +#define ADIN1110_CD BIT(7) +#define ADIN1110_WRITE BIT(5) + +#define ADIN1110_MAX_BUFF 2048 +#define ADIN1110_MAX_FRAMES_READ 64 +#define ADIN1110_WR_HEADER_LEN 2 +#define ADIN1110_FRAME_HEADER_LEN 2 +#define ADIN1110_INTERNAL_SIZE_HEADER_LEN 2 +#define ADIN1110_RD_HEADER_LEN 3 +#define ADIN1110_REG_LEN 4 +#define ADIN1110_FEC_LEN 4 + +#define ADIN1110_PHY_ID_VAL 0x0283BC91 +#define ADIN2111_PHY_ID_VAL 0x0283BCA1 + +#define ADIN_MAC_MAX_PORTS 2 +#define ADIN_MAC_MAX_ADDR_SLOTS 16 + +#define ADIN_MAC_MULTICAST_ADDR_SLOT 0 +#define ADIN_MAC_BROADCAST_ADDR_SLOT 1 +#define ADIN_MAC_P1_ADDR_SLOT 2 +#define ADIN_MAC_P2_ADDR_SLOT 3 +#define ADIN_MAC_FDB_ADDR_SLOT 4 + +DECLARE_CRC8_TABLE(adin1110_crc_table); + +enum adin1110_chips_id { + ADIN1110_MAC = 0, + ADIN2111_MAC, +}; + +struct adin1110_cfg { + enum adin1110_chips_id id; + const char *name; + u32 phy_ids[PHY_MAX_ADDR]; + u32 ports_nr; + u32 phy_id_val; +}; + +struct adin1110_port_priv { + struct adin1110_priv *priv; + struct net_device *netdev; + struct net_device *bridge; + struct phy_device *phydev; + struct work_struct tx_work; + u64 rx_packets; + u64 tx_packets; + u64 rx_bytes; + u64 tx_bytes; + struct work_struct rx_mode_work; + u32 flags; + struct sk_buff_head txq; + u32 nr; + u32 state; + struct adin1110_cfg *cfg; +}; + +struct adin1110_priv { + struct mutex lock; /* protect spi */ + spinlock_t state_lock; /* protect RX mode */ + struct mii_bus *mii_bus; + struct spi_device *spidev; + bool append_crc; + struct adin1110_cfg *cfg; + u32 tx_space; + u32 irq_mask; + bool forwarding; + int irq; + struct adin1110_port_priv *ports[ADIN_MAC_MAX_PORTS]; + char mii_bus_name[MII_BUS_ID_SIZE]; + u8 data[ADIN1110_MAX_BUFF] ____cacheline_aligned; +}; + +struct adin1110_switchdev_event_work { + struct work_struct work; + struct switchdev_notifier_fdb_info fdb_info; + struct adin1110_port_priv *port_priv; + unsigned long event; +}; + +static struct adin1110_cfg adin1110_cfgs[] = { + { + .id = ADIN1110_MAC, + .name = "adin1110", + .phy_ids = {1}, + .ports_nr = 1, + .phy_id_val = ADIN1110_PHY_ID_VAL, + }, + { + .id = ADIN2111_MAC, + .name = "adin2111", + .phy_ids = {1, 2}, + .ports_nr = 2, + .phy_id_val = ADIN2111_PHY_ID_VAL, + }, +}; + +static u8 adin1110_crc_data(u8 *data, u32 len) +{ + return crc8(adin1110_crc_table, data, len, 0); +} + +static int adin1110_read_reg(struct adin1110_priv *priv, u16 reg, u32 *val) +{ + u32 header_len = ADIN1110_RD_HEADER_LEN; + u32 read_len = ADIN1110_REG_LEN; + struct spi_transfer t = {0}; + int ret; + + priv->data[0] = ADIN1110_CD | FIELD_GET(GENMASK(12, 8), reg); + priv->data[1] = FIELD_GET(GENMASK(7, 0), reg); + priv->data[2] = 0x00; + + if (priv->append_crc) { + priv->data[2] = adin1110_crc_data(&priv->data[0], 2); + priv->data[3] = 0x00; + header_len++; + } + + if (priv->append_crc) + read_len++; + + memset(&priv->data[header_len], 0, read_len); + t.tx_buf = &priv->data[0]; + t.rx_buf = &priv->data[0]; + t.len = read_len + header_len; + + ret = spi_sync_transfer(priv->spidev, &t, 1); + if (ret) + return ret; + + if (priv->append_crc) { + u8 recv_crc; + u8 crc; + + crc = adin1110_crc_data(&priv->data[header_len], + ADIN1110_REG_LEN); + recv_crc = priv->data[header_len + ADIN1110_REG_LEN]; + + if (crc != recv_crc) { + dev_err_ratelimited(&priv->spidev->dev, "CRC error."); + return -EBADMSG; + } + } + + *val = get_unaligned_be32(&priv->data[header_len]); + + return ret; +} + +static int adin1110_write_reg(struct adin1110_priv *priv, u16 reg, u32 val) +{ + u32 header_len = ADIN1110_WR_HEADER_LEN; + u32 write_len = ADIN1110_REG_LEN; + + priv->data[0] = ADIN1110_CD | ADIN1110_WRITE | FIELD_GET(GENMASK(12, 8), reg); + priv->data[1] = FIELD_GET(GENMASK(7, 0), reg); + + if (priv->append_crc) { + priv->data[2] = adin1110_crc_data(&priv->data[0], header_len); + header_len++; + } + + put_unaligned_be32(val, &priv->data[header_len]); + if (priv->append_crc) { + priv->data[header_len + write_len] = adin1110_crc_data(&priv->data[header_len], + write_len); + write_len++; + } + + return spi_write(priv->spidev, &priv->data[0], header_len + write_len); +} + +static int adin1110_set_bits(struct adin1110_priv *priv, u16 reg, + unsigned long mask, unsigned long val) +{ + u32 write_val; + int ret; + + ret = adin1110_read_reg(priv, reg, &write_val); + if (ret < 0) + return ret; + + set_mask_bits(&write_val, mask, val); + + return adin1110_write_reg(priv, reg, write_val); +} + +static int adin1110_round_len(int len) +{ + /* can read/write only mutiples of 4 bytes of payload */ + len = ALIGN(len, 4); + + /* NOTE: ADIN1110_WR_HEADER_LEN should be used for write ops. */ + if (len + ADIN1110_RD_HEADER_LEN > ADIN1110_MAX_BUFF) + return -EINVAL; + + return len; +} + +static int adin1110_read_fifo(struct adin1110_port_priv *port_priv) +{ + struct adin1110_priv *priv = port_priv->priv; + u32 header_len = ADIN1110_RD_HEADER_LEN; + struct spi_transfer t = {0}; + u32 frame_size_no_fcs; + struct sk_buff *rxb; + u32 frame_size; + int round_len; + u16 reg; + int ret; + + if (!port_priv->nr) { + reg = ADIN1110_RX; + ret = adin1110_read_reg(priv, ADIN1110_RX_FSIZE, &frame_size); + } else { + reg = ADIN2111_RX_P2; + ret = adin1110_read_reg(priv, ADIN2111_RX_P2_FSIZE, + &frame_size); + } + + if (ret < 0) + return ret; + + /* The read frame size includes the extra 2 bytes + * from the ADIN1110 frame header. + */ + if (frame_size < ADIN1110_FRAME_HEADER_LEN + ADIN1110_FEC_LEN) + return -EINVAL; + + round_len = adin1110_round_len(frame_size); + if (round_len < 0) + return -EINVAL; + + frame_size_no_fcs = frame_size - ADIN1110_FRAME_HEADER_LEN - ADIN1110_FEC_LEN; + memset(priv->data, 0, ADIN1110_RD_HEADER_LEN); + + priv->data[0] = ADIN1110_CD | FIELD_GET(GENMASK(12, 8), reg); + priv->data[1] = FIELD_GET(GENMASK(7, 0), reg); + + if (priv->append_crc) { + priv->data[2] = adin1110_crc_data(&priv->data[0], 2); + header_len++; + } + + rxb = netdev_alloc_skb(port_priv->netdev, round_len + header_len); + if (!rxb) + return -ENOMEM; + + skb_put(rxb, frame_size_no_fcs + header_len + ADIN1110_FRAME_HEADER_LEN); + + t.tx_buf = &priv->data[0]; + t.rx_buf = &rxb->data[0]; + t.len = header_len + round_len; + + ret = spi_sync_transfer(priv->spidev, &t, 1); + if (ret) { + kfree_skb(rxb); + return ret; + } + + skb_pull(rxb, header_len + ADIN1110_FRAME_HEADER_LEN); + rxb->protocol = eth_type_trans(rxb, port_priv->netdev); + + if ((port_priv->flags & IFF_ALLMULTI && rxb->pkt_type == PACKET_MULTICAST) || + (port_priv->flags & IFF_BROADCAST && rxb->pkt_type == PACKET_BROADCAST)) + rxb->offload_fwd_mark = port_priv->priv->forwarding; + + netif_rx(rxb); + + port_priv->rx_bytes += frame_size - ADIN1110_FRAME_HEADER_LEN; + port_priv->rx_packets++; + + return 0; +} + +static int adin1110_write_fifo(struct adin1110_port_priv *port_priv, + struct sk_buff *txb) +{ + struct adin1110_priv *priv = port_priv->priv; + u32 header_len = ADIN1110_WR_HEADER_LEN; + __be16 frame_header; + int padding = 0; + int padded_len; + int round_len; + int ret; + + /* Pad frame to 64 byte length, + * MAC nor PHY will otherwise add the + * required padding. + * The FEC will be added by the MAC internally. + */ + if (txb->len + ADIN1110_FEC_LEN < 64) + padding = 64 - (txb->len + ADIN1110_FEC_LEN); + + padded_len = txb->len + padding + ADIN1110_FRAME_HEADER_LEN; + + round_len = adin1110_round_len(padded_len); + if (round_len < 0) + return round_len; + + ret = adin1110_write_reg(priv, ADIN1110_TX_FSIZE, padded_len); + if (ret < 0) + return ret; + + memset(priv->data, 0, round_len + ADIN1110_WR_HEADER_LEN); + + priv->data[0] = ADIN1110_CD | ADIN1110_WRITE; + priv->data[0] |= FIELD_GET(GENMASK(12, 8), ADIN1110_TX); + priv->data[1] = FIELD_GET(GENMASK(7, 0), ADIN1110_TX); + if (priv->append_crc) { + priv->data[2] = adin1110_crc_data(&priv->data[0], 2); + header_len++; + } + + /* mention the port on which to send the frame in the frame header */ + frame_header = cpu_to_be16(port_priv->nr); + memcpy(&priv->data[header_len], &frame_header, + ADIN1110_FRAME_HEADER_LEN); + + memcpy(&priv->data[header_len + ADIN1110_FRAME_HEADER_LEN], + txb->data, txb->len); + + ret = spi_write(priv->spidev, &priv->data[0], round_len + header_len); + if (ret < 0) + return ret; + + port_priv->tx_bytes += txb->len; + port_priv->tx_packets++; + + return 0; +} + +static int adin1110_read_mdio_acc(struct adin1110_priv *priv) +{ + u32 val; + int ret; + + mutex_lock(&priv->lock); + ret = adin1110_read_reg(priv, ADIN1110_MDIOACC, &val); + mutex_unlock(&priv->lock); + if (ret < 0) + return 0; + + return val; +} + +static int adin1110_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct adin1110_priv *priv = bus->priv; + u32 val = 0; + int ret; + + if (mdio_phy_id_is_c45(phy_id)) + return -EOPNOTSUPP; + + val |= FIELD_PREP(ADIN1110_MDIO_OP, ADIN1110_MDIO_OP_RD); + val |= FIELD_PREP(ADIN1110_MDIO_ST, 0x1); + val |= FIELD_PREP(ADIN1110_MDIO_PRTAD, phy_id); + val |= FIELD_PREP(ADIN1110_MDIO_DEVAD, reg); + + /* write the clause 22 read command to the chip */ + mutex_lock(&priv->lock); + ret = adin1110_write_reg(priv, ADIN1110_MDIOACC, val); + mutex_unlock(&priv->lock); + if (ret < 0) + return ret; + + /* ADIN1110_MDIO_TRDONE BIT of the ADIN1110_MDIOACC + * register is set when the read is done. + * After the transaction is done, ADIN1110_MDIO_DATA + * bitfield of ADIN1110_MDIOACC register will contain + * the requested register value. + */ + ret = readx_poll_timeout_atomic(adin1110_read_mdio_acc, priv, val, + (val & ADIN1110_MDIO_TRDONE), + 100, 30000); + if (ret < 0) + return ret; + + return (val & ADIN1110_MDIO_DATA); +} + +static int adin1110_mdio_write(struct mii_bus *bus, int phy_id, + int reg, u16 reg_val) +{ + struct adin1110_priv *priv = bus->priv; + u32 val = 0; + int ret; + + if (mdio_phy_id_is_c45(phy_id)) + return -EOPNOTSUPP; + + val |= FIELD_PREP(ADIN1110_MDIO_OP, ADIN1110_MDIO_OP_WR); + val |= FIELD_PREP(ADIN1110_MDIO_ST, 0x1); + val |= FIELD_PREP(ADIN1110_MDIO_PRTAD, phy_id); + val |= FIELD_PREP(ADIN1110_MDIO_DEVAD, reg); + val |= FIELD_PREP(ADIN1110_MDIO_DATA, reg_val); + + /* write the clause 22 write command to the chip */ + mutex_lock(&priv->lock); + ret = adin1110_write_reg(priv, ADIN1110_MDIOACC, val); + mutex_unlock(&priv->lock); + if (ret < 0) + return ret; + + return readx_poll_timeout_atomic(adin1110_read_mdio_acc, priv, val, + (val & ADIN1110_MDIO_TRDONE), + 100, 30000); +} + +/* ADIN1110 MAC-PHY contains an ADIN1100 PHY. + * ADIN2111 MAC-PHY contains two ADIN1100 PHYs. + * By registering a new MDIO bus we allow the PAL to discover + * the encapsulated PHY and probe the ADIN1100 driver. + */ +static int adin1110_register_mdiobus(struct adin1110_priv *priv, + struct device *dev) +{ + struct mii_bus *mii_bus; + int ret; + + mii_bus = devm_mdiobus_alloc(dev); + if (!mii_bus) + return -ENOMEM; + + snprintf(priv->mii_bus_name, MII_BUS_ID_SIZE, "%s-%u", + priv->cfg->name, spi_get_chipselect(priv->spidev, 0)); + + mii_bus->name = priv->mii_bus_name; + mii_bus->read = adin1110_mdio_read; + mii_bus->write = adin1110_mdio_write; + mii_bus->priv = priv; + mii_bus->parent = dev; + mii_bus->phy_mask = ~((u32)GENMASK(2, 0)); + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); + + ret = devm_mdiobus_register(dev, mii_bus); + if (ret) + return ret; + + priv->mii_bus = mii_bus; + + return 0; +} + +static bool adin1110_port_rx_ready(struct adin1110_port_priv *port_priv, + u32 status) +{ + if (!netif_oper_up(port_priv->netdev)) + return false; + + if (!port_priv->nr) + return !!(status & ADIN1110_RX_RDY); + else + return !!(status & ADIN2111_P2_RX_RDY); +} + +static void adin1110_read_frames(struct adin1110_port_priv *port_priv, + unsigned int budget) +{ + struct adin1110_priv *priv = port_priv->priv; + u32 status1; + int ret; + + while (budget) { + ret = adin1110_read_reg(priv, ADIN1110_STATUS1, &status1); + if (ret < 0) + return; + + if (!adin1110_port_rx_ready(port_priv, status1)) + break; + + ret = adin1110_read_fifo(port_priv); + if (ret < 0) + return; + + budget--; + } +} + +static void adin1110_wake_queues(struct adin1110_priv *priv) +{ + int i; + + for (i = 0; i < priv->cfg->ports_nr; i++) + netif_wake_queue(priv->ports[i]->netdev); +} + +static irqreturn_t adin1110_irq(int irq, void *p) +{ + struct adin1110_priv *priv = p; + u32 status1; + u32 val; + int ret; + int i; + + mutex_lock(&priv->lock); + + ret = adin1110_read_reg(priv, ADIN1110_STATUS1, &status1); + if (ret < 0) + goto out; + + if (priv->append_crc && (status1 & ADIN1110_SPI_ERR)) + dev_warn_ratelimited(&priv->spidev->dev, + "SPI CRC error on write.\n"); + + ret = adin1110_read_reg(priv, ADIN1110_TX_SPACE, &val); + if (ret < 0) + goto out; + + /* TX FIFO space is expressed in half-words */ + priv->tx_space = 2 * val; + + for (i = 0; i < priv->cfg->ports_nr; i++) { + if (adin1110_port_rx_ready(priv->ports[i], status1)) + adin1110_read_frames(priv->ports[i], + ADIN1110_MAX_FRAMES_READ); + } + + /* clear IRQ sources */ + adin1110_write_reg(priv, ADIN1110_STATUS0, ADIN1110_CLEAR_STATUS0); + adin1110_write_reg(priv, ADIN1110_STATUS1, priv->irq_mask); + +out: + mutex_unlock(&priv->lock); + + if (priv->tx_space > 0 && ret >= 0) + adin1110_wake_queues(priv); + + return IRQ_HANDLED; +} + +/* ADIN1110 can filter up to 16 MAC addresses, mac_nr here is the slot used */ +static int adin1110_write_mac_address(struct adin1110_port_priv *port_priv, + int mac_nr, const u8 *addr, + u8 *mask, u32 port_rules) +{ + struct adin1110_priv *priv = port_priv->priv; + u32 offset = mac_nr * 2; + u32 port_rules_mask; + int ret; + u32 val; + + if (!port_priv->nr) + port_rules_mask = ADIN1110_MAC_ADDR_APPLY2PORT; + else + port_rules_mask = ADIN2111_MAC_ADDR_APPLY2PORT2; + + if (port_rules & port_rules_mask) + port_rules_mask |= ADIN1110_MAC_ADDR_TO_HOST | ADIN2111_MAC_ADDR_TO_OTHER_PORT; + + port_rules_mask |= GENMASK(15, 0); + val = port_rules | get_unaligned_be16(&addr[0]); + ret = adin1110_set_bits(priv, ADIN1110_MAC_ADDR_FILTER_UPR + offset, + port_rules_mask, val); + if (ret < 0) + return ret; + + val = get_unaligned_be32(&addr[2]); + ret = adin1110_write_reg(priv, + ADIN1110_MAC_ADDR_FILTER_LWR + offset, val); + if (ret < 0) + return ret; + + /* Only the first two MAC address slots support masking. */ + if (mac_nr < ADIN_MAC_P1_ADDR_SLOT) { + val = get_unaligned_be16(&mask[0]); + ret = adin1110_write_reg(priv, + ADIN1110_MAC_ADDR_MASK_UPR + offset, + val); + if (ret < 0) + return ret; + + val = get_unaligned_be32(&mask[2]); + return adin1110_write_reg(priv, + ADIN1110_MAC_ADDR_MASK_LWR + offset, + val); + } + + return 0; +} + +static int adin1110_clear_mac_address(struct adin1110_priv *priv, int mac_nr) +{ + u32 offset = mac_nr * 2; + int ret; + + ret = adin1110_write_reg(priv, ADIN1110_MAC_ADDR_FILTER_UPR + offset, 0); + if (ret < 0) + return ret; + + ret = adin1110_write_reg(priv, ADIN1110_MAC_ADDR_FILTER_LWR + offset, 0); + if (ret < 0) + return ret; + + /* only the first two MAC address slots are maskable */ + if (mac_nr <= 1) { + ret = adin1110_write_reg(priv, ADIN1110_MAC_ADDR_MASK_UPR + offset, 0); + if (ret < 0) + return ret; + + ret = adin1110_write_reg(priv, ADIN1110_MAC_ADDR_MASK_LWR + offset, 0); + } + + return ret; +} + +static u32 adin1110_port_rules(struct adin1110_port_priv *port_priv, + bool fw_to_host, + bool fw_to_other_port) +{ + u32 port_rules = 0; + + if (!port_priv->nr) + port_rules |= ADIN1110_MAC_ADDR_APPLY2PORT; + else + port_rules |= ADIN2111_MAC_ADDR_APPLY2PORT2; + + if (fw_to_host) + port_rules |= ADIN1110_MAC_ADDR_TO_HOST; + + if (fw_to_other_port && port_priv->priv->forwarding) + port_rules |= ADIN2111_MAC_ADDR_TO_OTHER_PORT; + + return port_rules; +} + +static int adin1110_multicast_filter(struct adin1110_port_priv *port_priv, + int mac_nr, bool accept_multicast) +{ + u8 mask[ETH_ALEN] = {0}; + u8 mac[ETH_ALEN] = {0}; + u32 port_rules = 0; + + mask[0] = BIT(0); + mac[0] = BIT(0); + + if (accept_multicast && port_priv->state == BR_STATE_FORWARDING) + port_rules = adin1110_port_rules(port_priv, true, true); + + return adin1110_write_mac_address(port_priv, mac_nr, mac, + mask, port_rules); +} + +static int adin1110_broadcasts_filter(struct adin1110_port_priv *port_priv, + int mac_nr, bool accept_broadcast) +{ + u32 port_rules = 0; + u8 mask[ETH_ALEN]; + + eth_broadcast_addr(mask); + + if (accept_broadcast && port_priv->state == BR_STATE_FORWARDING) + port_rules = adin1110_port_rules(port_priv, true, true); + + return adin1110_write_mac_address(port_priv, mac_nr, mask, + mask, port_rules); +} + +static int adin1110_set_mac_address(struct net_device *netdev, + const unsigned char *dev_addr) +{ + struct adin1110_port_priv *port_priv = netdev_priv(netdev); + u8 mask[ETH_ALEN]; + u32 port_rules; + u32 mac_slot; + + if (!is_valid_ether_addr(dev_addr)) + return -EADDRNOTAVAIL; + + eth_hw_addr_set(netdev, dev_addr); + eth_broadcast_addr(mask); + + mac_slot = (!port_priv->nr) ? ADIN_MAC_P1_ADDR_SLOT : ADIN_MAC_P2_ADDR_SLOT; + port_rules = adin1110_port_rules(port_priv, true, false); + + return adin1110_write_mac_address(port_priv, mac_slot, netdev->dev_addr, + mask, port_rules); +} + +static int adin1110_ndo_set_mac_address(struct net_device *netdev, void *addr) +{ + struct sockaddr *sa = addr; + int ret; + + ret = eth_prepare_mac_addr_change(netdev, addr); + if (ret < 0) + return ret; + + return adin1110_set_mac_address(netdev, sa->sa_data); +} + +static int adin1110_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) +{ + if (!netif_running(netdev)) + return -EINVAL; + + return phy_do_ioctl(netdev, rq, cmd); +} + +static int adin1110_set_promisc_mode(struct adin1110_port_priv *port_priv, + bool promisc) +{ + struct adin1110_priv *priv = port_priv->priv; + u32 mask; + + if (port_priv->state != BR_STATE_FORWARDING) + promisc = false; + + if (!port_priv->nr) + mask = ADIN1110_FWD_UNK2HOST; + else + mask = ADIN2111_P2_FWD_UNK2HOST; + + return adin1110_set_bits(priv, ADIN1110_CONFIG2, + mask, promisc ? mask : 0); +} + +static int adin1110_setup_rx_mode(struct adin1110_port_priv *port_priv) +{ + int ret; + + ret = adin1110_set_promisc_mode(port_priv, + !!(port_priv->flags & IFF_PROMISC)); + if (ret < 0) + return ret; + + ret = adin1110_multicast_filter(port_priv, ADIN_MAC_MULTICAST_ADDR_SLOT, + !!(port_priv->flags & IFF_ALLMULTI)); + if (ret < 0) + return ret; + + ret = adin1110_broadcasts_filter(port_priv, + ADIN_MAC_BROADCAST_ADDR_SLOT, + !!(port_priv->flags & IFF_BROADCAST)); + if (ret < 0) + return ret; + + return adin1110_set_bits(port_priv->priv, ADIN1110_CONFIG1, + ADIN1110_CONFIG1_SYNC, ADIN1110_CONFIG1_SYNC); +} + +static bool adin1110_can_offload_forwarding(struct adin1110_priv *priv) +{ + int i; + + if (priv->cfg->id != ADIN2111_MAC) + return false; + + /* Can't enable forwarding if ports do not belong to the same bridge */ + if (priv->ports[0]->bridge != priv->ports[1]->bridge || !priv->ports[0]->bridge) + return false; + + /* Can't enable forwarding if there is a port + * that has been blocked by STP. + */ + for (i = 0; i < priv->cfg->ports_nr; i++) { + if (priv->ports[i]->state != BR_STATE_FORWARDING) + return false; + } + + return true; +} + +static void adin1110_rx_mode_work(struct work_struct *work) +{ + struct adin1110_port_priv *port_priv; + struct adin1110_priv *priv; + + port_priv = container_of(work, struct adin1110_port_priv, rx_mode_work); + priv = port_priv->priv; + + mutex_lock(&priv->lock); + adin1110_setup_rx_mode(port_priv); + mutex_unlock(&priv->lock); +} + +static void adin1110_set_rx_mode(struct net_device *dev) +{ + struct adin1110_port_priv *port_priv = netdev_priv(dev); + struct adin1110_priv *priv = port_priv->priv; + + spin_lock(&priv->state_lock); + + port_priv->flags = dev->flags; + schedule_work(&port_priv->rx_mode_work); + + spin_unlock(&priv->state_lock); +} + +static int adin1110_net_open(struct net_device *net_dev) +{ + struct adin1110_port_priv *port_priv = netdev_priv(net_dev); + struct adin1110_priv *priv = port_priv->priv; + u32 val; + int ret; + + mutex_lock(&priv->lock); + + /* Configure MAC to compute and append the FCS itself. */ + ret = adin1110_write_reg(priv, ADIN1110_CONFIG2, ADIN1110_CRC_APPEND); + if (ret < 0) + goto out; + + val = ADIN1110_TX_RDY_IRQ | ADIN1110_RX_RDY_IRQ | ADIN1110_SPI_ERR_IRQ; + if (priv->cfg->id == ADIN2111_MAC) + val |= ADIN2111_RX_RDY_IRQ; + + priv->irq_mask = val; + ret = adin1110_write_reg(priv, ADIN1110_IMASK1, ~val); + if (ret < 0) { + netdev_err(net_dev, "Failed to enable chip IRQs: %d\n", ret); + goto out; + } + + ret = adin1110_read_reg(priv, ADIN1110_TX_SPACE, &val); + if (ret < 0) { + netdev_err(net_dev, "Failed to read TX FIFO space: %d\n", ret); + goto out; + } + + priv->tx_space = 2 * val; + + port_priv->state = BR_STATE_FORWARDING; + ret = adin1110_set_mac_address(net_dev, net_dev->dev_addr); + if (ret < 0) { + netdev_err(net_dev, "Could not set MAC address: %pM, %d\n", + net_dev->dev_addr, ret); + goto out; + } + + ret = adin1110_set_bits(priv, ADIN1110_CONFIG1, ADIN1110_CONFIG1_SYNC, + ADIN1110_CONFIG1_SYNC); + +out: + mutex_unlock(&priv->lock); + + if (ret < 0) + return ret; + + phy_start(port_priv->phydev); + + netif_start_queue(net_dev); + + return 0; +} + +static int adin1110_net_stop(struct net_device *net_dev) +{ + struct adin1110_port_priv *port_priv = netdev_priv(net_dev); + struct adin1110_priv *priv = port_priv->priv; + u32 mask; + int ret; + + mask = !port_priv->nr ? ADIN2111_RX_RDY_IRQ : ADIN1110_RX_RDY_IRQ; + + /* Disable RX RDY IRQs */ + mutex_lock(&priv->lock); + ret = adin1110_set_bits(priv, ADIN1110_IMASK1, mask, mask); + mutex_unlock(&priv->lock); + if (ret < 0) + return ret; + + netif_stop_queue(port_priv->netdev); + flush_work(&port_priv->tx_work); + phy_stop(port_priv->phydev); + + return 0; +} + +static void adin1110_tx_work(struct work_struct *work) +{ + struct adin1110_port_priv *port_priv; + struct adin1110_priv *priv; + struct sk_buff *txb; + int ret; + + port_priv = container_of(work, struct adin1110_port_priv, tx_work); + priv = port_priv->priv; + + mutex_lock(&priv->lock); + + while ((txb = skb_dequeue(&port_priv->txq))) { + ret = adin1110_write_fifo(port_priv, txb); + if (ret < 0) + dev_err_ratelimited(&priv->spidev->dev, + "Frame write error: %d\n", ret); + + dev_kfree_skb(txb); + } + + mutex_unlock(&priv->lock); +} + +static netdev_tx_t adin1110_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct adin1110_port_priv *port_priv = netdev_priv(dev); + struct adin1110_priv *priv = port_priv->priv; + netdev_tx_t netdev_ret = NETDEV_TX_OK; + u32 tx_space_needed; + + tx_space_needed = skb->len + ADIN1110_FRAME_HEADER_LEN + ADIN1110_INTERNAL_SIZE_HEADER_LEN; + if (tx_space_needed > priv->tx_space) { + netif_stop_queue(dev); + netdev_ret = NETDEV_TX_BUSY; + } else { + priv->tx_space -= tx_space_needed; + skb_queue_tail(&port_priv->txq, skb); + } + + schedule_work(&port_priv->tx_work); + + return netdev_ret; +} + +static void adin1110_ndo_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *storage) +{ + struct adin1110_port_priv *port_priv = netdev_priv(dev); + + storage->rx_packets = port_priv->rx_packets; + storage->tx_packets = port_priv->tx_packets; + + storage->rx_bytes = port_priv->rx_bytes; + storage->tx_bytes = port_priv->tx_bytes; +} + +static int adin1110_port_get_port_parent_id(struct net_device *dev, + struct netdev_phys_item_id *ppid) +{ + struct adin1110_port_priv *port_priv = netdev_priv(dev); + struct adin1110_priv *priv = port_priv->priv; + + ppid->id_len = strnlen(priv->mii_bus_name, MAX_PHYS_ITEM_ID_LEN); + memcpy(ppid->id, priv->mii_bus_name, ppid->id_len); + + return 0; +} + +static int adin1110_ndo_get_phys_port_name(struct net_device *dev, + char *name, size_t len) +{ + struct adin1110_port_priv *port_priv = netdev_priv(dev); + int err; + + err = snprintf(name, len, "p%d", port_priv->nr); + if (err >= len) + return -EINVAL; + + return 0; +} + +static const struct net_device_ops adin1110_netdev_ops = { + .ndo_open = adin1110_net_open, + .ndo_stop = adin1110_net_stop, + .ndo_eth_ioctl = adin1110_ioctl, + .ndo_start_xmit = adin1110_start_xmit, + .ndo_set_mac_address = adin1110_ndo_set_mac_address, + .ndo_set_rx_mode = adin1110_set_rx_mode, + .ndo_validate_addr = eth_validate_addr, + .ndo_get_stats64 = adin1110_ndo_get_stats64, + .ndo_get_port_parent_id = adin1110_port_get_port_parent_id, + .ndo_get_phys_port_name = adin1110_ndo_get_phys_port_name, +}; + +static void adin1110_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *di) +{ + strscpy(di->driver, "ADIN1110", sizeof(di->driver)); + strscpy(di->bus_info, dev_name(dev->dev.parent), sizeof(di->bus_info)); +} + +static const struct ethtool_ops adin1110_ethtool_ops = { + .get_drvinfo = adin1110_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, +}; + +static void adin1110_adjust_link(struct net_device *dev) +{ + struct phy_device *phydev = dev->phydev; + + if (!phydev->link) + phy_print_status(phydev); +} + +/* PHY ID is stored in the MAC registers too, + * check spi connection by reading it. + */ +static int adin1110_check_spi(struct adin1110_priv *priv) +{ + struct gpio_desc *reset_gpio; + int ret; + u32 val; + + reset_gpio = devm_gpiod_get_optional(&priv->spidev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return dev_err_probe(&priv->spidev->dev, PTR_ERR(reset_gpio), + "failed to get reset gpio\n"); + if (reset_gpio) { + /* MISO pin is used for internal configuration, can't have + * anyone else disturbing the SDO line. + */ + spi_bus_lock(priv->spidev->controller); + + gpiod_set_value(reset_gpio, 1); + fsleep(10000); + gpiod_set_value(reset_gpio, 0); + + /* Need to wait 90 ms before interacting with + * the MAC after a HW reset. + */ + fsleep(90000); + + spi_bus_unlock(priv->spidev->controller); + } + + ret = adin1110_read_reg(priv, ADIN1110_PHY_ID, &val); + if (ret < 0) + return ret; + + if (val != priv->cfg->phy_id_val) { + dev_err(&priv->spidev->dev, "PHY ID expected: %x, read: %x\n", + priv->cfg->phy_id_val, val); + return -EIO; + } + + return 0; +} + +static int adin1110_hw_forwarding(struct adin1110_priv *priv, bool enable) +{ + int ret; + int i; + + priv->forwarding = enable; + + if (!priv->forwarding) { + for (i = ADIN_MAC_FDB_ADDR_SLOT; i < ADIN_MAC_MAX_ADDR_SLOTS; i++) { + ret = adin1110_clear_mac_address(priv, i); + if (ret < 0) + return ret; + } + } + + /* Forwarding is optimised when MAC runs in Cut Through mode. */ + ret = adin1110_set_bits(priv, ADIN1110_CONFIG2, + ADIN2111_PORT_CUT_THRU_EN, + priv->forwarding ? ADIN2111_PORT_CUT_THRU_EN : 0); + if (ret < 0) + return ret; + + for (i = 0; i < priv->cfg->ports_nr; i++) { + ret = adin1110_setup_rx_mode(priv->ports[i]); + if (ret < 0) + return ret; + } + + return ret; +} + +static int adin1110_port_bridge_join(struct adin1110_port_priv *port_priv, + struct net_device *bridge) +{ + struct adin1110_priv *priv = port_priv->priv; + int ret; + + port_priv->bridge = bridge; + + if (adin1110_can_offload_forwarding(priv)) { + mutex_lock(&priv->lock); + ret = adin1110_hw_forwarding(priv, true); + mutex_unlock(&priv->lock); + + if (ret < 0) + return ret; + } + + return adin1110_set_mac_address(port_priv->netdev, bridge->dev_addr); +} + +static int adin1110_port_bridge_leave(struct adin1110_port_priv *port_priv, + struct net_device *bridge) +{ + struct adin1110_priv *priv = port_priv->priv; + int ret; + + port_priv->bridge = NULL; + + mutex_lock(&priv->lock); + ret = adin1110_hw_forwarding(priv, false); + mutex_unlock(&priv->lock); + + return ret; +} + +static bool adin1110_port_dev_check(const struct net_device *dev) +{ + return dev->netdev_ops == &adin1110_netdev_ops; +} + +static int adin1110_netdevice_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct adin1110_port_priv *port_priv = netdev_priv(dev); + struct netdev_notifier_changeupper_info *info = ptr; + int ret = 0; + + if (!adin1110_port_dev_check(dev)) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_CHANGEUPPER: + if (netif_is_bridge_master(info->upper_dev)) { + if (info->linking) + ret = adin1110_port_bridge_join(port_priv, info->upper_dev); + else + ret = adin1110_port_bridge_leave(port_priv, info->upper_dev); + } + break; + default: + break; + } + + return notifier_from_errno(ret); +} + +static struct notifier_block adin1110_netdevice_nb = { + .notifier_call = adin1110_netdevice_event, +}; + +static void adin1110_disconnect_phy(void *data) +{ + phy_disconnect(data); +} + +static int adin1110_port_set_forwarding_state(struct adin1110_port_priv *port_priv) +{ + struct adin1110_priv *priv = port_priv->priv; + int ret; + + port_priv->state = BR_STATE_FORWARDING; + + mutex_lock(&priv->lock); + ret = adin1110_set_mac_address(port_priv->netdev, + port_priv->netdev->dev_addr); + if (ret < 0) + goto out; + + if (adin1110_can_offload_forwarding(priv)) + ret = adin1110_hw_forwarding(priv, true); + else + ret = adin1110_setup_rx_mode(port_priv); +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int adin1110_port_set_blocking_state(struct adin1110_port_priv *port_priv) +{ + u8 mac[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00}; + struct adin1110_priv *priv = port_priv->priv; + u8 mask[ETH_ALEN]; + u32 port_rules; + int mac_slot; + int ret; + + port_priv->state = BR_STATE_BLOCKING; + + mutex_lock(&priv->lock); + + mac_slot = (!port_priv->nr) ? ADIN_MAC_P1_ADDR_SLOT : ADIN_MAC_P2_ADDR_SLOT; + ret = adin1110_clear_mac_address(priv, mac_slot); + if (ret < 0) + goto out; + + ret = adin1110_hw_forwarding(priv, false); + if (ret < 0) + goto out; + + /* Allow only BPDUs to be passed to the CPU */ + eth_broadcast_addr(mask); + port_rules = adin1110_port_rules(port_priv, true, false); + ret = adin1110_write_mac_address(port_priv, mac_slot, mac, + mask, port_rules); +out: + mutex_unlock(&priv->lock); + + return ret; +} + +/* ADIN1110/2111 does not have any native STP support. + * Listen for bridge core state changes and + * allow all frames to pass or only the BPDUs. + */ +static int adin1110_port_attr_stp_state_set(struct adin1110_port_priv *port_priv, + u8 state) +{ + switch (state) { + case BR_STATE_FORWARDING: + return adin1110_port_set_forwarding_state(port_priv); + case BR_STATE_LEARNING: + case BR_STATE_LISTENING: + case BR_STATE_DISABLED: + case BR_STATE_BLOCKING: + return adin1110_port_set_blocking_state(port_priv); + default: + return -EINVAL; + } +} + +static int adin1110_port_attr_set(struct net_device *dev, const void *ctx, + const struct switchdev_attr *attr, + struct netlink_ext_ack *extack) +{ + struct adin1110_port_priv *port_priv = netdev_priv(dev); + + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_STP_STATE: + return adin1110_port_attr_stp_state_set(port_priv, + attr->u.stp_state); + default: + return -EOPNOTSUPP; + } +} + +static int adin1110_switchdev_blocking_event(struct notifier_block *unused, + unsigned long event, + void *ptr) +{ + struct net_device *netdev = switchdev_notifier_info_to_dev(ptr); + int ret; + + if (event == SWITCHDEV_PORT_ATTR_SET) { + ret = switchdev_handle_port_attr_set(netdev, ptr, + adin1110_port_dev_check, + adin1110_port_attr_set); + + return notifier_from_errno(ret); + } + + return NOTIFY_DONE; +} + +static struct notifier_block adin1110_switchdev_blocking_notifier = { + .notifier_call = adin1110_switchdev_blocking_event, +}; + +static void adin1110_fdb_offload_notify(struct net_device *netdev, + struct switchdev_notifier_fdb_info *rcv) +{ + struct switchdev_notifier_fdb_info info = {}; + + info.addr = rcv->addr; + info.vid = rcv->vid; + info.offloaded = true; + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, + netdev, &info.info, NULL); +} + +static int adin1110_fdb_add(struct adin1110_port_priv *port_priv, + struct switchdev_notifier_fdb_info *fdb) +{ + struct adin1110_priv *priv = port_priv->priv; + struct adin1110_port_priv *other_port; + u8 mask[ETH_ALEN]; + u32 port_rules; + int mac_nr; + u32 val; + int ret; + + netdev_dbg(port_priv->netdev, + "DEBUG: %s: MACID = %pM vid = %u flags = %u %u -- port %d\n", + __func__, fdb->addr, fdb->vid, fdb->added_by_user, + fdb->offloaded, port_priv->nr); + + if (!priv->forwarding) + return 0; + + if (fdb->is_local) + return -EINVAL; + + /* Find free FDB slot on device. */ + for (mac_nr = ADIN_MAC_FDB_ADDR_SLOT; mac_nr < ADIN_MAC_MAX_ADDR_SLOTS; mac_nr++) { + ret = adin1110_read_reg(priv, ADIN1110_MAC_ADDR_FILTER_UPR + (mac_nr * 2), &val); + if (ret < 0) + return ret; + if (!val) + break; + } + + if (mac_nr == ADIN_MAC_MAX_ADDR_SLOTS) + return -ENOMEM; + + other_port = priv->ports[!port_priv->nr]; + port_rules = adin1110_port_rules(other_port, false, true); + eth_broadcast_addr(mask); + + return adin1110_write_mac_address(other_port, mac_nr, (u8 *)fdb->addr, + mask, port_rules); +} + +static int adin1110_read_mac(struct adin1110_priv *priv, int mac_nr, u8 *addr) +{ + u32 val; + int ret; + + ret = adin1110_read_reg(priv, ADIN1110_MAC_ADDR_FILTER_UPR + (mac_nr * 2), &val); + if (ret < 0) + return ret; + + put_unaligned_be16(val, addr); + + ret = adin1110_read_reg(priv, ADIN1110_MAC_ADDR_FILTER_LWR + (mac_nr * 2), &val); + if (ret < 0) + return ret; + + put_unaligned_be32(val, addr + 2); + + return 0; +} + +static int adin1110_fdb_del(struct adin1110_port_priv *port_priv, + struct switchdev_notifier_fdb_info *fdb) +{ + struct adin1110_priv *priv = port_priv->priv; + u8 addr[ETH_ALEN]; + int mac_nr; + int ret; + + netdev_dbg(port_priv->netdev, + "DEBUG: %s: MACID = %pM vid = %u flags = %u %u -- port %d\n", + __func__, fdb->addr, fdb->vid, fdb->added_by_user, + fdb->offloaded, port_priv->nr); + + if (fdb->is_local) + return -EINVAL; + + for (mac_nr = ADIN_MAC_FDB_ADDR_SLOT; mac_nr < ADIN_MAC_MAX_ADDR_SLOTS; mac_nr++) { + ret = adin1110_read_mac(priv, mac_nr, addr); + if (ret < 0) + return ret; + + if (ether_addr_equal(addr, fdb->addr)) { + ret = adin1110_clear_mac_address(priv, mac_nr); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static void adin1110_switchdev_event_work(struct work_struct *work) +{ + struct adin1110_switchdev_event_work *switchdev_work; + struct adin1110_port_priv *port_priv; + int ret; + + switchdev_work = container_of(work, struct adin1110_switchdev_event_work, work); + port_priv = switchdev_work->port_priv; + + mutex_lock(&port_priv->priv->lock); + + switch (switchdev_work->event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + ret = adin1110_fdb_add(port_priv, &switchdev_work->fdb_info); + if (!ret) + adin1110_fdb_offload_notify(port_priv->netdev, + &switchdev_work->fdb_info); + break; + case SWITCHDEV_FDB_DEL_TO_DEVICE: + adin1110_fdb_del(port_priv, &switchdev_work->fdb_info); + break; + default: + break; + } + + mutex_unlock(&port_priv->priv->lock); + + kfree(switchdev_work->fdb_info.addr); + kfree(switchdev_work); + dev_put(port_priv->netdev); +} + +/* called under rcu_read_lock() */ +static int adin1110_switchdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *netdev = switchdev_notifier_info_to_dev(ptr); + struct adin1110_port_priv *port_priv = netdev_priv(netdev); + struct adin1110_switchdev_event_work *switchdev_work; + struct switchdev_notifier_fdb_info *fdb_info = ptr; + + if (!adin1110_port_dev_check(netdev)) + return NOTIFY_DONE; + + switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); + if (WARN_ON(!switchdev_work)) + return NOTIFY_BAD; + + INIT_WORK(&switchdev_work->work, adin1110_switchdev_event_work); + switchdev_work->port_priv = port_priv; + switchdev_work->event = event; + + switch (event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + case SWITCHDEV_FDB_DEL_TO_DEVICE: + memcpy(&switchdev_work->fdb_info, ptr, + sizeof(switchdev_work->fdb_info)); + switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); + + if (!switchdev_work->fdb_info.addr) + goto err_addr_alloc; + + ether_addr_copy((u8 *)switchdev_work->fdb_info.addr, + fdb_info->addr); + dev_hold(netdev); + break; + default: + kfree(switchdev_work); + return NOTIFY_DONE; + } + + queue_work(system_long_wq, &switchdev_work->work); + + return NOTIFY_DONE; + +err_addr_alloc: + kfree(switchdev_work); + return NOTIFY_BAD; +} + +static struct notifier_block adin1110_switchdev_notifier = { + .notifier_call = adin1110_switchdev_event, +}; + +static void adin1110_unregister_notifiers(void) +{ + unregister_switchdev_blocking_notifier(&adin1110_switchdev_blocking_notifier); + unregister_switchdev_notifier(&adin1110_switchdev_notifier); + unregister_netdevice_notifier(&adin1110_netdevice_nb); +} + +static int adin1110_setup_notifiers(void) +{ + int ret; + + ret = register_netdevice_notifier(&adin1110_netdevice_nb); + if (ret < 0) + return ret; + + ret = register_switchdev_notifier(&adin1110_switchdev_notifier); + if (ret < 0) + goto err_netdev; + + ret = register_switchdev_blocking_notifier(&adin1110_switchdev_blocking_notifier); + if (ret < 0) + goto err_sdev; + + return 0; + +err_sdev: + unregister_switchdev_notifier(&adin1110_switchdev_notifier); + +err_netdev: + unregister_netdevice_notifier(&adin1110_netdevice_nb); + + return ret; +} + +static int adin1110_probe_netdevs(struct adin1110_priv *priv) +{ + struct device *dev = &priv->spidev->dev; + struct adin1110_port_priv *port_priv; + struct net_device *netdev; + int ret; + int i; + + for (i = 0; i < priv->cfg->ports_nr; i++) { + netdev = devm_alloc_etherdev(dev, sizeof(*port_priv)); + if (!netdev) + return -ENOMEM; + + port_priv = netdev_priv(netdev); + port_priv->netdev = netdev; + port_priv->priv = priv; + port_priv->cfg = priv->cfg; + port_priv->nr = i; + priv->ports[i] = port_priv; + SET_NETDEV_DEV(netdev, dev); + + ret = device_get_ethdev_address(dev, netdev); + if (ret < 0) + return ret; + + netdev->irq = priv->spidev->irq; + INIT_WORK(&port_priv->tx_work, adin1110_tx_work); + INIT_WORK(&port_priv->rx_mode_work, adin1110_rx_mode_work); + skb_queue_head_init(&port_priv->txq); + + netif_carrier_off(netdev); + + netdev->if_port = IF_PORT_10BASET; + netdev->netdev_ops = &adin1110_netdev_ops; + netdev->ethtool_ops = &adin1110_ethtool_ops; + netdev->priv_flags |= IFF_UNICAST_FLT; + netdev->netns_immutable = true; + + port_priv->phydev = get_phy_device(priv->mii_bus, i + 1, false); + if (IS_ERR(port_priv->phydev)) { + netdev_err(netdev, "Could not find PHY with device address: %d.\n", i); + return PTR_ERR(port_priv->phydev); + } + + port_priv->phydev = phy_connect(netdev, + phydev_name(port_priv->phydev), + adin1110_adjust_link, + PHY_INTERFACE_MODE_INTERNAL); + if (IS_ERR(port_priv->phydev)) { + netdev_err(netdev, "Could not connect PHY with device address: %d.\n", i); + return PTR_ERR(port_priv->phydev); + } + + ret = devm_add_action_or_reset(dev, adin1110_disconnect_phy, + port_priv->phydev); + if (ret < 0) + return ret; + } + + /* ADIN1110 INT_N pin will be used to signal the host */ + ret = devm_request_threaded_irq(dev, priv->spidev->irq, NULL, + adin1110_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev_name(dev), priv); + if (ret < 0) + return ret; + + for (i = 0; i < priv->cfg->ports_nr; i++) { + ret = devm_register_netdev(dev, priv->ports[i]->netdev); + if (ret < 0) { + dev_err(dev, "Failed to register network device.\n"); + return ret; + } + } + + return 0; +} + +static int adin1110_probe(struct spi_device *spi) +{ + const struct spi_device_id *dev_id = spi_get_device_id(spi); + struct device *dev = &spi->dev; + struct adin1110_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct adin1110_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->spidev = spi; + priv->cfg = &adin1110_cfgs[dev_id->driver_data]; + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + + mutex_init(&priv->lock); + spin_lock_init(&priv->state_lock); + + /* use of CRC on control and data transactions is pin dependent */ + priv->append_crc = device_property_read_bool(dev, "adi,spi-crc"); + if (priv->append_crc) + crc8_populate_msb(adin1110_crc_table, 0x7); + + ret = adin1110_check_spi(priv); + if (ret < 0) { + dev_err(dev, "Probe SPI Read check failed: %d\n", ret); + return ret; + } + + ret = adin1110_write_reg(priv, ADIN1110_RESET, ADIN1110_SWRESET); + if (ret < 0) + return ret; + + ret = adin1110_register_mdiobus(priv, dev); + if (ret < 0) { + dev_err(dev, "Could not register MDIO bus %d\n", ret); + return ret; + } + + return adin1110_probe_netdevs(priv); +} + +static const struct of_device_id adin1110_match_table[] = { + { .compatible = "adi,adin1110" }, + { .compatible = "adi,adin2111" }, + { } +}; +MODULE_DEVICE_TABLE(of, adin1110_match_table); + +static const struct spi_device_id adin1110_spi_id[] = { + { .name = "adin1110", .driver_data = ADIN1110_MAC }, + { .name = "adin2111", .driver_data = ADIN2111_MAC }, + { } +}; +MODULE_DEVICE_TABLE(spi, adin1110_spi_id); + +static struct spi_driver adin1110_driver = { + .driver = { + .name = "adin1110", + .of_match_table = adin1110_match_table, + }, + .probe = adin1110_probe, + .id_table = adin1110_spi_id, +}; + +static int __init adin1110_driver_init(void) +{ + int ret; + + ret = adin1110_setup_notifiers(); + if (ret < 0) + return ret; + + ret = spi_register_driver(&adin1110_driver); + if (ret < 0) { + adin1110_unregister_notifiers(); + return ret; + } + + return 0; +} + +static void __exit adin1110_exit(void) +{ + adin1110_unregister_notifiers(); + spi_unregister_driver(&adin1110_driver); +} +module_init(adin1110_driver_init); +module_exit(adin1110_exit); + +MODULE_DESCRIPTION("ADIN1110 Network driver"); +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c new file mode 100644 index 000000000..93710baca --- /dev/null +++ b/drivers/net/ethernet/adi/adin1140.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include <linux/cleanup.h> +#include <linux/etherdevice.h> +#include <linux/kernel.h> +#include <linux/mdio.h> +#include <linux/module.h> +#include <linux/oa_tc6.h> +#include <linux/phy.h> + +#define ADIN1140_MAC_CONFIG2_STAT_CLR_RD BIT(6) +#define ADIN1140_MAC_CONFIG2_FWD_UNK2HOST BIT(2) + +#define ADIN1140_MAC_P1_LOOP_ADDR_REG 0xC4 + +#define ADIN1140_MAC_ADDR_FILT_UPR_REG 0x50 +#define ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 BIT(30) +#define ADIN1140_MAC_ADDR_FILT_TO_HOST BIT(16) + +#define ADIN1140_MAC_ADDR_FILT_LWR_REG 0x51 + +#define ADIN1140_MAC_ADDR_MASK_UPR_REG 0x70 +#define ADIN1140_MAC_ADDR_MASK_LWR_REG 0x71 + +#define ADIN1140_MAC_FILT_MC_SLOT 0U +#define ADIN1140_MAC_FILT_BC_SLOT 1U +#define ADIN1140_MAC_FILT_UC_SLOT 2U +#define ADIN1140_MAC_FILT_MAX_SLOT 16U +#define ADIN1140_MAC_FILT_MASK_LIMIT 2U + +#define ADIN1140_MAC_RX_FRAME_CNT 0xA1 +#define ADIN1140_MAC_RX_BC_FRAME_CNT 0xA2 +#define ADIN1140_MAC_RX_MC_FRAME_CNT 0xA3 +#define ADIN1140_MAC_RX_CRC_ERR_CNT 0xA5 +#define ADIN1140_MAC_RX_ALIGN_ERR_CNT 0xA6 +#define ADIN1140_MAC_RX_PREAMBLE_ERR_CNT 0xA7 +#define ADIN1140_MAC_RX_SHORT_ERR_CNT 0xA8 +#define ADIN1140_MAC_RX_LONG_ERR_CNT 0xA9 +#define ADIN1140_MAC_RX_PHY_ERR_CNT 0xAA +#define ADIN1140_MAC_RX_DRP_FULL_CNT 0xAB +#define ADIN1140_MAC_RX_DRP_FILTER_CNT 0xAD +#define ADIN1140_MAC_RX_IFG_ERR_CNT 0xAE +#define ADIN1140_MAC_TX_FRAME_CNT 0xB1 +#define ADIN1140_MAC_TX_BC_FRAME_CNT 0xB2 +#define ADIN1140_MAC_TX_MC_FRAME_CNT 0xB3 +#define ADIN1140_MAC_TX_SINGLE_COL_CNT 0xB5 +#define ADIN1140_MAC_TX_MULTI_COL_CNT 0xB6 +#define ADIN1140_MAC_TX_DEFERRED_CNT 0xB7 +#define ADIN1140_MAC_TX_LATE_COL_CNT 0xB8 +#define ADIN1140_MAC_TX_EXCESS_COL_CNT 0xB9 +#define ADIN1140_MAC_TX_UNDERRUN_CNT 0xBA + +/* ADIN1140_MAC_FILT_MAX_SLOT - 3 (multicast, broadcast and unicast + * reserved slots) + */ +#define ADIN1140_MAC_FILT_AVAIL 13U + +#define ADIN1140_PHY_CTRL_DEFAULT 0x1000 +#define ADIN1140_PHY_STATUS_DEFAULT 0x082D +#define ADIN1140_PHY_ID1 0x0283 +#define ADIN1140_PHY_ID2 0xBE00 + +#define ADIN1140_STATS_CHECK_DELAY (3 * HZ) + +enum adin1140_statistics_entry { + rx_frames, + rx_bc_frames, + rx_mc_frames, + rx_crc_errors, + rx_align_errors, + rx_preamble_errors, + rx_short_frame_errors, + rx_long_frame_errors, + rx_phy_errors, + rx_fifo_full_dropped, + rx_addr_filter_dropped, + rx_ifg_errors, + tx_frames, + tx_bc_frames, + tx_mc_frames, + tx_single_collision, + tx_multi_collision, + tx_deferred, + tx_late_collision, + tx_excess_collision, + tx_underrun, + ADIN1140_STATS_CNT, +}; + +struct adin1140_statistics_reg { + const char *name; + enum adin1140_statistics_entry idx; +}; + +struct adin1140_priv { + struct net_device *netdev; + struct oa_tc6 *tc6; + struct mii_bus *mdiobus; + struct phy_device *phydev; + struct delayed_work stats_work; + + /* Protects stats[] from concurrent updates in adin1140_stats_work + * and reads in the get_stats functions + */ + spinlock_t stat_lock; + u64 stats[ADIN1140_STATS_CNT]; +}; + +static const u32 adin1140_stat_regs[] = { + [rx_frames] = ADIN1140_MAC_RX_FRAME_CNT, + [rx_bc_frames] = ADIN1140_MAC_RX_BC_FRAME_CNT, + [rx_mc_frames] = ADIN1140_MAC_RX_MC_FRAME_CNT, + [rx_crc_errors] = ADIN1140_MAC_RX_CRC_ERR_CNT, + [rx_align_errors] = ADIN1140_MAC_RX_ALIGN_ERR_CNT, + [rx_preamble_errors] = ADIN1140_MAC_RX_PREAMBLE_ERR_CNT, + [rx_short_frame_errors] = ADIN1140_MAC_RX_SHORT_ERR_CNT, + [rx_long_frame_errors] = ADIN1140_MAC_RX_LONG_ERR_CNT, + [rx_phy_errors] = ADIN1140_MAC_RX_PHY_ERR_CNT, + [rx_fifo_full_dropped] = ADIN1140_MAC_RX_DRP_FULL_CNT, + [rx_addr_filter_dropped] = ADIN1140_MAC_RX_DRP_FILTER_CNT, + [rx_ifg_errors] = ADIN1140_MAC_RX_IFG_ERR_CNT, + [tx_frames] = ADIN1140_MAC_TX_FRAME_CNT, + [tx_bc_frames] = ADIN1140_MAC_TX_BC_FRAME_CNT, + [tx_mc_frames] = ADIN1140_MAC_TX_MC_FRAME_CNT, + [tx_single_collision] = ADIN1140_MAC_TX_SINGLE_COL_CNT, + [tx_multi_collision] = ADIN1140_MAC_TX_MULTI_COL_CNT, + [tx_deferred] = ADIN1140_MAC_TX_DEFERRED_CNT, + [tx_late_collision] = ADIN1140_MAC_TX_LATE_COL_CNT, + [tx_excess_collision] = ADIN1140_MAC_TX_EXCESS_COL_CNT, + [tx_underrun] = ADIN1140_MAC_TX_UNDERRUN_CNT, +}; + +static const struct adin1140_statistics_reg adin1140_stats[] = { + {.name = "rx_preamble_errors", .idx = rx_preamble_errors}, + {.name = "rx_ifg_errors", .idx = rx_ifg_errors}, +}; + +static int adin1140_mac_filter_set(struct adin1140_priv *priv, + const u8 *addr, const u8 *mask, + u8 slot) +{ + u32 reg_address; + u32 val; + int ret; + + if (slot >= ADIN1140_MAC_FILT_MAX_SLOT) + return -ENOSPC; + + reg_address = ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot; + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be16(&addr[0]) | + ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 | + ADIN1140_MAC_ADDR_FILT_TO_HOST); + if (ret) + return ret; + + reg_address = ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot; + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be32(&addr[2])); + if (ret) + return ret; + + /* Only the first 2 destination MAC filter slots support masking. + * For the other entries, the destination address in the received + * frame must match exactly. + */ + if (slot >= ADIN1140_MAC_FILT_MASK_LIMIT) + return 0; + + val = get_unaligned_be16(&mask[0]); + reg_address = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot); + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); + if (ret) + return ret; + + val = get_unaligned_be32(&mask[2]); + reg_address = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot); + + return oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); +} + +static int adin1140_mac_filter_clear(struct adin1140_priv *priv, u8 slot) +{ + u8 mask[ETH_ALEN]; + u8 addr[ETH_ALEN]; + + memset(mask, 0xFF, ETH_ALEN); + memset(addr, 0x0, ETH_ALEN); + + return adin1140_mac_filter_set(priv, addr, mask, slot); +} + +static int adin1140_filter_unicast(struct adin1140_priv *priv) +{ + /* Only the first 2 filter slots support masking, so no unicast + * address will ever need a mask. The first slots are used for the + * all multicast and broadcast filter. + */ + return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, NULL, + ADIN1140_MAC_FILT_UC_SLOT); +} + +static int adin1140_filter_all_multicast(struct adin1140_priv *priv, bool en) +{ + u8 multicast_addr[ETH_ALEN] = {1, 0, 0, 0, 0, 0}; + + if (en) + return adin1140_mac_filter_set(priv, multicast_addr, + multicast_addr, + ADIN1140_MAC_FILT_MC_SLOT); + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_MC_SLOT); +} + +static int adin1140_filter_broadcast(struct adin1140_priv *priv, bool enabled) +{ + u8 mask[ETH_ALEN]; + + if (enabled) { + memset(mask, 0xFF, ETH_ALEN); + return adin1140_mac_filter_set(priv, mask, mask, + ADIN1140_MAC_FILT_BC_SLOT); + } + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_BC_SLOT); +} + +static int adin1140_default_filter_config(struct adin1140_priv *priv) +{ + int ret; + + ret = adin1140_filter_broadcast(priv, true); + if (ret) + return ret; + + return adin1140_filter_unicast(priv); +} + +static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enabled) +{ + int ret; + u32 val; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, &val); + if (ret) + return ret; + + if (enabled) + val |= ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + else + val &= ~ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + + return oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, val); +} + +static int adin1140_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct adin1140_priv *priv = netdev_priv(dev); + struct netdev_hw_addr *ha; + bool all_multi, promisc; + u32 mac_addrs; + u8 slot, i; + int ret; + + /* The ADIN1140 has 16 dest MAC address filter slots: + * 0 - reserved for all multicast filter. + * 1 - reserved for broadcast filter. + * 2 - reserved for the device's own unicast MAC. + * 3 -> 15 - available for other unicast/multicast filters. + */ + + mac_addrs = netdev_hw_addr_list_count(uc); + all_multi = false; + promisc = false; + + if (priv->netdev->flags & IFF_PROMISC) + promisc = true; + else if (priv->netdev->flags & IFF_ALLMULTI) + all_multi = true; + else + mac_addrs += netdev_hw_addr_list_count(mc); + + if (mac_addrs > ADIN1140_MAC_FILT_AVAIL) { + /* The filter table is full. Enable promisc mode. */ + promisc = true; + } + + ret = adin1140_promiscuous_mode(priv, promisc); + if (ret) + return ret; + + ret = adin1140_filter_all_multicast(priv, all_multi); + if (ret) + return ret; + + slot = ADIN1140_MAC_FILT_UC_SLOT + 1; + if (!promisc) { + netdev_hw_addr_list_for_each(ha, uc) { + ret = adin1140_mac_filter_set(priv, ha->addr, NULL, + slot); + if (ret) + return ret; + + slot++; + } + + if (!all_multi) { + netdev_hw_addr_list_for_each(ha, mc) { + ret = adin1140_mac_filter_set(priv, ha->addr, + NULL, slot); + if (ret) + return ret; + + slot++; + } + } + } + + for (i = slot; i < ADIN1140_MAC_FILT_MAX_SLOT; i++) { + ret = adin1140_mac_filter_clear(priv, i); + if (ret) + return ret; + } + + return 0; +} + +static void adin1140_stats_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adin1140_priv *priv; + u32 reg_val; + int ret; + u32 i; + + priv = container_of(dwork, struct adin1140_priv, stats_work); + + for (i = 0; i < ARRAY_SIZE(adin1140_stat_regs); i++) { + ret = oa_tc6_read_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + adin1140_stat_regs[i], + ®_val); + if (ret) + goto out; + + scoped_guard(spinlock, &priv->stat_lock) + priv->stats[i] += reg_val; + } + +out: + schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY); +} + +static int adin1140_configure(struct adin1140_priv *priv) +{ + u32 reg_val; + int ret; + + ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6); + if (ret) + return ret; + + /* Disable MAC loopback */ + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + ADIN1140_MAC_P1_LOOP_ADDR_REG, 0x0); + if (ret) + return ret; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, ®_val); + if (ret) + return ret; + + reg_val |= ADIN1140_MAC_CONFIG2_STAT_CLR_RD; + + ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, reg_val); + if (ret) + return ret; + + return adin1140_default_filter_config(priv); +} + +static int adin1140_open(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + schedule_delayed_work(&priv->stats_work, ADIN1140_STATS_CHECK_DELAY); + + phy_start(netdev->phydev); + netif_start_queue(netdev); + + return 0; +} + +static int adin1140_close(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + cancel_delayed_work_sync(&priv->stats_work); + + netif_stop_queue(netdev); + phy_stop(netdev->phydev); + + return 0; +} + +static netdev_tx_t adin1140_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + /* The MAC doesn't automatically pad the frame to a 60 byte minimum + * size in case the host sends a shorter skb, so we have to do it in + * the driver. The FCS will be added by the MAC. + */ + if (skb_put_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + return oa_tc6_start_xmit(priv->tc6, skb); +} + +static int adin1140_set_mac_address(struct net_device *netdev, void *addr) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + struct sockaddr *address = addr; + u8 mask[ETH_ALEN]; + int ret; + + ret = eth_prepare_mac_addr_change(netdev, addr); + if (ret < 0) + return ret; + + if (ether_addr_equal(address->sa_data, netdev->dev_addr)) + return 0; + + memset(mask, 0xFF, ETH_ALEN); + ret = adin1140_mac_filter_set(priv, address->sa_data, mask, + ADIN1140_MAC_FILT_UC_SLOT); + if (ret) + return ret; + + eth_commit_mac_addr_change(netdev, addr); + + return 0; +} + +static void __adin1140_ndo_get_stats64(struct adin1140_priv *priv, + struct rtnl_link_stats64 *storage) +{ + storage->rx_errors = priv->stats[rx_crc_errors] + + priv->stats[rx_align_errors] + + priv->stats[rx_preamble_errors] + + priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors] + + priv->stats[rx_phy_errors] + + priv->stats[rx_ifg_errors]; + + storage->tx_errors = priv->stats[tx_excess_collision] + + priv->stats[tx_underrun]; + + storage->rx_dropped = priv->stats[rx_fifo_full_dropped] + + priv->stats[rx_addr_filter_dropped]; + + storage->multicast = priv->stats[rx_mc_frames]; + + storage->collisions = priv->stats[tx_single_collision] + + priv->stats[tx_multi_collision]; + + storage->rx_length_errors = priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors]; + storage->rx_over_errors = priv->stats[rx_fifo_full_dropped]; + storage->rx_crc_errors = priv->stats[rx_crc_errors]; + storage->rx_frame_errors = priv->stats[rx_align_errors]; + storage->rx_missed_errors = priv->stats[rx_fifo_full_dropped]; + + storage->tx_aborted_errors = priv->stats[tx_excess_collision]; + storage->tx_fifo_errors = priv->stats[tx_underrun]; + storage->tx_window_errors = priv->stats[tx_late_collision]; +} + +static void adin1140_ndo_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *storage) +{ + struct adin1140_priv *priv = netdev_priv(dev); + + storage->rx_packets = priv->netdev->stats.rx_packets; + storage->tx_packets = priv->netdev->stats.tx_packets; + + storage->rx_bytes = priv->netdev->stats.rx_bytes; + storage->tx_bytes = priv->netdev->stats.tx_bytes; + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_ndo_get_stats64(priv, storage); +} + +static void adin1140_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, "ADIN1140", sizeof(info->driver)); + strscpy(info->bus_info, dev_name(netdev->dev.parent), + sizeof(info->bus_info)); +} + +static void adin1140_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + u32 i; + + scoped_guard(spinlock, &priv->stat_lock) { + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + data[i] = priv->stats[adin1140_stats[i].idx]; + } +} + +static void adin1140_get_ethtool_strings(struct net_device *netdev, u32 sset, + u8 *p) +{ + u32 i; + + switch (sset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + ethtool_puts(&p, adin1140_stats[i].name); + + break; + } +} + +static int adin1140_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(adin1140_stats); + default: + return -EOPNOTSUPP; + } +} + +static void __adin1140_eth_mac_stats(struct adin1140_priv *priv, + struct ethtool_eth_mac_stats *mac_stats) +{ + mac_stats->FramesReceivedOK = priv->stats[rx_frames]; + mac_stats->BroadcastFramesReceivedOK = priv->stats[rx_bc_frames]; + mac_stats->MulticastFramesReceivedOK = priv->stats[rx_mc_frames]; + mac_stats->FrameCheckSequenceErrors = priv->stats[rx_crc_errors]; + mac_stats->AlignmentErrors = priv->stats[rx_align_errors]; + mac_stats->FrameTooLongErrors = priv->stats[rx_long_frame_errors]; + mac_stats->FramesLostDueToIntMACRcvError = + priv->stats[rx_fifo_full_dropped]; + mac_stats->FramesTransmittedOK = priv->stats[tx_frames]; + mac_stats->BroadcastFramesXmittedOK = priv->stats[tx_bc_frames]; + mac_stats->MulticastFramesXmittedOK = priv->stats[tx_mc_frames]; + mac_stats->SingleCollisionFrames = priv->stats[tx_single_collision]; + mac_stats->MultipleCollisionFrames = priv->stats[tx_multi_collision]; + mac_stats->FramesWithDeferredXmissions = priv->stats[tx_deferred]; + mac_stats->LateCollisions = priv->stats[tx_late_collision]; + mac_stats->FramesAbortedDueToXSColls = + priv->stats[tx_excess_collision]; + mac_stats->FramesLostDueToIntMACXmitError = priv->stats[tx_underrun]; +} + +static void adin1140_get_eth_mac_stats(struct net_device *netdev, + struct ethtool_eth_mac_stats *mac_stats) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_eth_mac_stats(priv, mac_stats); +} + +static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum) +{ + /* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 - + * 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be + * accessed while frames are being received by the PHY. In case this + * happens the CONFIG0 and CONFIG2 register values will get corrupted, + * getting a random value. Both reads and writes cause the same + * behavior. This is a workaround that avoids MDIO accesses all + * together. Since this is a 10BASE-T1S PHY, only the loopback and + * reset (AN) bits in the control register (0x0) can be written. + * These functionalities have custom implementations in the PHY + * driver. C45 accesses do not cause this issue. + */ + + switch (regnum) { + case MII_BMCR: + return ADIN1140_PHY_CTRL_DEFAULT; + case MII_BMSR: + return ADIN1140_PHY_STATUS_DEFAULT; + case MII_PHYSID1: + return ADIN1140_PHY_ID1; + case MII_PHYSID2: + return ADIN1140_PHY_ID2; + default: + return 0xFFFF; + } +} + +static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + return -EIO; +} + +static int adin1140_mdio_register(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + priv->mdiobus = devm_mdiobus_alloc(&spidev->dev); + if (!priv->mdiobus) + return dev_err_probe(&spidev->dev, -ENOMEM, + "MDIO bus alloc failed\n"); + + priv->mdiobus->name = "adin1140-mdiobus"; + priv->mdiobus->priv = priv->tc6; + priv->mdiobus->parent = &spidev->dev; + priv->mdiobus->phy_mask = GENMASK(31, 1); + priv->mdiobus->read = adin1140_mdiobus_read; + priv->mdiobus->write = adin1140_mdiobus_write; + priv->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45; + priv->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45; + + snprintf(priv->mdiobus->id, MII_BUS_ID_SIZE, "adin1140-%s.%u", + dev_name(&spidev->dev), spi_get_chipselect(spidev, 0)); + + return devm_mdiobus_register(&spidev->dev, priv->mdiobus); +} + +static void adin1140_handle_link_change(struct net_device *netdev) +{ + phy_print_status(netdev->phydev); +} + +static void adin1140_phy_remove(void *data) +{ + phy_disconnect(data); +} + +static int adin1140_phy_init(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + int ret; + + ret = adin1140_mdio_register(priv, spidev); + if (ret) + return ret; + + priv->phydev = phy_find_first(priv->mdiobus); + if (!priv->phydev) + return dev_err_probe(&spidev->dev, -ENODEV, "No PHY found\n"); + + priv->phydev->is_internal = true; + ret = phy_connect_direct(priv->netdev, priv->phydev, + &adin1140_handle_link_change, + PHY_INTERFACE_MODE_INTERNAL); + if (ret) + return dev_err_probe(&spidev->dev, ret, + "Can't attach PHY to %s\n", + priv->mdiobus->id); + + ret = devm_add_action_or_reset(&spidev->dev, adin1140_phy_remove, + priv->phydev); + if (ret) + return ret; + + phy_attached_info(priv->phydev); + + return 0; +} + +static const struct ethtool_ops adin1140_ethtool_ops = { + .get_drvinfo = adin1140_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ethtool_stats = adin1140_get_ethtool_stats, + .get_sset_count = adin1140_get_sset_count, + .get_strings = adin1140_get_ethtool_strings, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_eth_mac_stats = adin1140_get_eth_mac_stats, +}; + +static const struct net_device_ops adin1140_netdev_ops = { + .ndo_open = adin1140_open, + .ndo_stop = adin1140_close, + .ndo_start_xmit = adin1140_start_xmit, + .ndo_set_mac_address = adin1140_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode_async = adin1140_rx_mode, + .ndo_eth_ioctl = phy_do_ioctl_running, + .ndo_get_stats64 = adin1140_ndo_get_stats64, +}; + +static void adin1140_oa_tc6_remove(void *data) +{ + oa_tc6_exit(data); +} + +static int adin1140_probe(struct spi_device *spi) +{ + struct oa_tc6_quirks tc6_quirks = {}; + struct net_device *netdev; + struct adin1140_priv *priv; + int ret; + + netdev = devm_alloc_etherdev(&spi->dev, sizeof(struct adin1140_priv)); + if (!netdev) + return -ENOMEM; + + priv = netdev_priv(netdev); + priv->netdev = netdev; + spi_set_drvdata(spi, priv); + spin_lock_init(&priv->stat_lock); + + tc6_quirks.quirk_flags = OA_TC6_BROKEN_PHY; + + priv->tc6 = oa_tc6_init(spi, netdev, &tc6_quirks); + if (!priv->tc6) + return -ENODEV; + + ret = devm_add_action_or_reset(&spi->dev, adin1140_oa_tc6_remove, + priv->tc6); + if (ret) + return ret; + + ret = adin1140_phy_init(priv, spi); + if (ret) + return ret; + + if (device_get_ethdev_address(&spi->dev, netdev)) + eth_hw_addr_random(netdev); + + ret = adin1140_configure(priv); + if (ret) + return ret; + + INIT_DELAYED_WORK(&priv->stats_work, adin1140_stats_work); + + netdev->if_port = IF_PORT_10BASET; + netdev->irq = spi->irq; + netdev->netdev_ops = &adin1140_netdev_ops; + netdev->ethtool_ops = &adin1140_ethtool_ops; + netdev->netns_immutable = true; + netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE | + IFF_UNICAST_FLT; + + ret = devm_register_netdev(&spi->dev, netdev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to register netdev"); + + return 0; +} + +static const struct spi_device_id adin1140_spi_id[] = { + { .name = "ad3306" }, + { .name = "adin1140" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adin1140_spi_id); + +static const struct of_device_id adin1140_match_table[] = { + { .compatible = "adi,ad3306" }, + { .compatible = "adi,adin1140" }, + { } +}; +MODULE_DEVICE_TABLE(of, adin1140_match_table); + +static struct spi_driver adin1140_driver = { + .driver = { + .name = "adin1140", + .of_match_table = adin1140_match_table, + }, + .probe = adin1140_probe, + .id_table = adin1140_spi_id, +}; +module_spi_driver(adin1140_driver); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY"); +MODULE_AUTHOR("Ciprian Regus <ciprian.regus@analog.com>"); +MODULE_LICENSE("GPL"); |
