Netdev List
 help / color / mirror / Atom feed
From: Petr Wozniak <petr.wozniak@gmail.com>
To: netdev@vger.kernel.org
Cc: bjorn@mork.no, andrew@lunn.ch, linux-phy@lists.infradead.org,
	Petr Wozniak <petr.wozniak@gmail.com>
Subject: [PATCH] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge before assuming MDIO_I2C_ROLLBALL
Date: Fri, 15 May 2026 14:11:08 +0200	[thread overview]
Message-ID: <20260515121108.17792-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", and -- critically -- Vendor OUI
00:00:00, making OUI-based differentiation impossible. With
MDIO_I2C_ROLLBALL the kernel stalls waiting for a PHY that never
appears:

  sfp sfp2: probing phy device through the [MDIO_I2C_ROLLBALL] protocol
  sfp sfp2: no PHY detected, 24 tries left
  sfp sfp2: no PHY detected, 23 tries left
  ...

Fix this by probing for the RollBall bridge before committing to a
protocol. The probe sends the RollBall unlock password (four 0xFF bytes
to A2h:VSL+3), switches to MDIO page 3, issues CMD_READ, and polls for
CMD_DONE. A genuine RollBall bridge responds with CMD_DONE within ~70
ms. A module without a bridge never asserts it; the probe times out
after 200 ms (10 x 20 ms).

On probe success the existing MDIO_I2C_ROLLBALL + extended_cc path is
taken -- no behaviour change for real RollBall modules. On timeout,
MDIO_I2C_NONE is selected, telling the MAC to derive link parameters
directly from the EEPROM-declared interface type (10gbase-r) without
attempting PHY register access.

The probe adds at most 200 ms at link-up time for the no-bridge case,
and ~70 ms for real RollBall modules. This is a one-shot cost, not on
the data path.

Also add a separate quirk entry for the industrial variant "SFP-10G-T-I"
which uses the same RTL8261BE silicon.

Tested on BPI-R4 (MediaTek MT7988A, Linux 6.12):
 - RTL8261BE negative case (no bridge, probe timeout -> MDIO_I2C_NONE):
   link up 10Gbps, iperf3 9.34 Gbit/s (93% line rate) [OK]
 - RollBall positive case (CMD_DONE -> MDIO_I2C_ROLLBALL): logic mirrors
   the unlock sequence already in mdio-i2c.c; not yet verified on
   physical RollBall hardware due to lack of a suitable test module.

Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
---
 drivers/net/phy/sfp.c | 79 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 73 insertions(+), 6 deletions(-)

diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index XXXXXXX..XXXXXXX 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -960,11 +960,88 @@ static void sfp_fixup_rollball(struct sfp *sfp)
 	sfp->phy_t_retry = msecs_to_jiffies(1000);
 }
 
-static void sfp_fixup_rollball_cc(struct sfp *sfp)
+/* Local mirrors of RollBall protocol constants from mdio-i2c.c */
+#define SFP_ROLLBALL_PHY_ADDR	0x51
+#define SFP_ROLLBALL_MDIO_PAGE	3
+#define SFP_ROLLBALL_CMD_ADDR	0x80
+#define SFP_ROLLBALL_CMD_READ	0x02
+#define SFP_ROLLBALL_CMD_DONE	0x04
+
+static int sfp_rollball_a2_write(struct sfp *sfp, u8 reg,
+				 const u8 *data, int len)
+{
+	struct i2c_msg msg;
+	u8 buf[8];
+
+	buf[0] = reg;
+	memcpy(buf + 1, data, len);
+	msg.addr  = SFP_ROLLBALL_PHY_ADDR;
+	msg.flags = 0;
+	msg.len   = len + 1;
+	msg.buf   = buf;
+	return i2c_transfer(sfp->i2c, &msg, 1) == 1 ? 0 : -EIO;
+}
+
+static int sfp_rollball_a2_read(struct sfp *sfp, u8 reg, u8 *val)
 {
-	sfp_fixup_rollball(sfp);
+	struct i2c_msg msgs[2];
+
+	msgs[0].addr  = SFP_ROLLBALL_PHY_ADDR;
+	msgs[0].flags = 0;
+	msgs[0].len   = 1;
+	msgs[0].buf   = &reg;
+	msgs[1].addr  = SFP_ROLLBALL_PHY_ADDR;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len   = 1;
+	msgs[1].buf   = val;
+	return i2c_transfer(sfp->i2c, msgs, 2) == 2 ? 0 : -EIO;
+}
+
+/**
+ * sfp_has_rollball_bridge() - probe for a RollBall I2C-to-MDIO bridge
+ * @sfp: SFP instance
+ *
+ * Send the RollBall unlock password, switch to the MDIO page, issue CMD_READ
+ * and poll for CMD_DONE. A genuine RollBall bridge asserts CMD_DONE within
+ * ~70 ms. Modules without a bridge (e.g. RTL8261BE pure media converter)
+ * never respond; the poll times out after 200 ms.
+ *
+ * Returns true if a RollBall bridge is present, false otherwise.
+ */
+static bool sfp_has_rollball_bridge(struct sfp *sfp)
+{
+	u8 pw[4] = { 0xff, 0xff, 0xff, 0xff };
+	u8 page = SFP_ROLLBALL_MDIO_PAGE;
+	u8 cmd  = SFP_ROLLBALL_CMD_READ;
+	u8 saved_page = 0, res;
+	int i;
+
+	if (sfp_rollball_a2_write(sfp, SFP_VSL + 3, pw, sizeof(pw)) < 0)
+		return false;
+
+	sfp_rollball_a2_read(sfp, SFP_PAGE, &saved_page);
+
+	if (sfp_rollball_a2_write(sfp, SFP_PAGE, &page, 1) < 0 ||
+	    sfp_rollball_a2_write(sfp, SFP_ROLLBALL_CMD_ADDR, &cmd, 1) < 0)
+		goto restore;
+
+	for (i = 0; i < 10; i++) {
+		msleep(20);
+		if (!sfp_rollball_a2_read(sfp, SFP_ROLLBALL_CMD_ADDR, &res) &&
+		    res == SFP_ROLLBALL_CMD_DONE) {
+			sfp_rollball_a2_write(sfp, SFP_PAGE, &saved_page, 1);
+			return true;
+		}
+	}
+
+restore:
+	sfp_rollball_a2_write(sfp, SFP_PAGE, &saved_page, 1);
+	return false;
+}
+
+static void sfp_fixup_rollball_cc(struct sfp *sfp)
 {
+	/* Probe for an I2C-to-MDIO bridge: genuine RollBall modules assert
+	 * CMD_DONE within ~70 ms; pure media converters such as the RTL8261BE
+	 * have no bridge and time out after 200 ms.
+	 */
+	if (!sfp_has_rollball_bridge(sfp)) {
+		sfp->mdio_protocol = MDIO_I2C_NONE;
+		return;
+	}
+	sfp_fixup_rollball(sfp);
 	/* Some RollBall SFPs may have wrong (zero) extended compliance code
 	 * burned in EEPROM. For PHY probing we need the correct one.
 	 */
@@ -1036,7 +1113,8 @@ static const struct sfp_quirk sfp_quirks[] = {
 	SFP_QUIRK_S("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault),
 	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_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
+	SFP_QUIRK_F("OEM", "SFP-10G-T-I", sfp_fixup_rollball_cc),
+	SFP_QUIRK_F("OEM", "SFP-10G-T",   sfp_fixup_rollball_cc),
 	SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex),

             reply	other threads:[~2026-05-15 12:11 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-15 12:11 Petr Wozniak [this message]
2026-05-15 12:15 ` [PATCH] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge before assuming MDIO_I2C_ROLLBALL 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=20260515121108.17792-1-petr.wozniak@gmail.com \
    --to=petr.wozniak@gmail.com \
    --cc=andrew@lunn.ch \
    --cc=bjorn@mork.no \
    --cc=linux-phy@lists.infradead.org \
    --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