* [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; 8+ 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] 8+ 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-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, 0 replies; 8+ 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 [flat|nested] 8+ 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 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, 0 replies; 8+ 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 [flat|nested] 8+ 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-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, 0 replies; 8+ 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 [flat|nested] 8+ 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-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, 0 replies; 8+ 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 [flat|nested] 8+ 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-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, 0 replies; 8+ 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 [flat|nested] 8+ 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-09 8:59 ` [PATCH net-next v2 7/7] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
6 siblings, 0 replies; 8+ 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 [flat|nested] 8+ 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
6 siblings, 0 replies; 8+ 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 [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-09 9:02 UTC | newest]
Thread overview: 8+ 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-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 ` [PATCH net-next v2 3/7] net: ethernet: oa_tc6: Add support for MDIO indirect access Iustin Stolniceanu
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 ` [PATCH net-next v2 5/7] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®