[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260114225731.811993-5-maxime.chevallier@bootlin.com>
Date: Wed, 14 Jan 2026 23:57:26 +0100
From: Maxime Chevallier <maxime.chevallier@...tlin.com>
To: davem@...emloft.net,
Andrew Lunn <andrew@...n.ch>,
Jakub Kicinski <kuba@...nel.org>,
Eric Dumazet <edumazet@...gle.com>,
Paolo Abeni <pabeni@...hat.com>,
Russell King <linux@...linux.org.uk>,
Jonas Jelonek <jelonek.jonas@...il.com>,
Florian Fainelli <f.fainelli@...il.com>,
Heiner Kallweit <hkallweit1@...il.com>
Cc: Maxime Chevallier <maxime.chevallier@...tlin.com>,
netdev@...r.kernel.org,
linux-kernel@...r.kernel.org,
thomas.petazzoni@...tlin.com,
Simon Horman <horms@...nel.org>,
Romain Gantois <romain.gantois@...tlin.com>,
Marek Behún <kabel@...nel.org>,
bcm-kernel-feedback-list@...adcom.com
Subject: [PATCH net-next 4/6] net: phy: broadcom: Support SGMII to 100FX on BCM5461
Multiple SFP modules of the type "SGMII to 100BaseFX" appear to be using
a Broadcom BCM5461 PHY as a media converter inside the module. This is
the case for at least the Prolabs GLC-GE-100FX-C, and the FS
SFP-GE-100FX modules.
Out of the box, these modules don't work, and need the PHY to be
configured. Florian Fainelli has helped a lot, and provided some
programming instructions to use this mode in the PHY.
Implement support for that mode, based on Florian's instructions and some
more tweaks found by trial and error.
There's no register we can read from the PHY to know that the PHY is
operating in SGMII to 100FX, so we also add a .get_features() callback
that populates the PHY's supported linkmodes according to the module
caps parsed from the eeprom.
Signed-off-by: Maxime Chevallier <maxime.chevallier@...tlin.com>
---
drivers/net/phy/broadcom.c | 94 ++++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+)
diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
index cb306f9e80cc..bcdd6ed70b6b 100644
--- a/drivers/net/phy/broadcom.c
+++ b/drivers/net/phy/broadcom.c
@@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/phy.h>
+#include <linux/sfp.h>
#include <linux/device.h>
#include <linux/brcmphy.h>
#include <linux/of.h>
@@ -455,6 +456,78 @@ static int bcm54811_config_init(struct phy_device *phydev)
return bcm5481x_set_brrmode(phydev, priv->brr_mode);
}
+static int bcm5461_config_init(struct phy_device *phydev)
+{
+ int rc, val;
+
+ /* We don't have any special steps to follow for anything other than
+ * SGMII to 100BaseFX
+ */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII ||
+ !linkmode_test_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
+ phydev->supported))
+ return 0;
+
+ /* Select 1000BASE-X register set (primary SerDes) */
+ val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE);
+ if (val < 0)
+ return val;
+ val |= BCM54XX_SHD_MODE_1000BX;
+ rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);
+ if (rc < 0)
+ return rc;
+
+ /* Power down SerDes interface */
+ rc = phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
+ if (rc < 0)
+ return rc;
+
+ /* Select proper interface mode */
+ val &= ~BCM54XX_SHD_INTF_SEL_MASK;
+ val |= phydev->interface == PHY_INTERFACE_MODE_SGMII ?
+ BCM54XX_SHD_INTF_SEL_SGMII :
+ BCM54XX_SHD_INTF_SEL_GBIC;
+ rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);
+ if (rc < 0)
+ return rc;
+
+ /* Power up SerDes interface */
+ rc = phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+ if (rc < 0)
+ return rc;
+
+ /* For 100BaseFX, the signal detection is configured in bit 5 of the shadow
+ * 0b01100 in the 0x1C register.
+ *
+ * 0 to use EN_10B/SD as CMOS/TTL signal detect (default)
+ * 1 to use SD_100FX± as PECL signal detect
+ */
+ rc = bcm_phy_write_shadow(phydev, 0xC, BIT(5));
+ if (rc < 0)
+ return rc;
+
+ /* You can use either copper or SGMII interface for 100BaseFX and that will
+ * be configured this way:
+ *
+ * - in register 0x1C, shadow 0b10 (1000Base-T/100Base-TX/10Base-T Spare
+ * Control 1), set bit 4 to 1 to enable 100BaseFX
+ */
+ rc = bcm_phy_write_shadow(phydev, 0x2, BIT(4));
+ if (rc < 0)
+ return rc;
+
+ /* disable auto-negotiation with register 0x00 = 0x2100 */
+ phy_write(phydev, MII_BMCR, 0x2100);
+
+ /* set register 0x18 to 0x430 (bit 10 -> normal mode, bits 5:4 control
+ * the edge rate. 0b00 -> 4ns, 0b01 -> 5ns, 0b10 -> 3ns, 0b11 -> 0ns. This
+ * is the auxiliary control register (MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL).
+ */
+ phy_write(phydev, 0x18, 0x430);
+
+ return 0;
+}
+
static int bcm54xx_config_init(struct phy_device *phydev)
{
int reg, err, val;
@@ -492,6 +565,9 @@ static int bcm54xx_config_init(struct phy_device *phydev)
case PHY_ID_BCM54210E:
err = bcm54210e_config_init(phydev);
break;
+ case PHY_ID_BCM5461:
+ err = bcm5461_config_init(phydev);
+ break;
case PHY_ID_BCM54612E:
err = bcm54612e_config_init(phydev);
break;
@@ -1255,6 +1331,23 @@ static void bcm54xx_link_change_notify(struct phy_device *phydev)
bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08, ret);
}
+static int bcm5461_get_features(struct phy_device *phydev)
+{
+ if (!phy_on_sfp(phydev))
+ return genphy_read_abilities(phydev);
+
+ if (!phydev->parent_sfp_caps)
+ return -EINVAL;
+
+ /* For SGMII to 100FX modules, the reported linkmodes from
+ * genphy_read_abilities() are incorrect. Let's repy on the SFP module
+ * caps
+ */
+ linkmode_copy(phydev->supported, phydev->parent_sfp_caps->link_modes);
+
+ return 0;
+}
+
static int lre_read_master_slave(struct phy_device *phydev)
{
int cfg = MASTER_SLAVE_CFG_UNKNOWN, state;
@@ -1505,6 +1598,7 @@ static struct phy_driver broadcom_drivers[] = {
.probe = bcm54xx_phy_probe,
.config_init = bcm54xx_config_init,
.config_intr = bcm_phy_config_intr,
+ .get_features = bcm5461_get_features,
.handle_interrupt = bcm_phy_handle_interrupt,
.link_change_notify = bcm54xx_link_change_notify,
.led_brightness_set = bcm_phy_led_brightness_set,
--
2.49.0
Powered by blists - more mailing lists