From: "Alvin Šipraga" <alvin.sipraga@analog.com>
To: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
Cc: Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Parthiban Veerasooran <parthiban.veerasooran@microchip.com>,
Heiner Kallweit <hkallweit1@gmail.com>,
Russell King <linux@armlinux.org.uk>,
Marcelo Schmitt <marcelo.schmitt@analog.com>,
netdev@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org,
Ciprian Regus <ciprian.regus@analog.com>
Subject: Re: [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
Date: Wed, 4 Nov 2026 15:26:07 +0100 [thread overview]
Message-ID: <aus-LdkVRPxR6lFu@analog.com> (raw)
In-Reply-To: <20261103160918.409701-5-iustin.stolniceanu@analog.com>
On Tue, Nov 03, 2026 at 06:09:17PM +0200, Iustin Stolniceanu wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
>
> The ADIN1110 and ADIN2111 support two SPI host interface protocols,
> the generic SPI protocol and the OPEN Alliance TC6 protocol. The
> protocol is selected by the state of the SPI_CFG1 pin, which the device
> latches when it comes out of reset. The driver currently supports only
> the generic SPI protocol.
>
> Add support for the OPEN Alliance TC6 protocol on the ADIN1110, using
> the oa_tc6 framework, when the adi,spi-oa-tc6 property is set. The
> framework handles the data transfers and the MDIO bus. Register
> accesses go through read_reg/write_reg callbacks, so the existing MAC
> configuration code is shared by both protocols. The ADIN1110 specific
> MAC registers are in MMS 1.
>
> The framework does not append the FCS to transmitted frames, so set
> CRC_APPEND in CONFIG2 to let the MAC append it. Also set the port state
> to forwarding on open, as the generic SPI path does, otherwise the
> broadcast and multicast filters drop all received frames.
>
> The ADIN2111 is not supported in OPEN Alliance TC6 mode yet, and probe
> fails with -EOPNOTSUPP.
Not sure -EOPNOTSUPP is correct, maybe -EIO or something?
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> Co-developed-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
> ---
> drivers/net/ethernet/adi/Kconfig | 1 +
> drivers/net/ethernet/adi/adin1110.c | 186 +++++++++++++++++++++++++++-
> include/linux/oa_tc6.h | 1 +
> 3 files changed, 186 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig
> index bdb8ff7d15da..c35f68814c21 100644
> --- a/drivers/net/ethernet/adi/Kconfig
> +++ b/drivers/net/ethernet/adi/Kconfig
> @@ -22,6 +22,7 @@ config ADIN1110
> depends on SPI && NET_SWITCHDEV
> select CRC8
> select PHYLIB
> + select OA_TC6
> help
> Say yes here to build support for Analog Devices ADIN1110
> Low Power 10BASE-T1L Ethernet MAC-PHY.
> diff --git a/drivers/net/ethernet/adi/adin1110.c b/drivers/net/ethernet/adi/adin1110.c
> index 53fac3dc8be6..d08b165bf7c4 100644
> --- a/drivers/net/ethernet/adi/adin1110.c
> +++ b/drivers/net/ethernet/adi/adin1110.c
> @@ -19,6 +19,7 @@
> #include <linux/mii.h>
> #include <linux/module.h>
> #include <linux/netdevice.h>
> +#include <linux/oa_tc6.h>
> #include <linux/regulator/consumer.h>
> #include <linux/phy.h>
> #include <linux/property.h>
> @@ -114,6 +115,8 @@
> #define ADIN_MAC_P2_ADDR_SLOT 3
> #define ADIN_MAC_FDB_ADDR_SLOT 4
>
> +#define ADIN1110_MMS0_LIMIT 0x30
> +
> DECLARE_CRC8_TABLE(adin1110_crc_table);
>
> enum adin1110_chips_id {
> @@ -145,6 +148,7 @@ struct adin1110_port_priv {
> u32 nr;
> u32 state;
> struct adin1110_cfg *cfg;
> + struct oa_tc6 *tc6;
> };
>
> struct adin1110_priv {
> @@ -161,6 +165,9 @@ struct adin1110_priv {
> struct adin1110_port_priv *ports[ADIN_MAC_MAX_PORTS];
> char mii_bus_name[MII_BUS_ID_SIZE];
> u8 data[ADIN1110_MAX_BUFF] ____cacheline_aligned;
> +
> + int (*read_reg)(struct adin1110_priv *priv, u16 reg, u32 *val);
> + int (*write_reg)(struct adin1110_priv *priv, u16 reg, u32 val);
> };
>
> struct adin1110_switchdev_event_work {
> @@ -192,7 +199,18 @@ 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)
> +static int adin1110_read_reg_tc6(struct adin1110_priv *priv, u16 reg, u32 *val)
> +{
> + struct adin1110_port_priv *port_priv = priv->ports[0];
> +
> + if (reg >= ADIN1110_MMS0_LIMIT)
> + return oa_tc6_read_register_mms(port_priv->tc6, OA_TC6_MAC_MMS1,
> + reg, val);
> +
> + return oa_tc6_read_register(port_priv->tc6, reg, val);
> +}
> +
> +static int adin1110_read_reg_generic_spi(struct adin1110_priv *priv, u16 reg, u32 *val)
> {
> u32 header_len = ADIN1110_RD_HEADER_LEN;
> u32 read_len = ADIN1110_REG_LEN;
> @@ -240,7 +258,23 @@ static int adin1110_read_reg(struct adin1110_priv *priv, u16 reg, u32 *val)
> return ret;
> }
>
> -static int adin1110_write_reg(struct adin1110_priv *priv, u16 reg, u32 val)
> +static int adin1110_read_reg(struct adin1110_priv *priv, u16 reg, u32 *val)
> +{
> + return priv->read_reg(priv, reg, val);
> +}
> +
> +static int adin1110_write_reg_tc6(struct adin1110_priv *priv, u16 reg, u32 val)
> +{
> + struct adin1110_port_priv *port_priv = priv->ports[0];
> +
> + if (reg >= ADIN1110_MMS0_LIMIT)
> + return oa_tc6_write_register_mms(port_priv->tc6, OA_TC6_MAC_MMS1,
> + reg, val);
> +
> + return oa_tc6_write_register(port_priv->tc6, reg, val);
> +}
> +
> +static int adin1110_write_reg_generic_spi(struct adin1110_priv *priv, u16 reg, u32 val)
> {
> u32 header_len = ADIN1110_WR_HEADER_LEN;
> u32 write_len = ADIN1110_REG_LEN;
> @@ -263,6 +297,11 @@ static int adin1110_write_reg(struct adin1110_priv *priv, u16 reg, u32 val)
> return spi_write(priv->spidev, &priv->data[0], header_len + write_len);
> }
>
> +static int adin1110_write_reg(struct adin1110_priv *priv, u16 reg, u32 val)
> +{
> + return priv->write_reg(priv, reg, val);
> +}
> +
> static int adin1110_set_bits(struct adin1110_priv *priv, u16 reg,
> unsigned long mask, unsigned long val)
> {
> @@ -881,6 +920,22 @@ static void adin1110_set_rx_mode(struct net_device *dev)
> spin_unlock(&priv->state_lock);
> }
>
> +static int adin1110_set_rx_mode_tc6(struct net_device *dev,
> + struct netdev_hw_addr_list *uc,
> + struct netdev_hw_addr_list *mc)
> +{
> + struct adin1110_port_priv *port_priv = netdev_priv(dev);
> + struct adin1110_priv *priv = port_priv->priv;
> + int ret;
> +
> + mutex_lock(&priv->lock);
guard(mutex)(&priv->lock)?
OK, but the rest of the driver doesn't use that, so I can see why you
refrained from going that way.
> + port_priv->flags = dev->flags;
> + ret = adin1110_setup_rx_mode(port_priv);
> + mutex_unlock(&priv->lock);
> +
> + return ret;
> +}
> +
> static int adin1110_net_open(struct net_device *net_dev)
> {
> struct adin1110_port_priv *port_priv = netdev_priv(net_dev);
> @@ -938,6 +993,31 @@ static int adin1110_net_open(struct net_device *net_dev)
> return 0;
> }
>
> +static int adin1110_net_open_tc6(struct net_device *net_dev)
> +{
> + struct adin1110_port_priv *port_priv = netdev_priv(net_dev);
> + struct adin1110_priv *priv = port_priv->priv;
> + int ret;
> +
> + mutex_lock(&priv->lock);
> +
> + /* Needed for the broadcast/multicast filters to forward to the host */
> + port_priv->state = BR_STATE_FORWARDING;
> +
> + ret = adin1110_set_mac_address(net_dev, net_dev->dev_addr);
> + mutex_unlock(&priv->lock);
> + if (ret < 0) {
> + netdev_err(net_dev, "Could not set MAC address: %pM, %d\n",
> + net_dev->dev_addr, ret);
> + return ret;
> + }
For the above, it wasn't quite clear to me why you don't call
adin1110_port_set_forwarding_state()?
> +
> + phy_start(net_dev->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);
> @@ -961,6 +1041,14 @@ static int adin1110_net_stop(struct net_device *net_dev)
> return 0;
> }
>
> +static int adin1110_net_stop_tc6(struct net_device *net_dev)
> +{
> + netif_stop_queue(net_dev);
> + phy_stop(net_dev->phydev);
> +
> + return 0;
> +}
> +
> static void adin1110_tx_work(struct work_struct *work)
> {
> struct adin1110_port_priv *port_priv;
> @@ -1006,6 +1094,18 @@ static netdev_tx_t adin1110_start_xmit(struct sk_buff *skb, struct net_device *d
> return netdev_ret;
> }
>
> +static netdev_tx_t adin1110_start_xmit_tc6(struct sk_buff *skb, struct net_device *dev)
> +{
> + struct adin1110_port_priv *port_priv = netdev_priv(dev);
> +
> + if (skb_put_padto(skb, ETH_ZLEN)) {
> + dev->stats.tx_dropped++;
> + return NETDEV_TX_OK;
> + }
> +
> + return oa_tc6_start_xmit(port_priv->tc6, skb);
> +}
> +
> static void adin1110_ndo_get_stats64(struct net_device *dev,
> struct rtnl_link_stats64 *storage)
> {
> @@ -1056,6 +1156,16 @@ static const struct net_device_ops adin1110_netdev_ops = {
> .ndo_get_phys_port_name = adin1110_ndo_get_phys_port_name,
> };
>
> +static const struct net_device_ops adin1110_tc6_netdev_ops = {
> + .ndo_open = adin1110_net_open_tc6,
> + .ndo_stop = adin1110_net_stop_tc6,
> + .ndo_eth_ioctl = adin1110_ioctl,
> + .ndo_start_xmit = adin1110_start_xmit_tc6,
> + .ndo_set_mac_address = adin1110_ndo_set_mac_address,
> + .ndo_set_rx_mode_async = adin1110_set_rx_mode_tc6,
Would it make sense to use the async mode for the non-tc6 case as well?
Then you can drop the work there and have the same ndo callback for both.
> + .ndo_validate_addr = eth_validate_addr,
> +};
> +
> static void adin1110_get_drvinfo(struct net_device *dev,
> struct ethtool_drvinfo *di)
> {
> @@ -1123,6 +1233,69 @@ static int adin1110_check_spi(struct adin1110_priv *priv)
> return 0;
> }
>
> +static void adin1110_oa_tc6_remove(void *data)
> +{
> + oa_tc6_exit(data);
> +}
> +
> +static int adin1110_oa_tc6_probe(struct adin1110_priv *priv)
> +{
> + struct adin1110_port_priv *port_priv;
> + struct oa_tc6_quirks quirks = {0};
> + struct net_device *netdev;
> + struct device *dev;
> + int ret;
> +
> + dev = &priv->spidev->dev;
> +
> + if (priv->cfg->id == ADIN2111_MAC)
> + return dev_err_probe(dev, -EOPNOTSUPP,
> + "OA TC6 isn't supported for ADIN2111\n");
> +
> + 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 = 0;
> + priv->ports[0] = port_priv;
> + SET_NETDEV_DEV(netdev, dev);
> +
> + quirks.reset_settle_us = 50000;
> +
> + port_priv->tc6 = oa_tc6_init(priv->spidev, netdev, &quirks);
> + if (IS_ERR(port_priv->tc6))
> + return PTR_ERR(port_priv->tc6);
> +
> + priv->read_reg = adin1110_read_reg_tc6;
> + priv->write_reg = adin1110_write_reg_tc6;
> +
> + ret = devm_add_action_or_reset(dev, adin1110_oa_tc6_remove,
> + port_priv->tc6);
Spurious newline above.
> + if (ret)
> + return ret;
> +
> + /* The OA TC6 framework doesn't append the FCS, let the MAC do it. */
> + ret = adin1110_set_bits(priv, ADIN1110_CONFIG2, ADIN1110_CRC_APPEND,
> + ADIN1110_CRC_APPEND);
> + if (ret < 0)
> + return ret;
> +
> + if (device_get_ethdev_address(dev, netdev))
> + eth_hw_addr_random(netdev);
> +
> + netdev->if_port = IF_PORT_10BASET;
> + netdev->netdev_ops = &adin1110_tc6_netdev_ops;
> + netdev->ethtool_ops = &adin1110_ethtool_ops;
> + netdev->priv_flags |= IFF_UNICAST_FLT;
> + netdev->netns_immutable = true;
> +
> + return devm_register_netdev(dev, netdev);
> +}
> +
> static int adin1110_hw_forwarding(struct adin1110_priv *priv, bool enable)
> {
> int ret;
> @@ -1649,6 +1822,7 @@ 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;
> + bool oa_tc6_protocol;
> int ret;
>
> priv = devm_kzalloc(dev, sizeof(struct adin1110_priv), GFP_KERNEL);
> @@ -1663,11 +1837,19 @@ static int adin1110_probe(struct spi_device *spi)
> mutex_init(&priv->lock);
> spin_lock_init(&priv->state_lock);
>
> + oa_tc6_protocol = device_property_read_bool(dev, "adi,spi-oa-tc6");
> +
Spurious newline.
> + if (oa_tc6_protocol)
> + return adin1110_oa_tc6_probe(priv);
> +
> /* 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);
>
> + priv->read_reg = adin1110_read_reg_generic_spi;
> + priv->write_reg = adin1110_write_reg_generic_spi;
> +
> ret = adin1110_check_spi(priv);
> if (ret < 0) {
> dev_err(dev, "Probe SPI Read check failed: %d\n", ret);
> diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
> index e0627b99e569..2fd051e1c604 100644
> --- a/include/linux/oa_tc6.h
> +++ b/include/linux/oa_tc6.h
> @@ -79,6 +79,7 @@
> #define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
> #define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
>
> +struct mii_bus;
A mistake here? Doesn't seem related.
> struct oa_tc6;
>
> enum oa_tc6_quirk_flag {
> --
> 2.43.0
>
next prev parent reply other threads:[~2026-11-04 14:26 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 16:09 [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
2026-11-03 16:09 ` [PATCH net-next 1/5] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
2026-11-04 14:10 ` Alvin Šipraga
2026-11-03 16:09 ` [PATCH net-next 2/5] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
2026-11-04 14:08 ` Alvin Šipraga
2026-11-03 16:09 ` [PATCH net-next 3/5] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
2026-11-04 14:12 ` Alvin Šipraga
2026-11-03 16:09 ` [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
2026-11-04 14:26 ` Alvin Šipraga [this message]
2026-11-04 21:06 ` Andrew Lunn
2026-11-03 16:09 ` [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
2026-11-04 14:29 ` Alvin Šipraga
2026-11-04 21:16 ` Andrew Lunn
2026-11-03 20:49 ` [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Andrew Lunn
2026-11-04 8:06 ` Stolniceanu, Iustin
2026-11-04 14:59 ` Andrew Lunn
2026-11-04 15:14 ` Stolniceanu, Iustin
2026-11-03 20:54 ` Andrew Lunn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aus-LdkVRPxR6lFu@analog.com \
--to=alvin.sipraga@analog.com \
--cc=andrew+netdev@lunn.ch \
--cc=ciprian.regus@analog.com \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@kernel.org \
--cc=hkallweit1@gmail.com \
--cc=iustin.stolniceanu@analog.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=marcelo.schmitt@analog.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=parthiban.veerasooran@microchip.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®