* [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support
@ 2026-11-09 8:59 Iustin Stolniceanu
2026-11-09 8:59 ` [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus Iustin Stolniceanu
` (6 more replies)
0 siblings, 7 replies; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, Iustin Stolniceanu
The ADIN1110 supports two SPI host interface protocols: the generic SPI
protocol, which the adin1110 driver implements today, and the OPEN
Alliance TC6 protocol. Two pins select the mode, and the device latches
them when it comes out of reset:
- SPI_CFG1 selects the protocol: OPEN Alliance TC6 when low (or left
floating), generic SPI when high.
- SPI_CFG0 enables the CRC8 of the generic SPI protocol, or the control
data protection of the OPEN Alliance protocol: enabled when low,
disabled when high.
SPI_CFG0 can be read back, as the PROTE bit of CONFIG0, and the generic
SPI case is already described by the adi,spi-crc property. No register
reports SPI_CFG1, so the protocol cannot be detected by reading the chip.
This series adds OPEN Alliance TC6 support for the ADIN1110 using the
oa_tc6 framework, selected by a new adi,spi-oa-tc6 DT property:
1: oa_tc6: forward declare struct mii_bus in oa_tc6.h
2-3: oa_tc6: configurable reset settle times, and indirect MDIO access
(MDIOACC) for MAC-PHYs without direct PHY register access
4-5: adin1100: separate phy_driver entries per model, then skip the
soft reset on ADIN1110/ADIN2111, as it resets the whole MAC-PHY
6: dt-bindings: add adi,spi-oa-tc6
7: adin1110: add the OPEN Alliance TC6 probe path
The ADIN2111 is not supported in OPEN Alliance TC6 mode yet.
If adi,spi-oa-tc6 does not match the board strapping, probe fails with
-ENODEV and a message pointing at the SPI_CFG1 strap and the property.
As promised in the v1 thread, I tried the wrong framing on an
EVAL-ADIN1110EBZ in both directions. OA TC6 access to a generic SPI
strapped chip gets no valid reply and does not set SPI_ERR. Generic
SPI access to an OA TC6 strapped chip returns garbage and sets HDRE and
LOFE in STATUS0, which are write-1-to-clear, and OA TC6 access keeps
working afterwards. So the protocols can be told apart, but I kept the
explicit property for now. If autodetection is preferred, I can do that
before the binding is merged.
Testing: Raspberry Pi 4 with an EVAL-ADIN1110EBZ, SPI at 25 MHz, against
a 10BASE-T1L link partner, on a normal and a debug kernel (KASAN,
lockdep, kmemleak). OPEN Alliance TC6 mode: probe/remove 20x, sysfs
unbind/bind 10x, link up/down under traffic, frame sizes 42-1514 bytes,
flood ping, ~9.4 Mbit/s TCP in each direction, 0% loss on 10 Mbit/s
bidirectional UDP, joined multicast group and macvlan traffic. Generic
SPI mode: same results with and without the series. Both mismatches
(adi,spi-oa-tc6 set on a generic SPI strapped board, and the reverse)
fail with -ENODEV and a clear message. OPEN Alliance TC6 was tested with
and without control protection (SPI_CFG0).
Known limitations, unchanged by this series:
- A "Config unsync" error (e.g. after a chip reset) makes oa_tc6
disable the device until the driver is reloaded.
- On unload, a lockdep enabled kernel prints a "suspicious RCU usage"
warning from phy_detach_internal(), as phy_disconnect() is called
without the RTNL held. The generic SPI path of the unpatched driver
does the same.
Parts of the code and of the commit messages were developed with the
help of an AI assistant (Claude Opus 5.5); all changes were reviewed and
tested on hardware by the submitter. The v1 patches 1-4 were also
reviewed with Sashiko (Claude Opus 5.5) before sending. For v2 I ran
Sashiko again on the settle time patch (2/7) and the adin1110 patch
(7/7), the two with real code changes, and fixed the one real issue
it found.
Changes in v2:
- Reordered: the adin1100 patches now come before the adin1110 OA TC6
patch that needs them, so the series bisects cleanly.
- 1/7: new: split the struct mii_bus forward declaration out of the
adin1110 patch (Alvin)
Link: https://lore.kernel.org/r/au3C27DWOcZiMLxb@analog.com
- 2/7: split reset_settle_us into hwreset_settle_us (default 1 ms) and
softreset_settle_us (default 0); fsleep() unconditionally (Alvin)
Link: https://lore.kernel.org/r/aus8XiLynJmSXaXc@analog.com
- 3/7: use if (!DPRAC) mdio_indirect_mode = true (Alvin)
Link: https://lore.kernel.org/r/aus8GQ-Xut5XBNwU@analog.com
- 4/7: new: one phy_driver per model with PHY_ID_MATCH_MODEL() instead
of a single entry with mask 0xffffffcf (Andrew, Alvin)
Link: https://lore.kernel.org/r/309a1eb9-8255-42f0-a510-b40bf3302bbd@lunn.ch
- 5/7: (was 5/5) omit .soft_reset on the ADIN1110/ADIN2111 entries
instead of checking the PHY ID in the callback (Alvin)
Link: https://lore.kernel.org/r/autAidTgykOObtzp@analog.com
- 6/7: (was 3/5) disallow adi,spi-crc together with adi,spi-oa-tc6 (Alvin)
Link: https://lore.kernel.org/r/aus899mJ-XZg6ZgF@analog.com
- 7/7: (was 4/5) set 50 ms / 10 ms settle times from the datasheet;
-ENODEV instead of -EOPNOTSUPP for ADIN2111; whitespace nits. Fail
with -ENODEV and a clear message if adi,spi-oa-tc6 does not match the
strapping (Andrew). Accept all multicast when the multicast list is not
empty and don't set IFF_UNICAST_FLT, and use DEV_STATS_INC() for
tx_dropped (Sashiko).
Link: https://lore.kernel.org/r/aus-LdkVRPxR6lFu@analog.com
Link: https://lore.kernel.org/r/c1747691-2c2e-4a7a-af04-92c94cb041b2@lunn.ch
Link: https://lore.kernel.org/r/sashiko-outbox-197578@kernel.org
v1: https://lore.kernel.org/r/20261103160918.409701-1-iustin.stolniceanu@analog.com
Ciprian Regus (4):
net: ethernet: oa_tc6: Allow custom reset settle time values
net: ethernet: oa_tc6: Add support for MDIO indirect access
dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6
net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
Iustin Stolniceanu (3):
net: ethernet: oa_tc6: Forward declare struct mii_bus
net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111
net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
.../devicetree/bindings/net/adi,adin1110.yaml | 12 +
drivers/net/ethernet/adi/Kconfig | 1 +
drivers/net/ethernet/adi/adin1110.c | 206 +++++++++++++++++-
drivers/net/ethernet/oa_tc6.c | 188 ++++++++++++++--
drivers/net/phy/adin1100.c | 33 ++-
include/linux/oa_tc6.h | 20 ++
6 files changed, 435 insertions(+), 25 deletions(-)
base-commit: 7b6ab4dbc156e2f96419715130b3d5738a306c93
--
2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-10 9:42 ` Alvin Šipraga
2026-11-09 8:59 ` [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
` (5 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, Iustin Stolniceanu
The Clause 45 MDIO accessors in oa_tc6.h take a struct mii_bus pointer,
but the header does not declare the type. A file that includes oa_tc6.h
before linux/phy.h or linux/mdio.h gets a "'struct mii_bus' declared
inside parameter list" warning, which is an error with CONFIG_WERROR.
lan865x and adin1140 are not affected, as they include one of those
first. The adin1110 driver, which gets OA TC6 support later in this
series, does not, and hits the warning.
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
include/linux/oa_tc6.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 27f652d4920b..6eac6b842156 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -64,6 +64,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] 18+ messages in thread
* [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
2026-11-09 8:59 ` [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-09 17:10 ` Selvamani Rajagopal
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
` (4 subsequent siblings)
6 siblings, 2 replies; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, 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, and polls STATUS0 for RESETC right after
the software reset. 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 and up to 10 ms after a software reset before
its registers can be accessed, and it tristates its SDO pin during the
reset.
Add hwreset_settle_us and softreset_settle_us fields to struct
oa_tc6_quirks so drivers can set these times. If hwreset_settle_us is
not set, the GPIO reset keeps the existing 1 ms wait. If
softreset_settle_us is not set, there is no wait after the software
reset, as before.
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>
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
---
drivers/net/ethernet/oa_tc6.c | 17 +++++++++++++----
include/linux/oa_tc6.h | 4 ++++
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 364027c39fa4..549997ba363a 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -89,6 +89,8 @@ struct oa_tc6 {
bool int_flag;
bool disable_traffic;
bool prot_ctrl;
+ u32 hwreset_settle_us;
+ u32 softreset_settle_us;
enum oa_tc6_quirk_flag quirk_flags;
struct gpio_desc *reset_gpio;
};
@@ -652,6 +654,8 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6)
if (ret)
return ret;
+ fsleep(tc6->softreset_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,13 @@ 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)
+ tc6->hwreset_settle_us = 1000;
+ if (quirks) {
tc6->quirk_flags = quirks->quirk_flags;
+ if (quirks->hwreset_settle_us)
+ tc6->hwreset_settle_us = quirks->hwreset_settle_us;
+ tc6->softreset_settle_us = quirks->softreset_settle_us;
+ }
/* Set the SPI controller to pump at realtime priority */
tc6->spi->rt = true;
@@ -1515,12 +1524,12 @@ 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).
*/
fsleep(10);
gpiod_set_value_cansleep(tc6->reset_gpio, 0);
- fsleep(1000);
+ fsleep(tc6->hwreset_settle_us);
}
/* 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 6eac6b842156..f676e712e75d 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -73,6 +73,10 @@ enum oa_tc6_quirk_flag {
struct oa_tc6_quirks {
enum oa_tc6_quirk_flag quirk_flags;
+ /* Wait after deasserting the reset GPIO, 1 ms if not set */
+ u32 hwreset_settle_us;
+ /* Wait after the software reset, before polling for RESETC */
+ u32 softreset_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] 18+ messages in thread
* [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
2026-11-09 8:59 ` [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus Iustin Stolniceanu
2026-11-09 8:59 ` [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111 Iustin Stolniceanu
` (3 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, 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>
Reviewed-by: Alvin Šipraga <alvin.sipraga@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 549997ba363a..be1833c232e3 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 hwreset_settle_us;
u32 softreset_settle_us;
enum oa_tc6_quirk_flag quirk_flags;
@@ -436,24 +437,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)
@@ -462,6 +588,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);
@@ -476,6 +605,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);
@@ -507,6 +639,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;
@@ -525,6 +661,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;
@@ -535,8 +675,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;
+
+ if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS))
+ tc6->mdio_indirect_mode = true;
+
tc6->mdiobus = mdiobus_alloc();
if (!tc6->mdiobus) {
netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
@@ -587,13 +735,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 f676e712e75d..d4fce06ed93b 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] 18+ messages in thread
* [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (2 preceding siblings ...)
2026-11-09 8:59 ` [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-10 9:46 ` Alvin Šipraga
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
` (2 subsequent siblings)
6 siblings, 2 replies; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, Iustin Stolniceanu
The ADIN1100, ADIN1110 and ADIN2111 report different model numbers in
bits [9:4] of PHY_ID2 (0x8, 0x9 and 0xa), with the silicon revision in
bits [3:0]. A single phy_driver entry with the 0xffffffcf mask currently
binds all three by ignoring bits [5:4] of the model number.
Use one phy_driver entry per device, matched with PHY_ID_MATCH_MODEL(),
as is already done in the mdio_device_id table. This allows device
specific callbacks to be set in a following patch. The callbacks are
unchanged, so this is not a functional change apart from the reported
driver name.
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
drivers/net/phy/adin1100.c | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
index 8f9753d4318c..b946cb86b6c4 100644
--- a/drivers/net/phy/adin1100.c
+++ b/drivers/net/phy/adin1100.c
@@ -322,8 +322,7 @@ static int adin_probe(struct phy_device *phydev)
static struct phy_driver adin_driver[] = {
{
- .phy_id = PHY_ID_ADIN1100,
- .phy_id_mask = 0xffffffcf,
+ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100),
.name = "ADIN1100",
.get_features = adin_get_features,
.soft_reset = adin_soft_reset,
@@ -338,6 +337,38 @@ static struct phy_driver adin_driver[] = {
.get_sqi = adin_get_sqi,
.get_sqi_max = adin_get_sqi_max,
},
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110),
+ .name = "ADIN1110",
+ .get_features = adin_get_features,
+ .soft_reset = adin_soft_reset,
+ .probe = adin_probe,
+ .config_aneg = adin_config_aneg,
+ .read_status = adin_read_status,
+ .config_intr = adin_config_intr,
+ .handle_interrupt = adin_phy_handle_interrupt,
+ .set_loopback = adin_set_loopback,
+ .suspend = adin_suspend,
+ .resume = adin_resume,
+ .get_sqi = adin_get_sqi,
+ .get_sqi_max = adin_get_sqi_max,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111),
+ .name = "ADIN2111",
+ .get_features = adin_get_features,
+ .soft_reset = adin_soft_reset,
+ .probe = adin_probe,
+ .config_aneg = adin_config_aneg,
+ .read_status = adin_read_status,
+ .config_intr = adin_config_intr,
+ .handle_interrupt = adin_phy_handle_interrupt,
+ .set_loopback = adin_set_loopback,
+ .suspend = adin_suspend,
+ .resume = adin_resume,
+ .get_sqi = adin_get_sqi,
+ .get_sqi_max = adin_get_sqi_max,
+ },
};
module_phy_driver(adin_driver);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (3 preceding siblings ...)
2026-11-09 8:59 ` [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111 Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-10 9:48 ` Alvin Šipraga
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 6/7] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
2026-11-09 8:59 ` [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
6 siblings, 2 replies; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, 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.
This is not an issue with the generic SPI protocol, where the adin1110
driver configures the MAC in ndo_open, after phy_connect(). The OPEN
Alliance TC6 protocol support, added in a following patch, configures
the device in oa_tc6_init() before the PHY is attached, and the
configuration would be lost.
The MAC driver already resets the chip before attaching the PHY, so
don't set .soft_reset for these devices.
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
drivers/net/phy/adin1100.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
index b946cb86b6c4..ddb91b0e7081 100644
--- a/drivers/net/phy/adin1100.c
+++ b/drivers/net/phy/adin1100.c
@@ -341,7 +341,6 @@ static struct phy_driver adin_driver[] = {
PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110),
.name = "ADIN1110",
.get_features = adin_get_features,
- .soft_reset = adin_soft_reset,
.probe = adin_probe,
.config_aneg = adin_config_aneg,
.read_status = adin_read_status,
@@ -357,7 +356,6 @@ static struct phy_driver adin_driver[] = {
PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111),
.name = "ADIN2111",
.get_features = adin_get_features,
- .soft_reset = adin_soft_reset,
.probe = adin_probe,
.config_aneg = adin_config_aneg,
.read_status = adin_read_status,
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net-next v2 6/7] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (4 preceding siblings ...)
2026-11-09 8:59 ` [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-10 8:05 ` Krzysztof Kozlowski
2026-11-09 8:59 ` [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
6 siblings, 1 reply; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, 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.
adi,spi-crc enables the CRC of the generic SPI protocol, so it is not
allowed together with adi,spi-oa-tc6. The OPEN Alliance protection mode
needs no property, as the driver reads it from the PROTE bit in CONFIG0.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
.../devicetree/bindings/net/adi,adin1110.yaml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/adi,adin1110.yaml b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
index 0a73e01d7f97..fa30787c663d 100644
--- a/Documentation/devicetree/bindings/net/adi,adin1110.yaml
+++ b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
@@ -28,6 +28,12 @@ description: |
allOf:
- $ref: ethernet-controller.yaml#
- $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ required:
+ - adi,spi-oa-tc6
+ then:
+ properties:
+ adi,spi-crc: false
properties:
compatible:
@@ -43,6 +49,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] 18+ messages in thread
* [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
` (5 preceding siblings ...)
2026-11-09 8:59 ` [PATCH net-next v2 6/7] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
@ 2026-11-09 8:59 ` Iustin Stolniceanu
2026-11-12 12:00 ` netdev-bot+sashiko
6 siblings, 1 reply; 18+ messages in thread
From: Iustin Stolniceanu @ 2026-11-09 8:59 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: Alvin Šipraga, Ciprian Regus, netdev, devicetree,
linux-kernel, 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 MAC filter is not programmed with the unicast and multicast address
lists, so accept all multicast frames when the multicast list is not
empty, and do not set IFF_UNICAST_FLT. The core then enables promiscuous
mode for secondary unicast addresses.
The ADIN2111 is not supported in OPEN Alliance TC6 mode yet, and probe
fails with -ENODEV.
No register reports the selected protocol, so the driver relies on
adi,spi-oa-tc6 matching the board strapping. If the first OA TC6 control
transfer gets no valid reply, or the PHY ID does not match, probe fails
with -ENODEV and a message pointing at the strap and the property. The
generic SPI path now does the same on a PHY ID mismatch, instead of
returning -EIO.
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 | 206 +++++++++++++++++++++++++++-
2 files changed, 203 insertions(+), 4 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..3f054565acb0 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,27 @@ 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;
+ /* The multicast list isn't programmed into the MAC filter, so accept
+ * all multicast when it is not empty.
+ */
+ if (!netdev_hw_addr_list_empty(mc))
+ port_priv->flags |= IFF_ALLMULTI;
+ 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 +998,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 +1046,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 +1099,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_INC(dev, 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 +1161,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)
{
@@ -1115,14 +1230,89 @@ static int adin1110_check_spi(struct adin1110_priv *priv)
return ret;
if (val != priv->cfg->phy_id_val) {
- dev_err(&priv->spidev->dev, "PHY ID expected: %x, read: %x\n",
+ dev_err(&priv->spidev->dev,
+ "PHY ID expected: %x, read: %x, check the SPI_CFG1 strap and adi,spi-oa-tc6\n",
priv->cfg->phy_id_val, val);
- return -EIO;
+ return -ENODEV;
}
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;
+ u32 val;
+ int ret;
+
+ dev = &priv->spidev->dev;
+
+ if (priv->cfg->id == ADIN2111_MAC)
+ return dev_err_probe(dev, -ENODEV,
+ "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.hwreset_settle_us = 50000;
+ quirks.softreset_settle_us = 10000;
+
+ port_priv->tc6 = oa_tc6_init(priv->spidev, netdev, &quirks);
+ if (PTR_ERR_OR_ZERO(port_priv->tc6) == -EPROTO)
+ return dev_err_probe(dev, -ENODEV,
+ "no OA TC6 reply, check the SPI_CFG1 strap and adi,spi-oa-tc6\n");
+ 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;
+
+ ret = adin1110_read_reg(priv, ADIN1110_PHY_ID, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val != priv->cfg->phy_id_val)
+ return dev_err_probe(dev, -ENODEV, "PHY ID expected: %x, read: %x\n",
+ priv->cfg->phy_id_val, val);
+
+ /* 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->netns_immutable = true;
+
+ return devm_register_netdev(dev, netdev);
+}
+
static int adin1110_hw_forwarding(struct adin1110_priv *priv, bool enable)
{
int ret;
@@ -1649,6 +1839,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 +1854,18 @@ 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);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* RE: [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values
2026-11-09 8:59 ` [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
@ 2026-11-09 17:10 ` Selvamani Rajagopal
2026-11-12 12:00 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: Selvamani Rajagopal @ 2026-11-09 17:10 UTC (permalink / raw)
To: 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
Cc: Alvin Šipraga, Ciprian Regus, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
> -----Original Message-----
> From: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
> Subject: [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time
> values
>
> diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
> index 364027c39fa4..549997ba363a 100644
> --- a/drivers/net/ethernet/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6.c
> @@ -89,6 +89,8 @@ struct oa_tc6 {
> bool int_flag;
> bool disable_traffic;
> bool prot_ctrl;
> + u32 hwreset_settle_us;
> + u32 softreset_settle_us;
Should we add the structure "struct oa_tc6_quirks" here, instead of adding three parameters
from that structure, which would 1) make copying easier. 2) Avoid future changes to the oa_tc6
structure, in case more elements gets added in oa_tc6_quirks structure.
> enum oa_tc6_quirk_flag quirk_flags;
> struct gpio_desc *reset_gpio;
> };
>
> struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
> --
> 2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 6/7] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6
2026-11-09 8:59 ` [PATCH net-next v2 6/7] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
@ 2026-11-10 8:05 ` Krzysztof Kozlowski
0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-10 8:05 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, Alvin Šipraga, Ciprian Regus, netdev,
devicetree, linux-kernel
On Mon, Nov 09, 2026 at 10:59:51AM +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.
>
> adi,spi-crc enables the CRC of the generic SPI protocol, so it is not
> allowed together with adi,spi-oa-tc6. The OPEN Alliance protection mode
> needs no property, as the driver reads it from the PROTE bit in CONFIG0.
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
> ---
> .../devicetree/bindings/net/adi,adin1110.yaml | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/net/adi,adin1110.yaml b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
> index 0a73e01d7f97..fa30787c663d 100644
> --- a/Documentation/devicetree/bindings/net/adi,adin1110.yaml
> +++ b/Documentation/devicetree/bindings/net/adi,adin1110.yaml
> @@ -28,6 +28,12 @@ description: |
> allOf:
> - $ref: ethernet-controller.yaml#
> - $ref: /schemas/spi/spi-peripheral-props.yaml#
> + - if:
> + required:
> + - adi,spi-oa-tc6
> + then:
> + properties:
> + adi,spi-crc: false
Please move the entire allOf to the bottom, after "required:" block.
>
> properties:
> compatible:
> @@ -43,6 +49,12 @@ properties:
> Enable CRC8 checks on SPI read/writes.
> type: boolean
>
> + adi,spi-oa-tc6:
> + description: |
Do not need '|' unless you need to preserve formatting.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus
2026-11-09 8:59 ` [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus Iustin Stolniceanu
@ 2026-11-10 9:42 ` Alvin Šipraga
0 siblings, 0 replies; 18+ messages in thread
From: Alvin Šipraga @ 2026-11-10 9:42 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, Ciprian Regus, netdev, devicetree, linux-kernel
On Mon, Nov 09, 2026 at 10:59:46AM +0200, Iustin Stolniceanu wrote:
> The Clause 45 MDIO accessors in oa_tc6.h take a struct mii_bus pointer,
> but the header does not declare the type. A file that includes oa_tc6.h
> before linux/phy.h or linux/mdio.h gets a "'struct mii_bus' declared
> inside parameter list" warning, which is an error with CONFIG_WERROR.
>
> lan865x and adin1140 are not affected, as they include one of those
> first. The adin1110 driver, which gets OA TC6 support later in this
> series, does not, and hits the warning.
>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
> ---
> include/linux/oa_tc6.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
> index 27f652d4920b..6eac6b842156 100644
> --- a/include/linux/oa_tc6.h
> +++ b/include/linux/oa_tc6.h
> @@ -64,6 +64,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 [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111
2026-11-09 8:59 ` [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111 Iustin Stolniceanu
@ 2026-11-10 9:46 ` Alvin Šipraga
2026-11-12 12:00 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: Alvin Šipraga @ 2026-11-10 9:46 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, Ciprian Regus, netdev, devicetree, linux-kernel
On Mon, Nov 09, 2026 at 10:59:49AM +0200, Iustin Stolniceanu wrote:
> The ADIN1100, ADIN1110 and ADIN2111 report different model numbers in
> bits [9:4] of PHY_ID2 (0x8, 0x9 and 0xa), with the silicon revision in
> bits [3:0]. A single phy_driver entry with the 0xffffffcf mask currently
> binds all three by ignoring bits [5:4] of the model number.
>
> Use one phy_driver entry per device, matched with PHY_ID_MATCH_MODEL(),
> as is already done in the mdio_device_id table. This allows device
> specific callbacks to be set in a following patch. The callbacks are
> unchanged, so this is not a functional change apart from the reported
> driver name.
>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
> ---
> drivers/net/phy/adin1100.c | 35 +++++++++++++++++++++++++++++++++--
> 1 file changed, 33 insertions(+), 2 deletions(-)
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
2026-11-09 8:59 ` [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
@ 2026-11-10 9:48 ` Alvin Šipraga
2026-11-12 12:00 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: Alvin Šipraga @ 2026-11-10 9:48 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, Ciprian Regus, netdev, devicetree, linux-kernel
On Mon, Nov 09, 2026 at 10:59:50AM +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.
>
> This is not an issue with the generic SPI protocol, where the adin1110
> driver configures the MAC in ndo_open, after phy_connect(). The OPEN
> Alliance TC6 protocol support, added in a following patch, configures
> the device in oa_tc6_init() before the PHY is attached, and the
> configuration would be lost.
>
> The MAC driver already resets the chip before attaching the PHY, so
> don't set .soft_reset for these devices.
>
> Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
> ---
> drivers/net/phy/adin1100.c | 2 --
> 1 file changed, 2 deletions(-)
>
> diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
> index b946cb86b6c4..ddb91b0e7081 100644
> --- a/drivers/net/phy/adin1100.c
> +++ b/drivers/net/phy/adin1100.c
> @@ -341,7 +341,6 @@ static struct phy_driver adin_driver[] = {
> PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110),
> .name = "ADIN1110",
> .get_features = adin_get_features,
> - .soft_reset = adin_soft_reset,
> .probe = adin_probe,
> .config_aneg = adin_config_aneg,
> .read_status = adin_read_status,
> @@ -357,7 +356,6 @@ static struct phy_driver adin_driver[] = {
> PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111),
> .name = "ADIN2111",
> .get_features = adin_get_features,
> - .soft_reset = adin_soft_reset,
> .probe = adin_probe,
> .config_aneg = adin_config_aneg,
> .read_status = adin_read_status,
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values
2026-11-09 8:59 ` [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
2026-11-09 17:10 ` Selvamani Rajagopal
@ 2026-11-12 12:00 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 12:00 UTC (permalink / raw)
To: iustin.stolniceanu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, parthiban.veerasooran, hkallweit1, linux,
marcelo.schmitt, alvin.sipraga, ciprian.regus, netdev, devicetree,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] oa_tc6_init() (drivers/net/ethernet/oa_tc6.c) runs the
hardware reset sequence without holding the SPI bus.
--
net: ethernet: oa_tc6: Allow custom reset settle time values
This commit adds hwreset_settle_us and softreset_settle_us fields to
struct oa_tc6_quirks. MAC-PHY drivers can use them to set the wait after
deasserting the reset GPIO and the wait after the software reset. The
hardware reset wait stays at 1 ms by default, and there is still no
software reset wait unless a driver sets one.
> diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
> index 364027c39fa4a..549997ba363ad 100644
> --- a/drivers/net/ethernet/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6.c
[ ... ]
> @@ -1515,12 +1524,12 @@ 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).
> */
> fsleep(10);
> gpiod_set_value_cansleep(tc6->reset_gpio, 0);
> - fsleep(1000);
> + fsleep(tc6->hwreset_settle_us);
[Severity: Medium]
Should this reset sequence in oa_tc6_init() hold the SPI bus while
the MAC-PHY settles?
The commit message says the ADIN1110 tristates its SDO pin during
reset. For the same chip, adin1110_check_spi() in
drivers/net/ethernet/adi/adin1110.c locks the bus for the whole
reset and settle window:
if (reset_gpio) {
/* MISO pin is used for internal configuration, can't have
* anyone else disturbing the SDO line.
*/
spi_bus_lock(priv->spidev->controller);
gpiod_set_value(reset_gpio, 1);
fsleep(10000);
gpiod_set_value(reset_gpio, 0);
...
fsleep(90000);
spi_bus_unlock(priv->spidev->controller);
}
Here, reset is asserted by devm_gpiod_get_optional(..., GPIOD_OUT_HIGH),
then deasserted and held for hwreset_settle_us. No spi_bus_lock() or
spi_bus_unlock() surrounds any of these steps.
The last patch in the series, "net: ethernet: adi: adin1110: Add OA
TC6 protocol support for ADIN1110", does this in
adin1110_oa_tc6_probe():
quirks.hwreset_settle_us = 50000;
quirks.softreset_settle_us = 10000;
port_priv->tc6 = oa_tc6_init(priv->spidev, netdev, &quirks);
In that mode adin1110_check_spi() is never called. The bus-locked reset
is replaced by this unlocked sequence, and the window grows from 1 ms
to 50 ms.
Can a transfer by another device on the same spi_controller drive MISO
in that window and make the ADIN1110 latch a strap that differs from
the board configuration? The later software reset in
oa_tc6_sw_reset_macphy() does not appear to re-sample straps.
Whether this happens depends on which strap is on SDO and when the
part samples it. Those are datasheet details I couldn't verify from
the tree. If the protocol strap were flipped, the first OA TC6 transfer
would fail with -EPROTO. adin1110_oa_tc6_probe() would then fail the
probe with -ENODEV, and this would happen only sometimes, depending on
timing.
Later patches in the series don't seem to add a bus lock to this path.
Would wrapping the reset assert, deassert and settle in oa_tc6_init()
with spi_bus_lock() and spi_bus_unlock() be appropriate here?
> }
>
> /* Check the PROTE bit status so that we can reset the device */
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109085952.105331-1-iustin.stolniceanu%40analog.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access
2026-11-09 8:59 ` [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
@ 2026-11-12 12:00 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 12:00 UTC (permalink / raw)
To: iustin.stolniceanu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, parthiban.veerasooran, hkallweit1, linux,
marcelo.schmitt, alvin.sipraga, ciprian.regus, netdev, devicetree,
linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The indirect MDIO sequencing in drivers/net/ethernet/oa_tc6.c
relies on MDIOACC register behaviour that the patch never checks or…
- [Low] oa_tc6_mdiobus_register() decides that indirect MDIOACC access is
available only because STDCAP.DPRAC (BIT(8),…
--
net: ethernet: oa_tc6: Add support for MDIO indirect access
This adds indirect PHY register access through the MDIOACC registers
for OA TC6 MAC-PHYs that don't advertise direct access in STDCAP.DPRAC.
Clause 22 operations write a single frame to MDIOACC1. Clause 45
operations write an address frame to MDIOACC0 and a read or write frame
to MDIOACC1, then poll MDIOACC1 for TRDONE. The patch also removes the
-ENODEV failure that happened when DPRAC was clear.
> diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
> index 549997ba363ad..be1833c232e37 100644
> --- a/drivers/net/ethernet/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6.c
[ ... ]
> @@ -436,24 +437,149 @@ int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
[ ... ]
> +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 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;
[Severity: Medium]
Both of these sequences rely on MDIOACC behaviour that the code doesn't
check and the commit message doesn't cite from the spec.
oa_tc6_indirect_mdiobus_read_c45() and oa_tc6_indirect_mdiobus_write_c45()
write the address frame to MDIOACC0 and then write the read or write frame
to MDIOACC1 straight away. oa_tc6_mdioacc_wait_done() only polls
OA_TC6_REG_MDIOACC(1), so nothing ever checks TRDONE or TAERR for the
MDIOACC0 frame.
Do all TC6 MAC-PHYs queue MDIOACCn writes and run them in order? If a
device doesn't, could the data frame run before the MMD address is set and
return the contents of the wrong register?
For Clause 22, oa_tc6_indirect_mdiobus_read() and
oa_tc6_indirect_mdiobus_write() put the single frame in MDIOACC1. Does a
write to MDIOACC1 alone start a transaction, whatever MDIOACC0 holds? That
could be its reset value or a stale C45 address frame from an earlier
access.
The existing ADIN1110 driver uses MDIOACC0 for a single C22 transaction:
drivers/net/ethernet/adi/adin1110.c:adin1110_mdio_read() {
...
/* write the clause 22 read command to the chip */
mutex_lock(&priv->lock);
ret = adin1110_write_reg(priv, ADIN1110_MDIOACC, val);
...
}
ADIN1110_MDIOACC is defined as 0x20 there.
Suppose a MAC-PHY without DPRAC runs MDIOACC0..n starting at MDIOACC0, or
doesn't trigger on a lone MDIOACC1 write. Could the C22 reads in the
mdiobus_register() scan then time out, or put an extra frame on the bus?
This may well work on ADIN1110, which uses this path later in the series.
But adin1110.c only shows that MDIOACC0 works when used on its own. Could
the commit message point to the section of the OA TC6 spec that defines
this ordering and triggering behaviour?
[ ... ]
> @@ -535,8 +675,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;
> +
> + if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS))
> + tc6->mdio_indirect_mode = true;
[Severity: Low]
Should this also check the STDCAP bit for indirect PHY register access
(IPRAC, bit 9 in the OA TC6 STDCAP layout)? include/linux/oa_tc6.h only
defines OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS (BIT(8)).
The patch also removes oa_tc6_check_phy_reg_direct_access_capability()
from oa_tc6_phy_init(). So a MAC-PHY that advertises neither direct nor
indirect access, and doesn't set OA_TC6_BROKEN_PHY, is now driven through
MDIOACC0/1 anyway. Before this patch it failed with -ENODEV and "Direct
PHY register access is not supported by the MAC-PHY".
On such a device, if TRDONE never sets, each C22 scan read polls for up to
30 ms and returns -ETIMEDOUT. get_phy_c22_id() turns that into -EIO, and
probe fails with the generic "Could not register MDIO bus".
If the unimplemented register reads back with bit 31 set, the DATA field
would be used as PHY ID or register data that no PHY produced.
None of the in-tree users reach this case: lan865x has DPRAC, adin1140
sets OA_TC6_BROKEN_PHY, and ADIN1110 supports indirect access.
Could indirect mode be selected only when IPRAC is set, and the -ENODEV
error kept for when neither bit is set?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109085952.105331-1-iustin.stolniceanu%40analog.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111
2026-11-09 8:59 ` [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111 Iustin Stolniceanu
2026-11-10 9:46 ` Alvin Šipraga
@ 2026-11-12 12:00 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 12:00 UTC (permalink / raw)
To: iustin.stolniceanu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, parthiban.veerasooran, hkallweit1, linux,
marcelo.schmitt, alvin.sipraga, ciprian.regus, netdev, devicetree,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message for "net: phy: adin1100: Add separate
driver entries for ADIN1110/ADIN2111" says the patch is "not a…
--
net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111
This patch splits the single adin_driver[] entry into three entries, one
each for ADIN1100, ADIN1110 and ADIN2111. The old entry used a 0xffffffcf
mask to match all three parts. Each new entry uses PHY_ID_MATCH_MODEL(),
as the mdio_device_id table already does, and keeps the same callbacks.
> diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
> index 8f9753d4318c8..b946cb86b6c41 100644
> --- a/drivers/net/phy/adin1100.c
> +++ b/drivers/net/phy/adin1100.c
> @@ -322,8 +322,7 @@ static int adin_probe(struct phy_device *phydev)
>
> static struct phy_driver adin_driver[] = {
> {
> - .phy_id = PHY_ID_ADIN1100,
> - .phy_id_mask = 0xffffffcf,
> + PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100),
[Severity: Medium]
The commit message says:
The callbacks are unchanged, so this is not a functional change apart
from the reported driver name.
Doesn't this change also alter which PHY IDs bind to the driver?
The old entry used PHY_ID_ADIN1100 (0x0283bc81) with a 0xffffffcf mask.
That mask ignores bits [5:4] but keeps the revision bits [3:0]. So
phy_bus_match()->genphy_match_phy_device() only matched these four IDs,
all with revision 1:
0x0283bc81, 0x0283bc91, 0x0283bca1, 0x0283bcb1
PHY_ID_MATCH_MODEL() uses PHY_ID_MATCH_MODEL_MASK from
include/linux/phy.h:
#define PHY_ID_MATCH_MODEL_MASK GENMASK(31, 4)
That mask ignores the revision nibble completely. After this patch:
- Every silicon revision of models 0x8, 0x9 and 0xa binds to the ADIN
driver. Before, any revision other than 1 fell back to the generic PHY
driver.
- 0x0283bcb1 (model 0xb) was accepted by the old mask, but no new entry
matches it.
The 0xffffffcf mask came from commit 875b718ac380 ("net: phy: adin1100:
add PHY IDs of adin1110/adin2111"). That commit replaced a
revision-agnostic PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100) entry, and adin_tbl
stayed revision-agnostic. This patch partly reverts that narrowing.
Could the commit message describe the change in revision matching? It
would also help to say whether the change is a deliberate fix, perhaps
with a Fixes: tag for 875b718ac380, or a side effect.
The later patches in the series keep PHY_ID_MATCH_MODEL(), so this
difference is still there at the end of the series.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109085952.105331-1-iustin.stolniceanu%40analog.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
2026-11-09 8:59 ` [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
2026-11-10 9:48 ` Alvin Šipraga
@ 2026-11-12 12:00 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 12:00 UTC (permalink / raw)
To: iustin.stolniceanu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, parthiban.veerasooran, hkallweit1, linux,
marcelo.schmitt, alvin.sipraga, ciprian.regus, netdev, devicetree,
linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message says the whole-chip CRSM_SFT_RST is "not an
issue with the generic SPI protocol" because adin1110 configures the…
- [Low] The patch justifies dropping .soft_reset with "The MAC driver
already resets the chip before attaching the PHY".
--
net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111
This patch removes .soft_reset from the ADIN1110 and ADIN2111 entries in
the adin1100 PHY driver. On these MAC-PHYs, CRSM_SFT_RST resets the whole
chip, which would undo the configuration that the upcoming OPEN Alliance
TC6 support does in oa_tc6_init() before the PHY is attached.
> This is not an issue with the generic SPI protocol, where the adin1110
> driver configures the MAC in ndo_open, after phy_connect(). The OPEN
> Alliance TC6 protocol support, added in a following patch, configures
> the device in oa_tc6_init() before the PHY is attached, and the
> configuration would be lost.
[Severity: Medium]
Does this hold on the resume path as well? phy_init_hw() is not only
called at attach time. mdio_bus_phy_resume() also calls it when the PHY
was suspended by the MDIO bus and mac_managed_pm is not set:
drivers/net/phy/phy_device.c:mdio_bus_phy_resume() {
...
ret = phy_init_hw(phydev);
...
}
adin1110.c has no dev_pm_ops and never sets mac_managed_pm. The adin1100
driver provides .suspend, so on system resume with the interface up, the
sequence before this patch was:
mdio_bus_phy_resume()
phy_init_hw()
adin_soft_reset()
CRSM_SFT_RST (whole-chip reset)
Would that wipe what adin1110_net_open() programmed: CONFIG2 CRC_APPEND,
the IMASK1 interrupt mask, the MAC address filter slots and CONFIG1 SYNC?
Nothing in adin1110 writes them back. The link can then come up while
the MAC is still at reset defaults, and it stays that way until the
interface is taken down and up.
If so, this patch also fixes an existing resume regression on the generic
SPI path. Could the commit message describe that? And should it carry a
Fixes: tag so stable picks it up? The tag would probably be:
Fixes: 875b718ac380 ("net: phy: adin1100: add PHY IDs of adin1110/adin2111")
> diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
> index b946cb86b6c41..ddb91b0e7081f 100644
> --- a/drivers/net/phy/adin1100.c
> +++ b/drivers/net/phy/adin1100.c
> @@ -341,7 +341,6 @@ static struct phy_driver adin_driver[] = {
> PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110),
> .name = "ADIN1110",
> .get_features = adin_get_features,
> - .soft_reset = adin_soft_reset,
[Severity: Low]
With .soft_reset gone, does anything on the generic SPI path still wait
for the chip reset to finish before the PHY is accessed?
The commit message says "The MAC driver already resets the chip before
attaching the PHY". However, adin1110_probe() writes the reset and goes
straight to MDIO bus registration:
drivers/net/ethernet/adi/adin1110.c:adin1110_probe() {
...
ret = adin1110_write_reg(priv, ADIN1110_RESET, ADIN1110_SWRESET);
if (ret < 0)
return ret;
ret = adin1110_register_mdiobus(priv, dev);
...
}
There is no sleep and no poll of STATUS0.RESETC there. The commit message
itself says MDIO reads return stale data until the reset completes. The
oa_tc6 patch in this series ("net: ethernet: oa_tc6: Allow custom reset
settle time values") says the ADIN1110 can need up to 10 ms after a
software reset.
Before this patch, phy_connect()->phy_attach_direct()->phy_init_hw()->
adin_soft_reset() polled for SYS_RDY:
return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
(ret & ADIN_CRSM_SYS_RDY),
10000, 30000, true);
That was the only readiness check on this path. phy_init_hw() now goes
straight to config_intr(), which does a read-modify-write of
ADIN_PHY_SUBSYS_IRQ_MASK. phy_attach_direct() then calls adin_resume().
Either of these can run inside the reset window.
The practical impact looks small, since the MDIO scan already raced this
reset before the patch.
The TC6 path handles this in oa_tc6_sw_reset_macphy(): it calls fsleep()
for the settle time and then polls RESETC. Could adin1110_probe() do the
same before calling adin1110_register_mdiobus()?
> .probe = adin_probe,
> .config_aneg = adin_config_aneg,
> .read_status = adin_read_status,
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109085952.105331-1-iustin.stolniceanu%40analog.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
2026-11-09 8:59 ` [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
@ 2026-11-12 12:00 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-11-12 12:00 UTC (permalink / raw)
To: iustin.stolniceanu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, parthiban.veerasooran, hkallweit1, linux,
marcelo.schmitt, alvin.sipraga, ciprian.regus, netdev, devicetree,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] net: adin1110: NULL netdev_ops dereference in
adin1110_oa_tc6_probe()
- [Medium] In OA TC6 mode, the ADIN1110 hardware reset no longer goes
through the driver's own reset sequence in adin1110_check_spi().
- [Low] In adin1110_oa_tc6_probe() (drivers/net/ethernet/adi/adin1110.c),
every -EPROTO returned by oa_tc6_init() is turned into -ENODEV, and…
--
net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110
This adds OPEN Alliance TC6 SPI protocol support for the ADIN1110 using
the oa_tc6 framework, selected by the adi,spi-oa-tc6 property. Register
accesses go through new read_reg/write_reg callbacks, so both protocols
share the MAC configuration code. ADIN2111 is rejected in TC6 mode, and
strap/property mismatches are reported with -ENODEV.
> diff --git a/drivers/net/ethernet/adi/adin1110.c b/drivers/net/ethernet/adi/adin1110.c
> index 53fac3dc8be62..3f054565acb0e 100644
> --- a/drivers/net/ethernet/adi/adin1110.c
> +++ b/drivers/net/ethernet/adi/adin1110.c
[ ... ]
> @@ -1115,14 +1230,89 @@ static int adin1110_check_spi(struct adin1110_priv *priv)
[ ... ]
> +static int adin1110_oa_tc6_probe(struct adin1110_priv *priv)
> +{
[ ... ]
> + quirks.hwreset_settle_us = 50000;
> + quirks.softreset_settle_us = 10000;
> +
> + port_priv->tc6 = oa_tc6_init(priv->spidev, netdev, &quirks);
[Severity: High]
Isn't netdev->netdev_ops still NULL at this point?
devm_alloc_etherdev() leaves it unset. It is only assigned to
&adin1110_tc6_netdev_ops near the end of adin1110_oa_tc6_probe(),
after oa_tc6_init() has already returned.
quirks.quirk_flags does not set OA_TC6_BROKEN_PHY, so oa_tc6_init()
attaches the PHY to this netdev while it runs:
oa_tc6_init()
oa_tc6_phy_init()
phy_connect_direct()
phy_attach_direct()
phy_link_topo_add_phy()
netdev_need_ops_lock(dev)
With CONFIG_NET_SHAPER enabled, netdev_need_ops_lock() reads
dev->netdev_ops->net_shaper_ops. That would dereference the NULL
netdev_ops and oops on every probe in TC6 mode.
As far as I can tell, lan865x_probe() had the same ordering problem
with oa_tc6_init(). It now sets netdev_ops before calling into the
framework.
Can the netdev->netdev_ops assignment, and probably ethtool_ops with
it, be moved above oa_tc6_init()? None of the ndo callbacks should
be reachable before devm_register_netdev(), so moving the assignment
up looks safe.
[Severity: Medium]
In TC6 mode, adin1110_probe() branches to adin1110_oa_tc6_probe() before
adin1110_check_spi() runs, so oa_tc6_init() now does the hardware reset.
The existing reset in adin1110_check_spi() holds the SPI bus lock while
the configuration pins are latched:
drivers/net/ethernet/adi/adin1110.c:adin1110_check_spi() {
...
/* MISO pin is used for internal configuration, can't have
* anyone else disturbing the SDO line.
*/
spi_bus_lock(priv->spidev->controller);
gpiod_set_value(reset_gpio, 1);
fsleep(10000);
gpiod_set_value(reset_gpio, 0);
/* Need to wait 90 ms before interacting with
* the MAC after a HW reset.
*/
fsleep(90000);
spi_bus_unlock(priv->spidev->controller);
...
}
The reset in oa_tc6_init() requests the GPIO with GPIOD_OUT_HIGH and
does not lock the bus:
drivers/net/ethernet/oa_tc6.c:oa_tc6_init() {
...
if (tc6->reset_gpio) {
...
fsleep(10);
gpiod_set_value_cansleep(tc6->reset_gpio, 0);
fsleep(tc6->hwreset_settle_us);
}
...
}
On a shared SPI bus, can a transfer to another device drive MISO/SDO
while the ADIN1110 latches its configuration as it leaves reset? That
configuration includes the SPI_CFG1 protocol selection this path relies
on. If it latches wrong, the first control transfer would fail and probe
would report a strap/DT mismatch on a board that is strapped correctly.
The timings also differ from the generic SPI path. The reset is asserted
for 10 us instead of 10 ms, and the settle time is 50 ms where the
comment in adin1110_check_spi() asks for 90 ms. The oa_tc6 patch earlier
in this series cites a 50 ms datasheet limit. Which value is correct?
Should the comment in adin1110_check_spi() be updated, or should
hwreset_settle_us be 90 ms?
> + if (PTR_ERR_OR_ZERO(port_priv->tc6) == -EPROTO)
> + return dev_err_probe(dev, -ENODEV,
> + "no OA TC6 reply, check the SPI_CFG1 strap and adi,spi-oa-tc6\n");
[Severity: Low]
The commit message says:
If the first OA TC6 control transfer gets no valid reply, or the PHY ID
does not match, probe fails with -ENODEV and a message pointing at the
strap and the property.
Is this check limited to the first control transfer? In oa_tc6.c,
-EPROTO comes from the reply checkers that every control access uses:
drivers/net/ethernet/oa_tc6.c:oa_tc6_check_ctrl_write_reply() {
...
if (memcmp(tx_buf, rx_buf, size - OA_TC6_CTRL_IGNORED_SIZE))
return -EPROTO;
...
}
drivers/net/ethernet/oa_tc6.c:oa_tc6_check_ctrl_read_reply() {
...
if (*tx_buf != *rx_buf)
return -EPROTO;
...
if (complement != ~reply)
return -EPROTO;
...
}
oa_tc6_check_ctrl_protection() only succeeds if the chip answered in TC6
mode. After that, oa_tc6_init() still runs these steps:
oa_tc6_sw_reset_macphy()
oa_tc6_unmask_macphy_error_interrupts()
oa_tc6_phy_init()
oa_tc6_enable_data_transfer()
oa_tc6_update_buffer_status_from_register()
Each one returns its error unchanged as ERR_PTR(ret). A reply mismatch
in any of them would still be logged here as a strap/property problem,
with -ENODEV.
> + 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;
> +
> + ret = adin1110_read_reg(priv, ADIN1110_PHY_ID, &val);
> + if (ret < 0)
> + return ret;
The PHY ID read is handled differently. An -EPROTO from
adin1110_read_reg_tc6()->oa_tc6_read_register() is returned here as
-EPROTO rather than -ENODEV. Is that difference intended?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109085952.105331-1-iustin.stolniceanu%40analog.com
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-11-12 12:00 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-09 8:59 [PATCH net-next v2 0/7] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
2026-11-09 8:59 ` [PATCH net-next v2 1/7] net: ethernet: oa_tc6: Forward declare struct mii_bus Iustin Stolniceanu
2026-11-10 9:42 ` Alvin Šipraga
2026-11-09 8:59 ` [PATCH net-next v2 2/7] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
2026-11-09 17:10 ` Selvamani Rajagopal
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 4/7] net: phy: adin1100: Add separate driver entries for ADIN1110/ADIN2111 Iustin Stolniceanu
2026-11-10 9:46 ` Alvin Šipraga
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
2026-11-10 9:48 ` Alvin Šipraga
2026-11-12 12:00 ` netdev-bot+sashiko
2026-11-09 8:59 ` [PATCH net-next v2 6/7] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
2026-11-10 8:05 ` Krzysztof Kozlowski
2026-11-09 8:59 ` [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
2026-11-12 12:00 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).