From: Petr Wozniak <petr.wozniak@gmail.com>
To: netdev@vger.kernel.org
Cc: maxime.chevallier@bootlin.com, bjorn@mork.no, andrew@lunn.ch,
linux-phy@lists.infradead.org, kuba@kernel.org
Subject: [PATCH net-next v5] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge in mdio-i2c
Date: Tue, 19 May 2026 18:20:07 +0200 [thread overview]
Message-ID: <20260519162007.13635-1-petr.wozniak@gmail.com> (raw)
The "OEM"/"SFP-10G-T" quirk entry in sfp_fixup_rollball_cc()
unconditionally forces MDIO_I2C_ROLLBALL for all modules matching that
vendor/part-number combination. This works for modules that genuinely
implement a RollBall I2C-to-MDIO bridge, but silently breaks modules
that share the same EEPROM strings without having such a bridge.
The Realtek RTL8261BE-CG is one such module: a pure copper 10G SFP+
media converter with no I2C-to-MDIO bridge. Its EEPROM reports
vendor="OEM", part="SFP-10G-T-I", and -- critically -- Vendor OUI
00:00:00, making OUI-based differentiation impossible. With
MDIO_I2C_ROLLBALL forced, the module silently ACKs the unlock password
write, the MDIO bus is created, but no PHY responds; the SFP state
machine cycles through the RollBall PHY-probe retry window before
reporting no PHY.
Move the probe into i2c_mii_init_rollball() in mdio-i2c.c, where the
RollBall protocol constants are already defined. After sending the
unlock password, issue a CMD_READ and wait ~70 ms for CMD_DONE. A
genuine RollBall bridge asserts CMD_DONE within that window; modules
without a bridge never do, so i2c_mii_init_rollball() returns -ENODEV.
mdio_i2c_alloc() propagates -ENODEV to the caller to signal that no
bridge is present and PHY probing should be skipped.
sfp_sm_add_mdio_bus() catches -ENODEV and transitions
sfp->mdio_protocol to MDIO_I2C_NONE so the rest of the state machine
skips PHY probing for this module.
Add "OEM"/"SFP-10G-T-I" to the quirk table so RTL8261BE modules enter
the probe path; genuine RollBall modules continue to work as before.
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Changes since v4 (feedback from Maxime Chevallier):
- Fix commit message: replace "stalls" with accurate description of
the RollBall PHY-probe retry window
- Fix variable declaration order in i2c_mii_probe_rollball() to
follow reverse-xmas tree (descending line length)
- Remove spurious alignment space on "SFP-10G-T" quirk entry
- Document that -ENODEV from mdio_i2c_alloc() means no bridge present,
PHY probing should be skipped
Changes since v3 (feedback from Jakub Kicinski):
- Drop spurious Tested-by: tag -- author and tester are the same person
- Use PATCH net-next subject prefix
- Move -ENODEV handling from sfp_i2c_mdiobus_create() into
sfp_sm_add_mdio_bus() so bus-creation code does not mutate
sfp->mdio_protocol; the state machine is the correct place for
protocol-state transitions
- Split combined variable declaration for clarity
Changes since v2:
- Compile-tested and hardware-tested on BPI-R4 (MT7988A, 6.12.87)
- RTL8261BE (OEM/SFP-10G-T-I): probes MDIO_I2C_NONE, link Up 10Gbps
- Genuine RollBall (OEM/SFP-10G-T): bridge detected, link Up 10Gbps
drivers/net/mdio/mdio-i2c.c | 60 +++++++++++++++++++++++++++++++-----
drivers/net/phy/sfp.c | 16 ++++++++--
2 files changed, 65 insertions(+), 11 deletions(-)
--- a/drivers/net/mdio/mdio-i2c.c
+++ b/drivers/net/mdio/mdio-i2c.c
@@ -419,6 +419,46 @@
return 0;
}
+static int i2c_mii_probe_rollball(struct i2c_adapter *i2c)
+{
+ u8 data_buf[] = { ROLLBALL_DATA_ADDR, 0x01, 0x00, 0x00 };
+ u8 cmd_buf[] = { ROLLBALL_CMD_ADDR, ROLLBALL_CMD_READ };
+ u8 cmd_addr = ROLLBALL_CMD_ADDR;
+ struct i2c_msg msgs[2];
+ u8 result;
+ int ret;
+
+ msgs[0].addr = ROLLBALL_PHY_I2C_ADDR;
+ msgs[0].flags = 0;
+ msgs[0].len = sizeof(data_buf);
+ msgs[0].buf = data_buf;
+ msgs[1].addr = ROLLBALL_PHY_I2C_ADDR;
+ msgs[1].flags = 0;
+ msgs[1].len = sizeof(cmd_buf);
+ msgs[1].buf = cmd_buf;
+
+ ret = i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs));
+ if (ret)
+ return ret;
+
+ msleep(70);
+
+ msgs[0].addr = ROLLBALL_PHY_I2C_ADDR;
+ msgs[0].flags = 0;
+ msgs[0].len = 1;
+ msgs[0].buf = &cmd_addr;
+ msgs[1].addr = ROLLBALL_PHY_I2C_ADDR;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].len = 1;
+ msgs[1].buf = &result;
+
+ ret = i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs));
+ if (ret)
+ return ret;
+
+ /* -ENODEV: CMD_DONE not asserted — no RollBall bridge present */
+ return result == ROLLBALL_CMD_DONE ? 0 : -ENODEV;
+}
+
static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
{
struct i2c_msg msg;
@@ -439,10 +479,10 @@
ret = i2c_transfer(i2c, &msg, 1);
if (ret < 0)
return ret;
- else if (ret != 1)
+ if (ret != 1)
return -EIO;
- else
- return 0;
+
+ return i2c_mii_probe_rollball(i2c);
}
static bool mdio_i2c_check_functionality(struct i2c_adapter *i2c,
@@ -487,9 +527,13 @@
case MDIO_I2C_ROLLBALL:
ret = i2c_mii_init_rollball(i2c);
if (ret < 0) {
- dev_err(parent,
- "Cannot initialize RollBall MDIO I2C protocol: %d\n",
- ret);
+ 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. */
mdiobus_free(mii);
return ERR_PTR(ret);
}
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -579,7 +579,8 @@
// OEM SFP-GE-T is a 1000Base-T module with broken TX_FAULT indicator
SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault),
- SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
+ SFP_QUIRK_F("OEM", "SFP-10G-T-I", sfp_fixup_rollball),
+ SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
SFP_QUIRK_S("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-D", sfp_quirk_2500basex),
SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex),
@@ -2022,10 +2023,18 @@
static int sfp_sm_add_mdio_bus(struct sfp *sfp)
{
- if (sfp->mdio_protocol != MDIO_I2C_NONE)
- return sfp_i2c_mdiobus_create(sfp);
+ int ret;
- return 0;
+ 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;
}
/* Probe a SFP for a PHY device if the module supports copper - the PHY
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
next reply other threads:[~2026-05-19 16:20 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-19 16:20 Petr Wozniak [this message]
2026-05-20 21:19 ` [PATCH net-next v5] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge in mdio-i2c Jan Hoffmann
[not found] <20260519162007.12345-1-petr.wozniak@gmail.com>
2026-05-21 3:12 ` Petr Wozniak
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=20260519162007.13635-1-petr.wozniak@gmail.com \
--to=petr.wozniak@gmail.com \
--cc=andrew@lunn.ch \
--cc=bjorn@mork.no \
--cc=kuba@kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=maxime.chevallier@bootlin.com \
--cc=netdev@vger.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