Supporting SGMII to 100BaseFX SFP modules, with broadcom PHYs

Maxime Chevallier
2025-06-24T21:39:31Z

Hello everyone,

I'm reaching out to discuss an issue I've been facing with some SFP modules
that do SGMII to 100FX conversion.

I'm using that on a product that has 1G-only SFP cage, where SGMII or 1000BaseX
are the only options, and that product needs to talk to a 100FX link partner.

The only way this can ever work is with a media-converter PHY within the SFP,
and apparently such SFP exist :

https://www.fs.com/fr/products/37770.html?attribute=19567&id=335755

I've tried various SFP modules from FS, Prolabs and Phoenix Contact with
no luck. All these modules seem to integrate some variant of the
Broadcom BCM5641 PHYs.

I know that netdev@ isn't about fixing my local issues, but in the odd chance anyone
has ever either used such a module successfully, or has some insight on what is
going on with these Broadcom PHYs, I would appreciate a lot :) Any finding or
patch we can come-up with will be upstreamed of course :)

Any people with some experience on this PHY or this kind of module may be
able to shed some lights on the findings I was able to gather so far.

All modules have the same internal PHY, which exposes itself as a BCM5461 :

ID : 002060c1

I know that because I was able to talk to the PHY using mdio over i2c, at
address 0x56 on the i2c bus. On some modules, the PHY doesn't reply at all,
on some it stalls the i2c bus if I try to do 16bits accesses (I have to use 8 bits
accesses), and on some modules the regular 16bits accesses work...

That alone makes me wonder if there's not some kind of firmware running in
there replying to mdio ?

Regarding what I can achieve with these, YMMV :

- I have a pair of Prolabs module with the ID "CISCO-PROLABS GLC-GE-100FX-C".

These are the ones that can only do 8bits mdio accesses. When the PHY is
left undriven by the kernel, and you plug it into an SGMII-able SFP port, you
get a nice loop of 'link is up / link is down / link is up / ...' reported
by the MAC (or PCS). Its eeprom doesn't even say that it's a 100fx module
(id->base.e100_base_fx isn't set). It does say "Cisco compatible", maybe it's
using some flavour of SGMII that I don't know about ?

- I have a pair of FS modules with the ID "FS SFP-GE-100FX". These behave
almost exactly as the ones above, but it can be accessed with 16-bits mdio
transactions.

- I have a "PHOENIX CONTACT 2891081" that simply doesn't work

- And maybe the most promising of all, a pair of "PROLABS SFP-GE-100FX-C".
These reply on 16bits mdio accesses, and when you plug them with the PHY
undriven by the kernel (so relying only on internal config and straps), I
get link-up detected by the MAC through inband SGMII, and I can receive
traffic ! TX doesn't work though :(

On the MAC side, I tested with 3 different SoC, all using a different PCS :
- A Turris Omnia, that uses mvneta and its PCS
- A dwmac-socfpga board, using a Lynx / Altera TSE PCS to drive the SGMII
- A KSZ9477 and its variant of DW XPCS.

The behaviour is the same on all of them, so I'd say there's a very good chance
the modules are acting up. TBH I don't know much about sourcing SFPs, they
behave so differently that it may just be that I didn't find the exact reference
that for some reason happens to work ?

The link-partner is a device that only does 100BaseX.

On all of these modules, I've tried to either let the PHY completely unmanaged
by the kernel, no mdio transactions whatsoever and we leave the default PHY
settings to their thing. As nothing worked, I've tried driving the PHY through
the kernel's broadcom.c driver, but that driver really doesn't support 100FX so
it's also expected that this doesn't work. Unfortunately, I don't have
access to any documentation for that PHY...

The driver does say, for a similar model :

/* The PHY is strapped in RGMII-fiber mode when INTERF_SEL[1:0]
* is 01b, and the link between PHY and its link partner can be
* either 1000Base-X or 100Base-FX.
* RGMII-1000Base-X is properly supported, but RGMII-100Base-FX
* support is still missing as of now.
*/

Not quite the same as our case as it's talking about RGMII, not SGMII, but
maybe the people who wrote that code know a bit more or have access to some
documentation ? I've tried to put these persons in CC :)

In any case, should anyone want to give this a shot in the future, I'm using the
following patch so that the SFP machinery can try to probe PHYs on these
non-copper modules - that patch needs splitting up and is more of a hack than
anything else.

Thanks a lot everyone, and sorry for the noise if this is misplaced,

Maxime

-------x8---------------------------------------------------------------

From 101b7cc3c343f6b28e49aa11ed6e759797a1c2b5 Mon Sep 17 00:00:00 2001
From: Maxime Chevallier <maxime.chevallier@bootlin.com>
Date: Wed, 19 Mar 2025 16:13:58 +0100
Subject: [PATCH] net: sfp: Allow using a range of SGMII to 100BaseX SFP module

SGMII to 100BaseX modules are useful to support 100M links on a 1G
capable SFP port. These modules have an internal PHY, but none of the
tested modules appear to work.

This patch allows selecting SGMII as the host serdes interface for these
modules, as well as to try and drive the internal PHY from linux as a
last-effort attempt to get them to work, unsuccessfully.

Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/phy/sfp-bus.c | 7 ++++++-
drivers/net/phy/sfp.c | 22 ++++++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c
index f13c00b5b449..3ea062a66f37 100644
--- a/drivers/net/phy/sfp-bus.c
+++ b/drivers/net/phy/sfp-bus.c
@@ -120,6 +120,9 @@ bool sfp_may_have_phy(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
if (id->base.e1000_base_t)
return true;

+ if (id->base.e100_base_fx)
+ return true;
+
if (id->base.phys_id != SFF8024_ID_DWDM_SFP) {
switch (id->base.extended_cc) {
case SFF8024_ECC_10GBASE_T_SFI:
@@ -213,6 +216,7 @@ void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
if (id->base.e100_base_fx || id->base.e100_base_lx) {
phylink_set(modes, 100baseFX_Full);
__set_bit(PHY_INTERFACE_MODE_100BASEX, interfaces);
+ __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
}
if ((id->base.e_base_px || id->base.e_base_bx10) && br_nom == 100) {
phylink_set(modes, 100baseFX_Full);
@@ -378,7 +382,8 @@ phy_interface_t sfp_select_interface(struct sfp_bus *bus,
return PHY_INTERFACE_MODE_2500BASEX;

if (phylink_test(link_modes, 1000baseT_Half) ||
- phylink_test(link_modes, 1000baseT_Full))
+ phylink_test(link_modes, 1000baseT_Full) ||
+ phylink_test(link_modes, 100baseFX_Full))
return PHY_INTERFACE_MODE_SGMII;

if (phylink_test(link_modes, 1000baseX_Full))
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index 5347c95d1e77..03ba78d71b51 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -476,6 +476,19 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, modes);
}

+static void sfp_fixup_sgmii_to_100base(struct sfp *sfp)
+{
+ sfp->mdio_protocol = MDIO_I2C_MARVELL_C22;
+}
+
+static void sfp_quirk_sgmii_to_100base(const struct sfp_eeprom_id *id,
+ unsigned long *modes,
+ unsigned long *interfaces)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, modes);
+ __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
+}
+
#define SFP_QUIRK(_v, _p, _m, _f) \
{ .vendor = _v, .part = _p, .modes = _m, .fixup = _f, }
#define SFP_QUIRK_M(_v, _p, _m) SFP_QUIRK(_v, _p, _m, NULL)
@@ -543,6 +556,15 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
+
+ SFP_QUIRK("CISCO-PROLABS", "GLC-GE-100FX-C", sfp_quirk_sgmii_to_100base,
+ sfp_fixup_sgmii_to_100base),
+ SFP_QUIRK("PROLABS", "SFP-GE-100FX-C", sfp_quirk_sgmii_to_100base,
+ sfp_fixup_sgmii_to_100base),
+ SFP_QUIRK("FS", "SFP-GE-100FX", sfp_quirk_sgmii_to_100base,
+ sfp_fixup_sgmii_to_100base),
+ SFP_QUIRK("PHOENIX CONTACT", "2891081", sfp_quirk_sgmii_to_100base,
+ sfp_fixup_sgmii_to_100base),
};

static size_t sfp_strlen(const char *str, size_t maxlen)
--
2.49.0