root/drivers/net/ethernet/adi/adin1110.c
// 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");