mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
To: Andrew Lunn <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Parthiban Veerasooran <parthiban.veerasooran@microchip.com>,
	Heiner Kallweit <hkallweit1@gmail.com>,
	Russell King <linux@armlinux.org.uk>,
	Marcelo Schmitt <marcelo.schmitt@analog.com>
Cc: <netdev@vger.kernel.org>, <devicetree@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>,
	Ciprian Regus <ciprian.regus@analog.com>,
	Iustin Stolniceanu <iustin.stolniceanu@analog.com>
Subject: [PATCH net-next 2/5] net: ethernet: oa_tc6: Add support for MDIO indirect access
Date: Tue, 3 Nov 2026 18:09:15 +0200	[thread overview]
Message-ID: <20261103160918.409701-3-iustin.stolniceanu@analog.com> (raw)
In-Reply-To: <20261103160918.409701-1-iustin.stolniceanu@analog.com>

From: Ciprian Regus <ciprian.regus@analog.com>

The OPEN Alliance TC6 specification defines two ways for the SPI host
to access the registers of the PHY integrated in a MAC-PHY: direct
access, where the PHY registers are mapped into the MAC-PHY register
space, and indirect access, through the MDIOACC registers. A MAC-PHY
reports support for direct access through the DPRAC bit in the STDCAP
register.

The framework currently supports only direct access and fails to
register the MDIO bus when DPRAC is not set.

Read STDCAP when registering the MDIO bus and, if direct access is not
supported, implement the Clause 22 and Clause 45 MDIO operations as
indirect transactions through MDIOACC: for Clause 45, write the
address command to MDIOACC0 and the read or write command to
MDIOACC1; for Clause 22, write the command to MDIOACC1. Then poll
for TRDONE.

If TAERR (turnaround error) is set, no PHY answered at that address
and the DATA field must be ignored. Return 0xffff in that case, as
for an empty address on an MDIO bus, so that the PHY bus scan skips
the address instead of failing. Return -EIO for a write.

Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
Signed-off-by: Iustin Stolniceanu <iustin.stolniceanu@analog.com>
---
 drivers/net/ethernet/oa_tc6.c | 171 +++++++++++++++++++++++++++++++---
 include/linux/oa_tc6.h        |  15 +++
 2 files changed, 171 insertions(+), 15 deletions(-)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 68437b226528..1c67ea0ff22b 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -89,6 +89,7 @@ struct oa_tc6 {
 	bool int_flag;
 	bool disable_traffic;
 	bool prot_ctrl;
+	bool mdio_indirect_mode;
 	u32 reset_settle_us;
 	enum oa_tc6_quirk_flag quirk_flags;
 	struct gpio_desc *reset_gpio;
@@ -435,24 +436,149 @@ int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
 }
 EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);
 
-static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6)
+static void oa_tc6_handle_link_change(struct net_device *netdev)
 {
-	u32 regval;
+	phy_print_status(netdev->phydev);
+}
+
+static int oa_tc6_mdioacc_wait_done(struct oa_tc6 *tc6, u32 *regval)
+{
+	int ret, err;
+
+	ret = read_poll_timeout(oa_tc6_read_register, err,
+				err || (*regval & OA_TC6_MDIOACC_TRDONE),
+				100, 30000, false, tc6, OA_TC6_REG_MDIOACC(1),
+				regval);
+	if (ret)
+		return ret;
+
+	return err;
+}
+
+/* On a turnaround error no PHY answered and the DATA field must be ignored.
+ * Return all ones, like an MDIO bus with no PHY at that address, so that
+ * bus scanning skips the address instead of failing.
+ */
+static int oa_tc6_mdioacc_read_data(u32 reg_val)
+{
+	if (reg_val & OA_TC6_MDIOACC_TAERR)
+		return 0xffff;
+
+	return FIELD_GET(OA_TC6_MDIOACC_DATA, reg_val);
+}
+
+static int oa_tc6_mdioacc_write_done(struct oa_tc6 *tc6)
+{
+	u32 reg_val;
 	int ret;
 
-	ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, &regval);
+	ret = oa_tc6_mdioacc_wait_done(tc6, &reg_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, &reg_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, &reg_val);
+	if (ret)
+		return ret;
+
+	return oa_tc6_mdioacc_read_data(reg_val);
+}
+
+static int oa_tc6_indirect_mdiobus_write(struct oa_tc6 *tc6, int addr,
+					 int regnum, u16 val)
+{
+	u32 reg_val;
+	int ret;
+
+	reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_WR);
+	reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C22);
+	reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr);
+	reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, regnum);
+	reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, val);
+
+	ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val);
+	if (ret)
+		return ret;
+
+	return oa_tc6_mdioacc_write_done(tc6);
 }
 
 static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
@@ -461,6 +587,9 @@ static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
 	u32 regval;
 	int ret;
 
+	if (tc6->mdio_indirect_mode)
+		return oa_tc6_indirect_mdiobus_read(tc6, addr, regnum);
+
 	ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
 				   (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
 				   &regval);
@@ -475,6 +604,9 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
 {
 	struct oa_tc6 *tc6 = bus->priv;
 
+	if (tc6->mdio_indirect_mode)
+		return oa_tc6_indirect_mdiobus_write(tc6, addr, regnum, val);
+
 	return oa_tc6_write_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
 				     (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
 				     val);
@@ -506,6 +638,10 @@ int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
 	int mms;
 	int ret;
 
+	if (tc6->mdio_indirect_mode)
+		return oa_tc6_indirect_mdiobus_read_c45(tc6, addr, devnum,
+							regnum);
+
 	mms = oa_tc6_get_phy_c45_mms(devnum);
 	if (mms < 0)
 		return mms;
@@ -524,6 +660,10 @@ int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
 	struct oa_tc6 *tc6 = bus->priv;
 	int mms;
 
+	if (tc6->mdio_indirect_mode)
+		return oa_tc6_indirect_mdiobus_write_c45(tc6, addr, devnum,
+							 regnum, val);
+
 	mms = oa_tc6_get_phy_c45_mms(devnum);
 	if (mms < 0)
 		return mms;
@@ -534,8 +674,16 @@ EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45);
 
 static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
 {
+	u32 regval;
 	int ret;
 
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, &regval);
+	if (ret)
+		return ret;
+
+	tc6->mdio_indirect_mode = !(OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS &
+				    regval);
+
 	tc6->mdiobus = mdiobus_alloc();
 	if (!tc6->mdiobus) {
 		netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
@@ -586,13 +734,6 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
 	if (tc6->quirk_flags & OA_TC6_BROKEN_PHY)
 		return 0;
 
-	ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
-	if (ret) {
-		netdev_err(tc6->netdev,
-			   "Direct PHY register access is not supported by the MAC-PHY\n");
-		return ret;
-	}
-
 	ret = oa_tc6_mdiobus_register(tc6);
 	if (ret)
 		return ret;
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 139dd4c33950..e0627b99e569 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -50,6 +50,21 @@
 #define OA_TC6_INT_MASK0_ALL_INTERRUPTS                 (GENMASK(5, 0) | \
 							 GENMASK(12, 7))
 
+#define OA_TC6_REG_MDIOACC(x)	(0x20 + (x))
+#define OA_TC6_MDIOACC_TRDONE	BIT(31)
+#define OA_TC6_MDIOACC_TAERR	BIT(30)
+#define OA_TC6_MDIOACC_ST	GENMASK(29, 28)
+#define OA_TC6_MDIOACC_OP	GENMASK(27, 26)
+#define OA_TC6_MDIOACC_PRTAD	GENMASK(25, 21)
+#define OA_TC6_MDIOACC_DEVAD	GENMASK(20, 16)
+#define OA_TC6_MDIOACC_DATA	GENMASK(15, 0)
+
+#define OA_TC6_MDIOACC_ST_C45		0x0
+#define OA_TC6_MDIOACC_ST_C22		0x1
+#define OA_TC6_MDIOACC_OP_ADDR		0x0
+#define OA_TC6_MDIOACC_OP_WR		0x1
+#define OA_TC6_MDIOACC_OP_RD		0x3
+
 /* PHY Clause 22 registers base address and mask */
 #define OA_TC6_PHY_STD_REG_ADDR_BASE		0xFF00
 #define OA_TC6_PHY_STD_REG_ADDR_MASK		0x1F
-- 
2.43.0


  parent reply	other threads:[~2026-11-03 16:10 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 16:09 [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Iustin Stolniceanu
2026-11-03 16:09 ` [PATCH net-next 1/5] net: ethernet: oa_tc6: Allow custom reset settle time values Iustin Stolniceanu
2026-11-03 16:09 ` Iustin Stolniceanu [this message]
2026-11-03 16:09 ` [PATCH net-next 3/5] dt-bindings: net: adi,adin1110: Add adi,spi-oa-tc6 Iustin Stolniceanu
2026-11-03 16:09 ` [PATCH net-next 4/5] net: ethernet: adi: adin1110: Add OA TC6 protocol support for ADIN1110 Iustin Stolniceanu
2026-11-03 16:09 ` [PATCH net-next 5/5] net: phy: adin1100: Skip the soft reset on ADIN1110/ADIN2111 Iustin Stolniceanu
2026-11-03 20:49 ` [PATCH net-next 0/5] net: ethernet: adi: adin1110: Add OPEN Alliance TC6 support Andrew Lunn
2026-11-03 20:54 ` Andrew Lunn

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261103160918.409701-3-iustin.stolniceanu@analog.com \
    --to=iustin.stolniceanu@analog.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=ciprian.regus@analog.com \
    --cc=conor+dt@kernel.org \
    --cc=davem@davemloft.net \
    --cc=devicetree@vger.kernel.org \
    --cc=edumazet@kernel.org \
    --cc=hkallweit1@gmail.com \
    --cc=krzk+dt@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=marcelo.schmitt@analog.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=parthiban.veerasooran@microchip.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®