Re: [net-next v7 2/3] net: ethernet: adi: Add ADIN1110 support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



hello,

On Tue, 2022-09-06 at 11:22 +0300, andrei.tachici@xxxxxxxxxxxxxxx wrote:
> From: Alexandru Tachici <alexandru.tachici@xxxxxxxxxx>
> 
> The ADIN1110 is a low power single port 10BASE-T1L MAC-PHY
> designed for industrial Ethernet applications. It integrates
> an Ethernet PHY core with a MAC and all the associated analog
> circuitry, input and output clock buffering.
> 
> ADIN1110 MAC-PHY encapsulates the ADIN1100 PHY. The PHY registers
> can be accessed through the MDIO MAC registers.
> We are registering an MDIO bus with custom read/write in order
> to let the PHY to be discovered by the PAL. This will let
> the ADIN1100 Linux driver to probe and take control of
> the PHY.
> 
> The ADIN2111 is a low power, low complexity, two-Ethernet ports
> switch with integrated 10BASE-T1L PHYs and one serial peripheral
> interface (SPI) port.
> 
> The device is designed for industrial Ethernet applications using
> low power constrained nodes and is compliant with the IEEE 802.3cg-
> 2019
> Ethernet standard for long reach 10 Mbps single pair Ethernet (SPE).
> The switch supports various routing configurations between
> the two Ethernet ports and the SPI host port providing a flexible
> solution for line, daisy-chain, or ring network topologies.
> 
> The ADIN2111 supports cable reach of up to 1700 meters with ultra
> low power consumption of 77 mW. The two PHY cores support the
> 1.0 V p-p operating mode and the 2.4 V p-p operating mode defined
> in the IEEE 802.3cg standard.
> 
> The device integrates the switch, two Ethernet physical layer (PHY)
> cores with a media access control (MAC) interface and all the
> associated analog circuitry, and input and output clock buffering.
> 
> The device also includes internal buffer queues, the SPI and
> subsystem registers, as well as the control logic to manage the reset
> and clock control and hardware pin configuration.
> 
> Access to the PHYs is exposed via an internal MDIO bus. Writes/reads
> can be performed by reading/writing to the ADIN2111 MDIO registers
> via SPI.
> 
> On probe, for each port, a struct net_device is allocated and
> registered. When both ports are added to the same bridge, the driver
> will enable offloading of frame forwarding at the hardware level.
> 
> Driver offers STP support. Normal operation on forwarding state.
> Allows only frames with the 802.1d DA to be passed to the host
> when in any of the other states.
> 
> When both ports of ADIN2111 belong to the same SW bridge a maximum
> of 12 FDB entries will offloaded by the hardware and are marked as
> such.
> 
> Co-developed-by: Lennart Franzen <lennart@xxxxxxxxxxxx>
> Signed-off-by: Lennart Franzen <lennart@xxxxxxxxxxxx>
> Signed-off-by: Alexandru Tachici <alexandru.tachici@xxxxxxxxxx>
> ---
>  drivers/net/ethernet/Kconfig        |    1 +
>  drivers/net/ethernet/Makefile       |    1 +
>  drivers/net/ethernet/adi/Kconfig    |   28 +
>  drivers/net/ethernet/adi/Makefile   |    6 +
>  drivers/net/ethernet/adi/adin1110.c | 1627
> +++++++++++++++++++++++++++
>  5 files changed, 1663 insertions(+)
>  create mode 100644 drivers/net/ethernet/adi/Kconfig
>  create mode 100644 drivers/net/ethernet/adi/Makefile
>  create mode 100644 drivers/net/ethernet/adi/adin1110.c
> 
> diff --git a/drivers/net/ethernet/Kconfig
> b/drivers/net/ethernet/Kconfig
> index 9a55c1d5a0a1..1917da784191 100644
> --- a/drivers/net/ethernet/Kconfig
> +++ b/drivers/net/ethernet/Kconfig
> @@ -121,6 +121,7 @@ config LANTIQ_XRX200
>  	  Support for the PMAC of the Gigabit switch (GSWIP) inside
> the
>  	  Lantiq / Intel VRX200 VDSL SoC
>  
> +source "drivers/net/ethernet/adi/Kconfig"
>  source "drivers/net/ethernet/litex/Kconfig"
>  source "drivers/net/ethernet/marvell/Kconfig"
>  source "drivers/net/ethernet/mediatek/Kconfig"
> diff --git a/drivers/net/ethernet/Makefile
> b/drivers/net/ethernet/Makefile
> index c06e75ed4231..0d872d4efcd1 100644
> --- a/drivers/net/ethernet/Makefile
> +++ b/drivers/net/ethernet/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_NET_VENDOR_8390) += 8390/
>  obj-$(CONFIG_NET_VENDOR_ACTIONS) += actions/
>  obj-$(CONFIG_NET_VENDOR_ADAPTEC) += adaptec/
>  obj-$(CONFIG_GRETH) += aeroflex/
> +obj-$(CONFIG_NET_VENDOR_ADI) += adi/
>  obj-$(CONFIG_NET_VENDOR_AGERE) += agere/
>  obj-$(CONFIG_NET_VENDOR_ALACRITECH) += alacritech/
>  obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/
> diff --git a/drivers/net/ethernet/adi/Kconfig
> b/drivers/net/ethernet/adi/Kconfig
> new file mode 100644
> index 000000000000..da3bdd302502
> --- /dev/null
> +++ b/drivers/net/ethernet/adi/Kconfig
> @@ -0,0 +1,28 @@
> +# 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
> +	help
> +	  Say yes here to build support for Analog Devices ADIN1110
> +	  Low Power 10BASE-T1L Ethernet MAC-PHY.
> +
> +endif # NET_VENDOR_ADI
> diff --git a/drivers/net/ethernet/adi/Makefile
> b/drivers/net/ethernet/adi/Makefile
> new file mode 100644
> index 000000000000..d0383d94303c
> --- /dev/null
> +++ b/drivers/net/ethernet/adi/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
> +#
> +# Makefile for the Analog Devices network device drivers.
> +#
> +
> +obj-$(CONFIG_ADIN1110) += adin1110.o
> diff --git a/drivers/net/ethernet/adi/adin1110.c
> b/drivers/net/ethernet/adi/adin1110.c
> new file mode 100644
> index 000000000000..391718d056cc
> --- /dev/null
> +++ b/drivers/net/ethernet/adi/adin1110.c
> @@ -0,0 +1,1627 @@
> +// 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/if_bridge.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/gpio.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 <asm/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;
> +	char			name[MDIO_NAME_SIZE];
> +	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[2] = {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++;
> +	}
> +
> +	t[0].tx_buf = &priv->data[0];
> +	t[0].len = header_len;
> +
> +	if (priv->append_crc)
> +		read_len++;
> +
> +	memset(&priv->data[header_len], 0, read_len);
> +	t[1].rx_buf = &priv->data[header_len];
> +	t[1].len = read_len;
> +
> +	ret = spi_sync_transfer(priv->spidev, t, 2);
> +	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[2] = {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 ret;
> +
> +	round_len = adin1110_round_len(frame_size);
> +	if (round_len < 0)
> +		return ret;
> +
> +	frame_size_no_fcs = frame_size - ADIN1110_FRAME_HEADER_LEN -
> ADIN1110_FEC_LEN;
> +
> +	rxb = netdev_alloc_skb(port_priv->netdev, round_len);
> +	if (!rxb)
> +		return -ENOMEM;
> +
> +	memset(priv->data, 0, round_len + 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++;
> +	}
> +
> +	skb_put(rxb, frame_size_no_fcs + ADIN1110_FRAME_HEADER_LEN);
> +
> +	t[0].tx_buf = &priv->data[0];
> +	t[0].len = header_len;
> +
> +	t[1].rx_buf = &rxb->data[0];
> +	t[1].len = round_len;
> +
> +	ret = spi_sync_transfer(priv->spidev, t, 2);
> +	if (ret) {
> +		kfree_skb(rxb);
> +		return ret;
> +	}
> +
> +	skb_pull(rxb, 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 = 1;
> +
> +	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 |
> 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(adin1110_read_mdio_acc, priv, val,
> (val & ADIN1110_MDIO_TRDONE),
> +				 10000, 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(adin1110_read_mdio_acc, priv, val,
> (val & ADIN1110_MDIO_TRDONE),
> +				  10000, 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, priv->spidev->chip_select);
> +
> +	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));
> +	mii_bus->probe_capabilities = MDIOBUS_C22;
> +	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];
> +
> +	memset(mask, 0xFF, ETH_ALEN);
> +
> +	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);
> +	memset(mask, 0xFF, ETH_ALEN);
> +
> +	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 = container_of(work,
> struct adin1110_port_priv, rx_mode_work);
> +	struct adin1110_priv *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 = container_of(work,
> struct adin1110_port_priv, tx_work);
> +	struct adin1110_priv *priv = port_priv->priv;
> +	struct sk_buff *txb;
> +	int ret;
> +
> +	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, MII_BUS_ID_SIZE);
> +	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)
> +{
> +	int ret;
> +	u32 val;
> +
> +	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 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;
> +
> +	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 bool adin1110_port_dev_check(const struct net_device *dev)
> +{
> +	return dev->netdev_ops == &adin1110_netdev_ops;
> +}
> +
> +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)
> +		return ret;

Should be:
		goto out;

or it will leave the mutex locked.

> +
> +	/* Allow only BPDUs to be passed to the CPU */
> +	memset(mask, 0xFF, ETH_ALEN);
> +	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(port_priv, false, true);
> +	memset(mask, 0xFF, ETH_ALEN);
> +
> +	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(sizeof(*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 *data)
> +{
> +	unregister_switchdev_blocking_notifier(&adin1110_switchdev_b
> locking_notifier);
> +	unregister_switchdev_notifier(&adin1110_switchdev_notifier);
> +	unregister_netdevice_notifier(&adin1110_netdevice_nb);
> +}
> +
> +static int adin1110_setup_notifiers(struct adin1110_priv *priv)
> +{
> +	struct device *dev = &priv->spidev->dev;
> +	int ret;
> +
> +	ret = register_netdevice_notifier(&adin1110_netdevice_nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret =
> register_switchdev_notifier(&adin1110_switchdev_notifier);
> +	if (ret < 0) {

the preferred style would be
		goto err_netdev;

> +		unregister_netdevice_notifier(&adin1110_netdevice_nb
> );
> +		return ret;
> +	}
> +
> +	ret =
> register_switchdev_blocking_notifier(&adin1110_switchdev_blocking_not
> ifier);
> +	if (ret < 0) {

here:
		goto err_sdev;

> +		unregister_netdevice_notifier(&adin1110_netdevice_nb
> );
> +		unregister_switchdev_notifier(&adin1110_switchdev_no
> tifier);
> +		return ret;
> +	}
> +
> +	return devm_add_action_or_reset(dev,
> adin1110_unregister_notifiers, NULL);

err_sdev:
	unregister_switchdev_notifier(&adin1110_switchdev_notifier);

err_netdev:
	unregister_netdevice_notifier(&adin1110_netdevice_nb);
	return ret;

[...]

Many lines exceed the 80 chars boundaries, even if it's not a strict
limit, you could try to fit it a little more, e.g. reducing the
indentation level where possible and relevant and/or truncating the
longest line.

Thanks!

Paolo





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux