From: Jakub Kicinski <kuba@kernel.org>
To: Petr Wozniak <petr.wozniak@gmail.com>
Cc: netdev@vger.kernel.org, bjorn@mork.no, andrew@lunn.ch,
linux-phy@lists.infradead.org
Subject: Re: [PATCH v3] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge in mdio-i2c
Date: Mon, 18 May 2026 17:10:26 -0700 [thread overview]
Message-ID: <20260518171026.4f2f0c20@kernel.org> (raw)
In-Reply-To: <20260515174044.26036-1-petr.wozniak@gmail.com>
On Fri, 15 May 2026 19:40:44 +0200 Petr Wozniak wrote:
> 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 the kernel stalls waiting for a PHY that never
> appears:
>
> sfp sfp2: probing phy device through the [MDIO_I2C_ROLLBALL] protocol
>
> 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.
> sfp_i2c_mdiobus_create() treats -ENODEV as no PHY and falls back to
> MDIO_I2C_NONE without creating an MDIO bus.
>
> 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>
> Tested-by: Petr Wozniak <petr.wozniak@gmail.com>
Tested-by tag is for when the person testing is different than the
author of the patch. Please drop.
> Targeting: net-next
This usually means --subject-prefix="PATCH net-next vN"
But more importantly, this patch does not apply to netdev/net-next
> 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 | 59 +++++++++++++++++++++++++++++++++----
> drivers/net/phy/sfp.c | 8 ++++-
> 2 files changed, 60 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/net/mdio/mdio-i2c.c b/drivers/net/mdio/mdio-i2c.c
> index da2001e..1973fda 100644
> --- a/drivers/net/mdio/mdio-i2c.c
> +++ b/drivers/net/mdio/mdio-i2c.c
> @@ -352,6 +352,52 @@ static int i2c_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad,
> return 0;
> }
>
> +/*
> + * Probe for a RollBall I2C-to-MDIO bridge by issuing CMD_READ and waiting
> + * ~70 ms for CMD_DONE. Genuine RollBall bridges respond within that window.
> + * Modules that share the same EEPROM vendor/part strings but have no bridge
> + * (e.g. RTL8261BE pure copper media converter) never assert CMD_DONE, so
> + * -ENODEV is returned for them.
> + */
> +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, result;
Is this code doing some magic or you're assigning to values to one
integer?
> + struct i2c_msg msgs[2];
> + 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;
> +
> + return result == ROLLBALL_CMD_DONE ? 0 : -ENODEV;
> +}
> +
> static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
> {
> struct i2c_msg msg;
> @@ -372,10 +418,10 @@ static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
> 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);
> }
>
> struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
> @@ -399,9 +445,10 @@ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
> 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);
> mdiobus_free(mii);
> return ERR_PTR(ret);
> }
> diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
> index 218c775..ce745b6 100644
> --- a/drivers/net/phy/sfp.c
> +++ b/drivers/net/phy/sfp.c
> @@ -599,7 +599,8 @@ static const struct sfp_quirk sfp_quirks[] = {
> SFP_QUIRK_S("TP-LINK", "TL-SM410U", sfp_quirk_oem_2_5g),
>
> SFP_QUIRK_F("ETU", "ESP-T5-R", sfp_fixup_rollball_cc),
> - 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),
> @@ -802,6 +803,11 @@ static int sfp_i2c_mdiobus_create(struct sfp *sfp)
> int ret;
>
> i2c_mii = mdio_i2c_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
> + if (PTR_ERR_OR_ZERO(i2c_mii) == -ENODEV) {
> + /* RollBall probe found no bridge: no PHY on this module */
> + sfp->mdio_protocol = MDIO_I2C_NONE;
Uh, oh. Hope someone with SFP expertise will review v4.
> + return 0;
> + }
> if (IS_ERR(i2c_mii))
> return PTR_ERR(i2c_mii);
>
--
pw-bot: cr
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
WARNING: multiple messages have this Message-ID (diff)
From: Jakub Kicinski <kuba@kernel.org>
To: Petr Wozniak <petr.wozniak@gmail.com>
Cc: netdev@vger.kernel.org, bjorn@mork.no, andrew@lunn.ch,
linux-phy@lists.infradead.org
Subject: Re: [PATCH v3] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge in mdio-i2c
Date: Mon, 18 May 2026 17:10:26 -0700 [thread overview]
Message-ID: <20260518171026.4f2f0c20@kernel.org> (raw)
In-Reply-To: <20260515174044.26036-1-petr.wozniak@gmail.com>
On Fri, 15 May 2026 19:40:44 +0200 Petr Wozniak wrote:
> 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 the kernel stalls waiting for a PHY that never
> appears:
>
> sfp sfp2: probing phy device through the [MDIO_I2C_ROLLBALL] protocol
>
> 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.
> sfp_i2c_mdiobus_create() treats -ENODEV as no PHY and falls back to
> MDIO_I2C_NONE without creating an MDIO bus.
>
> 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>
> Tested-by: Petr Wozniak <petr.wozniak@gmail.com>
Tested-by tag is for when the person testing is different than the
author of the patch. Please drop.
> Targeting: net-next
This usually means --subject-prefix="PATCH net-next vN"
But more importantly, this patch does not apply to netdev/net-next
> 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 | 59 +++++++++++++++++++++++++++++++++----
> drivers/net/phy/sfp.c | 8 ++++-
> 2 files changed, 60 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/net/mdio/mdio-i2c.c b/drivers/net/mdio/mdio-i2c.c
> index da2001e..1973fda 100644
> --- a/drivers/net/mdio/mdio-i2c.c
> +++ b/drivers/net/mdio/mdio-i2c.c
> @@ -352,6 +352,52 @@ static int i2c_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad,
> return 0;
> }
>
> +/*
> + * Probe for a RollBall I2C-to-MDIO bridge by issuing CMD_READ and waiting
> + * ~70 ms for CMD_DONE. Genuine RollBall bridges respond within that window.
> + * Modules that share the same EEPROM vendor/part strings but have no bridge
> + * (e.g. RTL8261BE pure copper media converter) never assert CMD_DONE, so
> + * -ENODEV is returned for them.
> + */
> +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, result;
Is this code doing some magic or you're assigning to values to one
integer?
> + struct i2c_msg msgs[2];
> + 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;
> +
> + return result == ROLLBALL_CMD_DONE ? 0 : -ENODEV;
> +}
> +
> static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
> {
> struct i2c_msg msg;
> @@ -372,10 +418,10 @@ static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
> 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);
> }
>
> struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
> @@ -399,9 +445,10 @@ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
> 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);
> mdiobus_free(mii);
> return ERR_PTR(ret);
> }
> diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
> index 218c775..ce745b6 100644
> --- a/drivers/net/phy/sfp.c
> +++ b/drivers/net/phy/sfp.c
> @@ -599,7 +599,8 @@ static const struct sfp_quirk sfp_quirks[] = {
> SFP_QUIRK_S("TP-LINK", "TL-SM410U", sfp_quirk_oem_2_5g),
>
> SFP_QUIRK_F("ETU", "ESP-T5-R", sfp_fixup_rollball_cc),
> - 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),
> @@ -802,6 +803,11 @@ static int sfp_i2c_mdiobus_create(struct sfp *sfp)
> int ret;
>
> i2c_mii = mdio_i2c_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
> + if (PTR_ERR_OR_ZERO(i2c_mii) == -ENODEV) {
> + /* RollBall probe found no bridge: no PHY on this module */
> + sfp->mdio_protocol = MDIO_I2C_NONE;
Uh, oh. Hope someone with SFP expertise will review v4.
> + return 0;
> + }
> if (IS_ERR(i2c_mii))
> return PTR_ERR(i2c_mii);
>
--
pw-bot: cr
next prev parent reply other threads:[~2026-05-19 0:10 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-15 17:40 [PATCH v3] net: phy: sfp: probe for RollBall I2C-to-MDIO bridge in mdio-i2c Petr Wozniak
2026-05-15 17:40 ` Petr Wozniak
2026-05-19 0:10 ` Jakub Kicinski [this message]
2026-05-19 0:10 ` Jakub Kicinski
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=20260518171026.4f2f0c20@kernel.org \
--to=kuba@kernel.org \
--cc=andrew@lunn.ch \
--cc=bjorn@mork.no \
--cc=linux-phy@lists.infradead.org \
--cc=netdev@vger.kernel.org \
--cc=petr.wozniak@gmail.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.