[PATCH net-next] net: phy: mdio-i2c: defer RollBall bridge probe to PHY discovery

From: Petr Wozniak

Date: Thu Jun 04 2026 - 14:12:37 EST


commit 8fe125892f40 ("net: phy: sfp: probe for RollBall I2C-to-MDIO
bridge in mdio-i2c") introduced a regression: for SFP modules inserted
before system boot, the RollBall I2C-to-MDIO bridge is not yet ready to
respond to CMD_READ/CMD_DONE cycles when sfp_sm_add_mdio_bus() runs.
The 200 ms probe times out, i2c_mii_probe_rollball() returns -ENODEV,
and sfp_sm_add_mdio_bus() sets mdio_protocol = MDIO_I2C_NONE. By the
time sfp_sm_probe_for_phy() runs (up to ~17 s later on affected
hardware), the bridge is fully initialized — but PHY probing is skipped
because the protocol has already been changed to NONE.

Move the probe from i2c_mii_init_rollball() — called at bus-creation
time — to sfp_sm_probe_for_phy() in sfp.c, where it runs after the SFP
state machine's module initialization delays. Export the probe function
as mdio_i2c_probe_rollball() so sfp.c can call it.

For RTL8261BE-based modules: the probe correctly returns -ENODEV at PHY
discovery time, causing sfp_sm_probe_for_phy() to destroy the MDIO bus
and set MDIO_I2C_NONE — eliminating the 5+ minute PHY probe retry loop.

For genuine RollBall modules inserted before boot (e.g. FLYPRO
SFP-10GT-CS-30M with Aquantia AQR113C): the probe now runs after
initialization is complete and correctly returns 0 — PHY detection
proceeds normally.

Reported-by: Aleksander Bajkowski <olek2@xxxxx>
Fixes: 8fe125892f40 ("net: phy: sfp: probe for RollBall I2C-to-MDIO bridge in mdio-i2c")
Signed-off-by: Petr Wozniak <petr.wozniak@xxxxxxxxx>
---
drivers/net/mdio/mdio-i2c.c | 18 ++++--------------
drivers/net/phy/sfp.c | 18 ++++++++++++------
include/linux/mdio/mdio-i2c.h | 1 +
3 files changed, 17 insertions(+), 20 deletions(-)

diff --git a/drivers/net/mdio/mdio-i2c.c b/drivers/net/mdio/mdio-i2c.c
--- a/drivers/net/mdio/mdio-i2c.c
+++ b/drivers/net/mdio/mdio-i2c.c
@@ -352,7 +352,7 @@
return 0;
}

-static int i2c_mii_probe_rollball(struct i2c_adapter *i2c)
+int mdio_i2c_probe_rollball(struct i2c_adapter *i2c)
{
u8 data_buf[] = { ROLLBALL_DATA_ADDR, 0x01, 0x00, 0x00 };
u8 cmd_buf[] = { ROLLBALL_CMD_ADDR, ROLLBALL_CMD_READ };
@@ -397,9 +397,11 @@

return -ENODEV;
}
+EXPORT_SYMBOL_GPL(mdio_i2c_probe_rollball);

static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
{
+ /* Send the RollBall unlock password; bridge presence is verified
+ * later, in sfp_sm_probe_for_phy(), after module initialization. */
struct i2c_msg msg;
u8 pw[5];
int ret;
@@ -419,7 +421,7 @@
if (ret != 1)
return -EIO;

- return i2c_mii_probe_rollball(i2c);
+ return 0;
}

struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
@@ -444,12 +446,8 @@
case MDIO_I2C_ROLLBALL:
ret = i2c_mii_init_rollball(i2c);
if (ret < 0) {
- if (ret != -ENODEV)
- dev_err(parent,
- "Cannot initialize RollBall MDIO I2C protocol: %d\n",
- ret);
- /* -ENODEV propagates to caller: no bridge present,
- * PHY probing should be skipped for this module. */
+ dev_err(parent,
+ "Cannot initialize RollBall MDIO I2C protocol: %d\n",
+ ret);
mdiobus_free(mii);
return ERR_PTR(ret);
}
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -2028,17 +2028,8 @@ static int sfp_sm_add_mdio_bus(struct sfp *sfp)
dev_info(sfp->dev, "probing phy device through the [%s] protocol\n",
mdio_i2c_proto_type(sfp->mdio_protocol));

- int ret;
-
if (sfp->mdio_protocol == MDIO_I2C_NONE)
return 0;

- ret = sfp_i2c_mdiobus_create(sfp);
- if (ret == -ENODEV) {
- /* Probe confirmed no bridge present; skip PHY discovery. */
- sfp->mdio_protocol = MDIO_I2C_NONE;
- return 0;
- }
- return ret;
+ return sfp_i2c_mdiobus_create(sfp);
}

/* Probe a SFP for a PHY device if the module supports copper - the PHY
@@ -2058,8 +2049,23 @@ static int sfp_sm_probe_for_phy(struct sfp *sfp)

case MDIO_I2C_ROLLBALL:
- err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR_ROLLBALL, true);
+ /* Probe here, after module initialization delays, so that
+ * genuine RollBall bridges have had time to start up.
+ * Modules without a bridge (e.g. RTL8261BE) return -ENODEV. */
+ err = mdio_i2c_probe_rollball(sfp->i2c);
+ if (err == -ENODEV) {
+ sfp_i2c_mdiobus_destroy(sfp);
+ sfp->mdio_protocol = MDIO_I2C_NONE;
+ break;
+ }
+ if (!err)
+ err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR_ROLLBALL, true);
break;
}
diff --git a/include/linux/mdio/mdio-i2c.h b/include/linux/mdio/mdio-i2c.h
--- a/include/linux/mdio/mdio-i2c.h
+++ b/include/linux/mdio/mdio-i2c.h
@@ -33,5 +33,6 @@

struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
enum mdio_i2c_proto protocol);
+int mdio_i2c_probe_rollball(struct i2c_adapter *i2c);

#endif
--
2.51.0