* [PATCH net-next 1/5] net: ethernet: oa_tc6: Allow custom reset settle time values
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 ` 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
` (5 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Iustin Stolniceanu @ 2026-11-03 16:09 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt
Cc: netdev, devicetree, linux-kernel, Ciprian Regus,
Iustin Stolniceanu
From: Ciprian Regus <ciprian.regus@analog.com>
oa_tc6_init() waits a fixed 1 ms after deasserting the reset GPIO
before accessing the MAC-PHY. The reset timing parameters of a MAC-PHY
device are outside of the OA TC6 scope. For example, the ADIN1110 needs
up to 50 ms after RESET is deasserted before its registers can be
accessed.
Add a reset_settle_us field to struct oa_tc6_quirks so drivers can
set this time. If it is not set, the GPIO reset keeps the existing
1 ms wait.
When set, also apply the wait after the software reset, before polling
STATUS0 for RESETC. This is needed for devices that tristate their SDO
pin during a reset. Drivers that don't set reset_settle_us keep the
current behaviour.
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/oa_tc6.c | 18 ++++++++++++++----
include/linux/oa_tc6.h | 2 ++
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 364027c39fa4..68437b226528 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -89,6 +89,7 @@ struct oa_tc6 {
bool int_flag;
bool disable_traffic;
bool prot_ctrl;
+ u32 reset_settle_us;
enum oa_tc6_quirk_flag quirk_flags;
struct gpio_desc *reset_gpio;
};
@@ -652,6 +653,9 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6)
if (ret)
return ret;
+ if (tc6->reset_settle_us)
+ fsleep(tc6->reset_settle_us);
+
/* Poll for soft reset complete for every 1ms until 1s timeout */
ret = readx_poll_timeout(oa_tc6_read_status0, tc6, regval,
regval & OA_TC6_STATUS0_RESETC,
@@ -1474,8 +1478,10 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
- if (quirks)
+ if (quirks) {
tc6->quirk_flags = quirks->quirk_flags;
+ tc6->reset_settle_us = quirks->reset_settle_us;
+ }
/* Set the SPI controller to pump at realtime priority */
tc6->spi->rt = true;
@@ -1515,12 +1521,16 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
"failed to get reset gpio\n"));
if (tc6->reset_gpio) {
- /* Keep the reset asserted for 10 us and then allow 1 ms of
- * settle time for the crystal oscillator startup.
+ /* Keep the reset asserted for 10 us and then allow the reset
+ * settle time (1 ms by default) for the crystal oscillator
+ * startup.
*/
fsleep(10);
gpiod_set_value_cansleep(tc6->reset_gpio, 0);
- fsleep(1000);
+ if (tc6->reset_settle_us)
+ fsleep(tc6->reset_settle_us);
+ else
+ fsleep(1000);
}
/* Check the PROTE bit status so that we can reset the device */
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 27f652d4920b..139dd4c33950 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -72,6 +72,8 @@ enum oa_tc6_quirk_flag {
struct oa_tc6_quirks {
enum oa_tc6_quirk_flag quirk_flags;
+ /* Wait after the GPIO reset (default 1 ms) and the software reset */
+ u32 reset_settle_us;
};
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 1/5] net: ethernet: oa_tc6: Allow custom reset settle time values
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
0 siblings, 0 replies; 19+ messages in thread
From: Alvin Šipraga @ 2026-11-04 14:10 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
On Tue, Nov 03, 2026 at 06:09:14PM +0200, Iustin Stolniceanu wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
>
> oa_tc6_init() waits a fixed 1 ms after deasserting the reset GPIO
> before accessing the MAC-PHY. The reset timing parameters of a MAC-PHY
> device are outside of the OA TC6 scope. For example, the ADIN1110 needs
> up to 50 ms after RESET is deasserted before its registers can be
> accessed.
>
> Add a reset_settle_us field to struct oa_tc6_quirks so drivers can
> set this time. If it is not set, the GPIO reset keeps the existing
> 1 ms wait.
>
> When set, also apply the wait after the software reset, before polling
> STATUS0 for RESETC. This is needed for devices that tristate their SDO
> pin during a reset. Drivers that don't set reset_settle_us keep the
> current behaviour.
Would it maybe make sense to split this into two quirk values? Something
like hwreset_settle_us and softreset_settle_us? The ADIN1110 datasheet
seems to have a slightly lower value for software reset.
Either way,
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
>
> 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/oa_tc6.c | 18 ++++++++++++++----
> include/linux/oa_tc6.h | 2 ++
> 2 files changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
> index 364027c39fa4..68437b226528 100644
> --- a/drivers/net/ethernet/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6.c
> @@ -89,6 +89,7 @@ struct oa_tc6 {
> bool int_flag;
> bool disable_traffic;
> bool prot_ctrl;
> + u32 reset_settle_us;
> enum oa_tc6_quirk_flag quirk_flags;
> struct gpio_desc *reset_gpio;
> };
> @@ -652,6 +653,9 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6)
> if (ret)
> return ret;
>
> + if (tc6->reset_settle_us)
> + fsleep(tc6->reset_settle_us);
If you have a separate softreset version, you can just set the value
if (quirks) and flseep() here unconditionally.
> +
> /* Poll for soft reset complete for every 1ms until 1s timeout */
> ret = readx_poll_timeout(oa_tc6_read_status0, tc6, regval,
> regval & OA_TC6_STATUS0_RESETC,
> @@ -1474,8 +1478,10 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
> mutex_init(&tc6->spi_ctrl_lock);
> spin_lock_init(&tc6->tx_skb_lock);
>
> - if (quirks)
> + if (quirks) {
> tc6->quirk_flags = quirks->quirk_flags;
> + tc6->reset_settle_us = quirks->reset_settle_us;
> + }
>
> /* Set the SPI controller to pump at realtime priority */
> tc6->spi->rt = true;
> @@ -1515,12 +1521,16 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
> "failed to get reset gpio\n"));
>
> if (tc6->reset_gpio) {
> - /* Keep the reset asserted for 10 us and then allow 1 ms of
> - * settle time for the crystal oscillator startup.
> + /* Keep the reset asserted for 10 us and then allow the reset
> + * settle time (1 ms by default) for the crystal oscillator
> + * startup.
> */
> fsleep(10);
> gpiod_set_value_cansleep(tc6->reset_gpio, 0);
> - fsleep(1000);
> + if (tc6->reset_settle_us)
> + fsleep(tc6->reset_settle_us);
> + else
> + fsleep(1000);
> }
>
> /* Check the PROTE bit status so that we can reset the device */
> diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
> index 27f652d4920b..139dd4c33950 100644
> --- a/include/linux/oa_tc6.h
> +++ b/include/linux/oa_tc6.h
> @@ -72,6 +72,8 @@ enum oa_tc6_quirk_flag {
>
> struct oa_tc6_quirks {
> enum oa_tc6_quirk_flag quirk_flags;
> + /* Wait after the GPIO reset (default 1 ms) and the software reset */
> + u32 reset_settle_us;
> };
>
> struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 2/5] net: ethernet: oa_tc6: Add support for MDIO indirect access
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-03 16:09 ` 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
` (4 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Iustin Stolniceanu @ 2026-11-03 16:09 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt
Cc: netdev, devicetree, linux-kernel, Ciprian Regus,
Iustin Stolniceanu
From: Ciprian Regus <ciprian.regus@analog.com>
The OPEN Alliance TC6 specification defines two ways for the SPI host
to access the registers of the PHY integrated in a MAC-PHY: direct
access, where the PHY registers are mapped into the MAC-PHY register
space, and indirect access, through the MDIOACC registers. A MAC-PHY
reports support for direct access through the DPRAC bit in the STDCAP
register.
The framework currently supports only direct access and fails to
register the MDIO bus when DPRAC is not set.
Read STDCAP when registering the MDIO bus and, if direct access is not
supported, implement the Clause 22 and Clause 45 MDIO operations as
indirect transactions through MDIOACC: for Clause 45, write the
address command to MDIOACC0 and the read or write command to
MDIOACC1; for Clause 22, write the command to MDIOACC1. Then poll
for TRDONE.
If TAERR (turnaround error) is set, no PHY answered at that address
and the DATA field must be ignored. Return 0xffff in that case, as
for an empty address on an MDIO bus, so that the PHY bus scan skips
the address instead of failing. Return -EIO for a write.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
drivers/net/ethernet/oa_tc6.c | 171 +++++++++++++++++++++++++++++++---
include/linux/oa_tc6.h | 15 +++
2 files changed, 171 insertions(+), 15 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 68437b226528..1c67ea0ff22b 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -89,6 +89,7 @@ struct oa_tc6 {
bool int_flag;
bool disable_traffic;
bool prot_ctrl;
+ bool mdio_indirect_mode;
u32 reset_settle_us;
enum oa_tc6_quirk_flag quirk_flags;
struct gpio_desc *reset_gpio;
@@ -435,24 +436,149 @@ int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
}
EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);
-static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6)
+static void oa_tc6_handle_link_change(struct net_device *netdev)
{
- u32 regval;
+ phy_print_status(netdev->phydev);
+}
+
+static int oa_tc6_mdioacc_wait_done(struct oa_tc6 *tc6, u32 *regval)
+{
+ int ret, err;
+
+ ret = read_poll_timeout(oa_tc6_read_register, err,
+ err || (*regval & OA_TC6_MDIOACC_TRDONE),
+ 100, 30000, false, tc6, OA_TC6_REG_MDIOACC(1),
+ regval);
+ if (ret)
+ return ret;
+
+ return err;
+}
+
+/* On a turnaround error no PHY answered and the DATA field must be ignored.
+ * Return all ones, like an MDIO bus with no PHY at that address, so that
+ * bus scanning skips the address instead of failing.
+ */
+static int oa_tc6_mdioacc_read_data(u32 reg_val)
+{
+ if (reg_val & OA_TC6_MDIOACC_TAERR)
+ return 0xffff;
+
+ return FIELD_GET(OA_TC6_MDIOACC_DATA, reg_val);
+}
+
+static int oa_tc6_mdioacc_write_done(struct oa_tc6 *tc6)
+{
+ u32 reg_val;
int ret;
- ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val);
+ ret = oa_tc6_mdioacc_wait_done(tc6, ®_val);
if (ret)
return ret;
- if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS))
- return -ENODEV;
+ return reg_val & OA_TC6_MDIOACC_TAERR ? -EIO : 0;
+}
- return 0;
+static int oa_tc6_indirect_mdiobus_read_c45(struct oa_tc6 *tc6, int addr,
+ int devnum, int regnum)
+{
+ u32 reg_val;
+ int ret;
+
+ reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_ADDR);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, regnum);
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(0), reg_val);
+ if (ret)
+ return ret;
+
+ reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_RD);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum);
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_mdioacc_wait_done(tc6, ®_val);
+ if (ret)
+ return ret;
+
+ return oa_tc6_mdioacc_read_data(reg_val);
}
-static void oa_tc6_handle_link_change(struct net_device *netdev)
+static int oa_tc6_indirect_mdiobus_write_c45(struct oa_tc6 *tc6, int addr,
+ int devnum, int regnum, u16 val)
{
- phy_print_status(netdev->phydev);
+ u32 reg_val;
+ int ret;
+
+ reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_ADDR);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, regnum);
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(0), reg_val);
+ if (ret)
+ return ret;
+
+ reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_WR);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, val);
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val);
+ if (ret)
+ return ret;
+
+ return oa_tc6_mdioacc_write_done(tc6);
+}
+
+static int oa_tc6_indirect_mdiobus_read(struct oa_tc6 *tc6, int addr,
+ int regnum)
+{
+ u32 reg_val;
+ int ret;
+
+ reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_RD);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C22);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, regnum);
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_mdioacc_wait_done(tc6, ®_val);
+ if (ret)
+ return ret;
+
+ return oa_tc6_mdioacc_read_data(reg_val);
+}
+
+static int oa_tc6_indirect_mdiobus_write(struct oa_tc6 *tc6, int addr,
+ int regnum, u16 val)
+{
+ u32 reg_val;
+ int ret;
+
+ reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_WR);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C22);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, regnum);
+ reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, val);
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val);
+ if (ret)
+ return ret;
+
+ return oa_tc6_mdioacc_write_done(tc6);
}
static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
@@ -461,6 +587,9 @@ static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
u32 regval;
int ret;
+ if (tc6->mdio_indirect_mode)
+ return oa_tc6_indirect_mdiobus_read(tc6, addr, regnum);
+
ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
(regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
®val);
@@ -475,6 +604,9 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
{
struct oa_tc6 *tc6 = bus->priv;
+ if (tc6->mdio_indirect_mode)
+ return oa_tc6_indirect_mdiobus_write(tc6, addr, regnum, val);
+
return oa_tc6_write_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
(regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
val);
@@ -506,6 +638,10 @@ int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
int mms;
int ret;
+ if (tc6->mdio_indirect_mode)
+ return oa_tc6_indirect_mdiobus_read_c45(tc6, addr, devnum,
+ regnum);
+
mms = oa_tc6_get_phy_c45_mms(devnum);
if (mms < 0)
return mms;
@@ -524,6 +660,10 @@ int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
struct oa_tc6 *tc6 = bus->priv;
int mms;
+ if (tc6->mdio_indirect_mode)
+ return oa_tc6_indirect_mdiobus_write_c45(tc6, addr, devnum,
+ regnum, val);
+
mms = oa_tc6_get_phy_c45_mms(devnum);
if (mms < 0)
return mms;
@@ -534,8 +674,16 @@ EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45);
static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
{
+ u32 regval;
int ret;
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val);
+ if (ret)
+ return ret;
+
+ tc6->mdio_indirect_mode = !(OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS &
+ regval);
+
tc6->mdiobus = mdiobus_alloc();
if (!tc6->mdiobus) {
netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
@@ -586,13 +734,6 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
if (tc6->quirk_flags & OA_TC6_BROKEN_PHY)
return 0;
- ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
- if (ret) {
- netdev_err(tc6->netdev,
- "Direct PHY register access is not supported by the MAC-PHY\n");
- return ret;
- }
-
ret = oa_tc6_mdiobus_register(tc6);
if (ret)
return ret;
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 139dd4c33950..e0627b99e569 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -50,6 +50,21 @@
#define OA_TC6_INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \
GENMASK(12, 7))
+#define OA_TC6_REG_MDIOACC(x) (0x20 + (x))
+#define OA_TC6_MDIOACC_TRDONE BIT(31)
+#define OA_TC6_MDIOACC_TAERR BIT(30)
+#define OA_TC6_MDIOACC_ST GENMASK(29, 28)
+#define OA_TC6_MDIOACC_OP GENMASK(27, 26)
+#define OA_TC6_MDIOACC_PRTAD GENMASK(25, 21)
+#define OA_TC6_MDIOACC_DEVAD GENMASK(20, 16)
+#define OA_TC6_MDIOACC_DATA GENMASK(15, 0)
+
+#define OA_TC6_MDIOACC_ST_C45 0x0
+#define OA_TC6_MDIOACC_ST_C22 0x1
+#define OA_TC6_MDIOACC_OP_ADDR 0x0
+#define OA_TC6_MDIOACC_OP_WR 0x1
+#define OA_TC6_MDIOACC_OP_RD 0x3
+
/* PHY Clause 22 registers base address and mask */
#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 2/5] net: ethernet: oa_tc6: Add support for MDIO indirect access
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
0 siblings, 0 replies; 19+ messages in thread
From: Alvin Šipraga @ 2026-11-04 14:08 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
On Tue, Nov 03, 2026 at 06:09:15PM +0200, Iustin Stolniceanu wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
>
> The OPEN Alliance TC6 specification defines two ways for the SPI host
> to access the registers of the PHY integrated in a MAC-PHY: direct
> access, where the PHY registers are mapped into the MAC-PHY register
> space, and indirect access, through the MDIOACC registers. A MAC-PHY
> reports support for direct access through the DPRAC bit in the STDCAP
> register.
>
> The framework currently supports only direct access and fails to
> register the MDIO bus when DPRAC is not set.
>
> Read STDCAP when registering the MDIO bus and, if direct access is not
> supported, implement the Clause 22 and Clause 45 MDIO operations as
> indirect transactions through MDIOACC: for Clause 45, write the
> address command to MDIOACC0 and the read or write command to
> MDIOACC1; for Clause 22, write the command to MDIOACC1. Then poll
> for TRDONE.
>
> If TAERR (turnaround error) is set, no PHY answered at that address
> and the DATA field must be ignored. Return 0xffff in that case, as
> for an empty address on an MDIO bus, so that the PHY bus scan skips
> the address instead of failing. Return -EIO for a write.
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
Just a small comment below.
> ---
> drivers/net/ethernet/oa_tc6.c | 171 +++++++++++++++++++++++++++++++---
> include/linux/oa_tc6.h | 15 +++
> 2 files changed, 171 insertions(+), 15 deletions(-)
>
[...]
> @@ -534,8 +674,16 @@ EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45);
>
> static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> {
> + u32 regval;
> int ret;
>
> + ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val);
> + if (ret)
> + return ret;
> +
> + tc6->mdio_indirect_mode = !(OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS &
> + regval);
Maybe just if (xxx) tc6->mdio_indirect_mode = true?
Kind regards,
Alvin
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 3/5] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6
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-03 16:09 ` [PATCH net-next 2/5] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
@ 2026-11-03 16:09 ` 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
` (3 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Iustin Stolniceanu @ 2026-11-03 16:09 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt
Cc: netdev, devicetree, linux-kernel, Ciprian Regus,
Iustin Stolniceanu
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, together with SPI_CFG0, which
selects CRC (generic SPI) or protection (OPEN Alliance) mode.
The protocol is set by the board hardware, so the driver must know it
in advance. Add a boolean property to describe a device configured for
the OPEN Alliance TC6 protocol. Without it, the generic SPI protocol is
assumed, as before.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
Documentation/devicetree/bindings/net/adi,adin1110.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/adi,adin1110.yaml b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
index 0a73e01d7f97..e19b6c94708d 100644
--- a/Documentation/devicetree/bindings/net/adi,adin1110.yaml
+++ b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
@@ -43,6 +43,12 @@ properties:
Enable CRC8 checks on SPI read/writes.
type: boolean
+ adi,spi-oa-tc6:
+ description: |
+ The device is configured, through the SPI_CFG1 pin, for the
+ Open Alliance TC6 protocol instead of the generic SPI one.
+ type: boolean
+
interrupts:
maxItems: 1
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 3/5] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6
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
0 siblings, 0 replies; 19+ messages in thread
From: Alvin Šipraga @ 2026-11-04 14:12 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
On Tue, Nov 03, 2026 at 06:09:16PM +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, together with SPI_CFG0, which
> selects CRC (generic SPI) or protection (OPEN Alliance) mode.
>
> The protocol is set by the board hardware, so the driver must know it
> in advance. Add a boolean property to describe a device configured for
> the OPEN Alliance TC6 protocol. Without it, the generic SPI protocol is
> assumed, as before.
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
> ---
> Documentation/devicetree/bindings/net/adi,adin1110.yaml | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/net/adi,adin1110.yaml b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
> index 0a73e01d7f97..e19b6c94708d 100644
> --- a/Documentation/devicetree/bindings/net/adi,adin1110.yaml
> +++ b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
> @@ -43,6 +43,12 @@ properties:
> Enable CRC8 checks on SPI read/writes.
> type: boolean
>
> + adi,spi-oa-tc6:
> + description: |
> + The device is configured, through the SPI_CFG1 pin, for the
> + Open Alliance TC6 protocol instead of the generic SPI one.
> + type: boolean
> +
Should this disallow the adi,spi-crc property if set?
I also see that there's a similar protection concept in OA TC6, but that
seems not to be supported here? Is that intrinsically the same as adi,spi-crc?
> interrupts:
> maxItems: 1
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
2026-11-03 16:09 [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (2 preceding siblings ...)
2026-11-03 16:09 ` [PATCH net-next 3/5] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
@ 2026-11-03 16:09 ` Iustin Stolniceanu
2026-11-04 14:26 ` Alvin Šipraga
2026-11-04 16:10 ` sashiko-bot
2026-11-03 16:09 ` [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
` (2 subsequent siblings)
6 siblings, 2 replies; 19+ messages in thread
From: Iustin Stolniceanu @ 2026-11-03 16:09 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt
Cc: netdev, devicetree, linux-kernel, Ciprian Regus,
Iustin Stolniceanu
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.
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);
+ 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;
+ }
+
+ 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,
+ .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);
+ 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");
+
+ 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;
struct oa_tc6;
enum oa_tc6_quirk_flag {
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
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
2026-11-04 21:06 ` Andrew Lunn
2026-11-04 16:10 ` sashiko-bot
1 sibling, 1 reply; 19+ messages in thread
From: Alvin Šipraga @ 2026-11-04 14:26 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
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
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
2026-11-04 14:26 ` Alvin Šipraga
@ 2026-11-04 21:06 ` Andrew Lunn
0 siblings, 0 replies; 19+ messages in thread
From: Andrew Lunn @ 2026-11-04 21:06 UTC (permalink / raw)
To: Alvin Šipraga
Cc: Iustin Stolniceanu, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Parthiban Veerasooran, Heiner Kallweit,
Russell King, Marcelo Schmitt, netdev, devicetree, linux-kernel,
Ciprian Regus
> > +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)?
netdev Maintainers have a strong dislike for this sort of guard. Not
in this case, but often it protects too much, since the end of the
function is frequently passed the end of the critical section.
The scoped variant can be used, since that explicitly marks the end of
the critical section.
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
Using device-managed and cleanup.h constructs
Netdev remains skeptical about promises of all “auto-cleanup” APIs,
including even devm_ helpers, historically. They are not the preferred
style of implementation, merely an acceptable one.
Use of guard() is discouraged within any function longer than 20
lines, scoped_guard() is considered more readable. Using normal
lock/unlock is still (weakly) preferred.
Andrew
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
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
@ 2026-11-04 16:10 ` sashiko-bot
1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-04 16:10 UTC (permalink / raw)
To: Iustin Stolniceanu; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] net: adin1110: broken uc and mc filtering in adin1110_set_rx_mode_tc6()
--
commit fee140bec5b369ab45ae064be0620e5878578e1e
Author: Ciprian Regus <ciprian.regus@analog.com>
net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
This commit adds OPEN Alliance TC6 SPI protocol support for the ADIN1110
MAC-PHY using the oa_tc6 framework when the adi,spi-oa-tc6 property is
present. It introduces OA TC6 register accessors, net_device_ops callbacks,
and probe handling while sharing the existing MAC configuration logic.
> diff --git a/drivers/net/ethernet/adi/adin1110.c b/drivers/net/ethernet/adi/adin1110.c
> index 53fac3dc8be62..d08b165bf7c48 100644
> --- a/drivers/net/ethernet/adi/adin1110.c
> +++ b/drivers/net/ethernet/adi/adin1110.c
[ ... ]
> @@ -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);
> + port_priv->flags = dev->flags;
> + ret = adin1110_setup_rx_mode(port_priv);
> + mutex_unlock(&priv->lock);
> +
> + return ret;
> +}
[ ... ]
> @@ -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)
> +{
[ ... ]
> + 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);
> +}
[Severity: Medium]
In adin1110_oa_tc6_probe(), netdev->priv_flags sets IFF_UNICAST_FLT, which
tells netif_rx_mode_run() -> netif_uc_promisc_update() not to fall back to
IFF_PROMISC when secondary unicast addresses are added to dev->uc.
However, adin1110_set_rx_mode_tc6() ignores both the uc and mc address list
snapshots and only calls adin1110_setup_rx_mode():
drivers/net/ethernet/adi/adin1110.c:adin1110_setup_rx_mode() {
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));
...
}
Because neither dev->uc nor dev->mc is programmed into the hardware MAC
filter slots, and ADIN_MAC_MULTICAST_ADDR_SLOT is only enabled when
IFF_ALLMULTI is set, won't frames destined to secondary unicast addresses
or joined multicast groups (such as IPv6 Neighbor Discovery solicited-node
multicast when IFF_ALLMULTI is not set) be silently dropped in OA TC6 mode?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103160918.409701-1-iustin.stolniceanu@analog.com?part=4
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
2026-11-03 16:09 [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (3 preceding siblings ...)
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-03 16:09 ` Iustin Stolniceanu
2026-11-04 14:29 ` Alvin Šipraga
2026-11-03 20:49 ` [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Andrew Lunn
2026-11-03 20:54 ` Andrew Lunn
6 siblings, 1 reply; 19+ messages in thread
From: Iustin Stolniceanu @ 2026-11-03 16:09 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt
Cc: netdev, devicetree, linux-kernel, Ciprian Regus,
Iustin Stolniceanu
The ADIN1110 and ADIN2111 MAC-PHYs integrate the ADIN1100 PHY and use
this driver. On these devices, CRSM_SFT_RST resets the whole chip, not
only the PHY, undoing the configuration done by the MAC driver before
the PHY is attached. MDIO reads also return stale data until the reset
completes.
The MAC driver already resets the chip before attaching the PHY, so
skip the soft reset on these devices.
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
drivers/net/phy/adin1100.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
index 8f9753d4318c..7b4e68285a9d 100644
--- a/drivers/net/phy/adin1100.c
+++ b/drivers/net/phy/adin1100.c
@@ -232,6 +232,15 @@ static int adin_soft_reset(struct phy_device *phydev)
{
int ret;
+ /* On the ADIN1110/ADIN2111 MAC-PHYs, CRSM_SFT_RST resets the whole
+ * chip, undoing the MAC driver's setup, and MDIO reads return stale
+ * data until it completes. The MAC driver has already reset the
+ * chip, so skip it here.
+ */
+ if (phy_id_compare_model(phydev->phy_id, PHY_ID_ADIN1110) ||
+ phy_id_compare_model(phydev->phy_id, PHY_ID_ADIN2111))
+ return 0;
+
ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN_CRSM_SFT_RST, ADIN_CRSM_SFT_RST_EN);
if (ret < 0)
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
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
0 siblings, 1 reply; 19+ messages in thread
From: Alvin Šipraga @ 2026-11-04 14:29 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
On Tue, Nov 03, 2026 at 06:09:18PM +0200, Iustin Stolniceanu wrote:
> The ADIN1110 and ADIN2111 MAC-PHYs integrate the ADIN1100 PHY and use
> this driver. On these devices, CRSM_SFT_RST resets the whole chip, not
> only the PHY, undoing the configuration done by the MAC driver before
> the PHY is attached. MDIO reads also return stale data until the reset
> completes.
>
> The MAC driver already resets the chip before attaching the PHY, so
> skip the soft reset on these devices.
Is this a bugfix?
>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
> ---
> drivers/net/phy/adin1100.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
> index 8f9753d4318c..7b4e68285a9d 100644
> --- a/drivers/net/phy/adin1100.c
> +++ b/drivers/net/phy/adin1100.c
> @@ -232,6 +232,15 @@ static int adin_soft_reset(struct phy_device *phydev)
> {
> int ret;
>
> + /* On the ADIN1110/ADIN2111 MAC-PHYs, CRSM_SFT_RST resets the whole
> + * chip, undoing the MAC driver's setup, and MDIO reads return stale
> + * data until it completes. The MAC driver has already reset the
> + * chip, so skip it here.
> + */
> + if (phy_id_compare_model(phydev->phy_id, PHY_ID_ADIN1110) ||
> + phy_id_compare_model(phydev->phy_id, PHY_ID_ADIN2111))
> + return 0;
It seems more common to have a separate struct phy_driver entry in the
array passed to module_phy_driver() with a more precise
phy_id/mask. Then for ADIN1110/ADIN2111 you'd omit the
.soft_reset. Maybe Andrew knows what is preferred here?
> +
> ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN_CRSM_SFT_RST, ADIN_CRSM_SFT_RST_EN);
> if (ret < 0)
> return ret;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
2026-11-04 14:29 ` Alvin Šipraga
@ 2026-11-04 21:16 ` Andrew Lunn
0 siblings, 0 replies; 19+ messages in thread
From: Andrew Lunn @ 2026-11-04 21:16 UTC (permalink / raw)
To: Alvin Šipraga
Cc: Iustin Stolniceanu, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Parthiban Veerasooran, Heiner Kallweit,
Russell King, Marcelo Schmitt, netdev, devicetree, linux-kernel,
Ciprian Regus
> > @@ -232,6 +232,15 @@ static int adin_soft_reset(struct phy_device *phydev)
> > {
> > int ret;
> >
> > + /* On the ADIN1110/ADIN2111 MAC-PHYs, CRSM_SFT_RST resets the whole
> > + * chip, undoing the MAC driver's setup, and MDIO reads return stale
> > + * data until it completes. The MAC driver has already reset the
> > + * chip, so skip it here.
> > + */
> > + if (phy_id_compare_model(phydev->phy_id, PHY_ID_ADIN1110) ||
> > + phy_id_compare_model(phydev->phy_id, PHY_ID_ADIN2111))
> > + return 0;
>
> It seems more common to have a separate struct phy_driver entry in the
> array passed to module_phy_driver() with a more precise
> phy_id/mask. Then for ADIN1110/ADIN2111 you'd omit the
> .soft_reset. Maybe Andrew knows what is preferred here?
Humm...
#define PHY_ID_ADIN1100 0x0283bc81
#define PHY_ID_ADIN1110 0x0283bc91
#define PHY_ID_ADIN2111 0x0283bca1
static struct phy_driver adin_driver[] = {
{
.phy_id = PHY_ID_ADIN1100,
.phy_id_mask = 0xffffffcf,
.name = "ADIN1100",
That phy_id_mask is funky.
static const struct mdio_device_id __maybe_unused adin_tbl[] = {
{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100) },
{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110) },
{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111) },
{ }
And this contradicts it.
Please leaf through the datasheet for these devices and check how the
ID is defined. Often the lowest nibble is the revision, so 0x0283bc81
and 0x0283bc8f would be the same device, just different bugs.
However, they can be different devices. This is why we have:
#define PHY_ID_MATCH_EXACT(id) .phy_id = (id), .phy_id_mask = PHY_ID_MATCH_EXTACT_MASK
#define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = PHY_ID_MATCH_MODEL_MASK
#define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = PHY_ID_MATCH_VENDOR_MASK
Since they are different devices, it would be good to have multiple
struct phy_driver, if that can be done without breaking backwards
compatibility.
Andrew
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support
2026-11-03 16:09 [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (4 preceding siblings ...)
2026-11-03 16:09 ` [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
@ 2026-11-03 20:49 ` Andrew Lunn
2026-11-04 8:06 ` Stolniceanu, Iustin
2026-11-03 20:54 ` Andrew Lunn
6 siblings, 1 reply; 19+ messages in thread
From: Andrew Lunn @ 2026-11-03 20:49 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
On Tue, Nov 03, 2026 at 06:09:13PM +0200, Iustin Stolniceanu wrote:
> The ADIN1110 supports two SPI host interface protocols: the generic SPI
> protocol, which the adin1110 driver implements today, and the OPEN
> Alliance TC6 protocol. The protocol is selected by the SPI_CFG1 pin,
> which the device latches when it comes out of reset.
Is it possible to read the status of this pin from a register?
Andrew
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support
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
0 siblings, 1 reply; 19+ messages in thread
From: Stolniceanu, Iustin @ 2026-11-04 8:06 UTC (permalink / raw)
To: Andrew Lunn
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Schmitt, Marcelo, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Regus, Ciprian
On Tue, Nov 03, 2026 at 20:49 UTC, Andrew Lunn wrote:
> On Tue, Nov 03, 2026 at 06:09:13PM +0200, Iustin Stolniceanu wrote:
> > The ADIN1110 supports two SPI host interface protocols: the generic SPI
> > protocol, which the adin1110 driver implements today, and the OPEN
> > Alliance TC6 protocol. The protocol is selected by the SPI_CFG1 pin,
> > which the device latches when it comes out of reset.
>
> Is it possible to read the status of this pin from a register?
No, not for SPI_CFG1. The ADIN1110 has no register that reports the
SPI_CFG1 level or the selected host protocol.
The only way to detect the mode at runtime would be to probe it:
read the PHYID register using one framing and fall back to the
other if the value does not match. I have not tried that yet, and I
am not sure that clocking a transaction in the wrong framing is
harmless for the device, so I went with a DT property describing how
the board straps the pin.
If you would prefer autodetection, I can try it on hardware for V2.
Thanks,
Iustin
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support
2026-11-04 8:06 ` Stolniceanu, Iustin
@ 2026-11-04 14:59 ` Andrew Lunn
2026-11-04 15:14 ` Stolniceanu, Iustin
0 siblings, 1 reply; 19+ messages in thread
From: Andrew Lunn @ 2026-11-04 14:59 UTC (permalink / raw)
To: Stolniceanu, Iustin
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Schmitt, Marcelo, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Regus, Ciprian
On Wed, Nov 04, 2026 at 08:06:56AM +0000, Stolniceanu, Iustin wrote:
> On Tue, Nov 03, 2026 at 20:49 UTC, Andrew Lunn wrote:
> > On Tue, Nov 03, 2026 at 06:09:13PM +0200, Iustin Stolniceanu wrote:
> > > The ADIN1110 supports two SPI host interface protocols: the generic SPI
> > > protocol, which the adin1110 driver implements today, and the OPEN
> > > Alliance TC6 protocol. The protocol is selected by the SPI_CFG1 pin,
> > > which the device latches when it comes out of reset.
> >
> > Is it possible to read the status of this pin from a register?
>
> No, not for SPI_CFG1. The ADIN1110 has no register that reports the
> SPI_CFG1 level or the selected host protocol.
Seems like a badly thought out hardware design.
> The only way to detect the mode at runtime would be to probe it:
> read the PHYID register using one framing and fall back to the
> other if the value does not match. I have not tried that yet, and I
> am not sure that clocking a transaction in the wrong framing is
> harmless for the device, so I went with a DT property describing how
> the board straps the pin.
>
> If you would prefer autodetection, I can try it on hardware for V2.
Do you have an reference design using a jumper, so you can try all
combinations? At minimum, it would be good to probe the chip, print an
error message, and return ENODEV, if the property and the hardware
don't match.
Andrew
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support
2026-11-04 14:59 ` Andrew Lunn
@ 2026-11-04 15:14 ` Stolniceanu, Iustin
0 siblings, 0 replies; 19+ messages in thread
From: Stolniceanu, Iustin @ 2026-11-04 15:14 UTC (permalink / raw)
To: Andrew Lunn
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Schmitt, Marcelo, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Regus, Ciprian
On Wed, Nov 4, 2026 at 4:59 PM, Andrew Lunn wrote:
> On Wed, Nov 04, 2026 at 08:06:56AM +0000, Stolniceanu, Iustin wrote:
> > No, not for SPI_CFG1. The ADIN1110 has no register that reports the
> > SPI_CFG1 level or the selected host protocol.
>
> Seems like a badly thought out hardware design.
Agreed, it is inconvenient indeed.
> Do you have an reference design using a jumper, so you can try all
> combinations?
Yes, I do have a reference board and I am able to test all combinations.
I will check what the device does when it is accessed with the wrong framing
and report back.
> At minimum, it would be good to probe the chip, print an error
> message, and return ENODEV, if the property and the hardware don't
> match.
Will do. In V2, probe will read the PHYID using the framing selected
by the property. If it does not match, it will print an error saying
that the adi,spi-oa-tc6 setting does not match how the board is
strapped, and return -ENODEV.
If accessing the device in the wrong framing turns out to be harmless,
I can look at autodetection and either drop the property or keep it
only as an override. I would rather decide that after testing than
commit to it now.
Best regards,
Iustin
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support
2026-11-03 16:09 [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (5 preceding siblings ...)
2026-11-03 20:49 ` [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Andrew Lunn
@ 2026-11-03 20:54 ` Andrew Lunn
6 siblings, 0 replies; 19+ messages in thread
From: Andrew Lunn @ 2026-11-03 20:54 UTC (permalink / raw)
To: Iustin Stolniceanu
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Heiner Kallweit, Russell King,
Marcelo Schmitt, netdev, devicetree, linux-kernel, Ciprian Regus
> Open question: indirect MDIO is used automatically when STDCAP.DPRAC is
> not set. Would you prefer an explicit quirk flag instead?
I would say the current code is correct. Lets assume vendors don't
mess up and set STDCAP.DPRAC when they in fact don't support direct
access, until a vendor does actually mess up. Then we can add a quirk.
Andrew
^ permalink raw reply [flat|nested] 19+ messages in thread