Skip to content

Instantly share code, notes, and snippets.

@ericwoud
Last active August 30, 2023 13:25
Show Gist options
  • Save ericwoud/d912301a93cd41b39621a65cc372a5c0 to your computer and use it in GitHub Desktop.
Save ericwoud/d912301a93cd41b39621a65cc372a5c0 to your computer and use it in GitHub Desktop.
SFP OEM SFP-2.5G-T patches
/*
Old news in this gist, development continues here:
https://github.com/ericwoud/linux/tree/net-phy-realtek-next
to create pull request to:
https://github.com/dangowrt/linux/tree/net-phy-realtek-next
//////////////////////////////////////////////////////////////
This file is only added for reading and keeping the link to this page in tact...
Only use the 000*.patch files.
Based on (partly included in 0006.patch):
[PATCH] net: phy: realtek: rtl8221: allow to configure SERDES mode
[PATCH] net: phy: realtek: disable SGMII in-band AN for 2.5G PHYs
By Daniel Golle to add interface switching
*/
From 63ef1c65a0431dd6f6aa6f64c7109d93c501150d Mon Sep 17 00:00:00 2001
From: Eric Woudstra <ericwouds@gmail.com>
Date: Fri, 28 Jul 2023 16:01:56 +0200
Subject: [PATCH 1/5] net: phy: sfp: Fixup for OEM SFP-2.5G-T module
Implement fixup for OEM SFP-2.5G-T module. It can now recognise and attach
the rtl8221B, using the RollBall protocol.
Remove the quirk. With implementing the fixup, the PHY is attached, so the
values changed in the quirk are no longer used.
---
drivers/net/phy/sfp.c | 27 +++++++++------------------
1 file changed, 9 insertions(+), 18 deletions(-)
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index d855a1830..91262d1ea 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -363,6 +363,14 @@ static void sfp_fixup_rollball_proto(struct sfp *sfp, unsigned int secs)
sfp->module_t_wait = msecs_to_jiffies(secs * 1000);
}
+// For 2.5GBASE-T short-reach modules
+static void sfp_fixup_oem_2_5g(struct sfp *sfp)
+{
+ sfp_fixup_rollball_proto(sfp, 4);
+ sfp->id.base.connector = SFF8024_CONNECTOR_RJ45;
+ sfp->id.base.extended_cc = SFF8024_ECC_2_5GBASE_T;
+}
+
static void sfp_fixup_fs_10gt(struct sfp *sfp)
{
sfp_fixup_10gbaset_30m(sfp);
@@ -404,23 +412,6 @@ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
__set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
}
-static void sfp_quirk_disable_autoneg(const struct sfp_eeprom_id *id,
- unsigned long *modes,
- unsigned long *interfaces)
-{
- linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, modes);
-}
-
-static void sfp_quirk_oem_2_5g(const struct sfp_eeprom_id *id,
- unsigned long *modes,
- unsigned long *interfaces)
-{
- /* Copper 2.5G SFP */
- linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, modes);
- __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
- sfp_quirk_disable_autoneg(id, modes, interfaces);
-}
-
static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
unsigned long *modes,
unsigned long *interfaces)
@@ -475,7 +466,7 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK_F("Walsun", "HXSX-ATRI-1", sfp_fixup_fs_10gt),
SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
- SFP_QUIRK_M("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
+ SFP_QUIRK_F("OEM", "SFP-2.5G-T", sfp_fixup_oem_2_5g),
SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
--
2.40.1
From bb4e4568af01630ce817a018c31b4b61dda7f013 Mon Sep 17 00:00:00 2001
From: Eric Woudstra <ericwouds@gmail.com>
Date: Wed, 2 Aug 2023 17:06:39 +0200
Subject: [PATCH 3/5] net: phy: realtek: rtlgen_get_speed(): Pass register
value as argument
The register that needs to be read to determine the speed, is not
always read in the same manner, also not always at the same address.
To be able to use the one rtlgen_get_speed() function, generalize
rtlgen_get_speed() even more. Pass the value from the device specific
register as argument to rtlgen_get_speed().
---
drivers/net/phy/realtek.c | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
index 894172a3e..e138a93ac 100644
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -537,17 +537,11 @@ static int rtl8366rb_config_init(struct phy_device *phydev)
}
/* get actual speed to cover the downshift case */
-static int rtlgen_get_speed(struct phy_device *phydev)
+static int rtlgen_get_speed(struct phy_device *phydev, int val)
{
- int val;
-
if (!phydev->link)
return 0;
- val = phy_read_paged(phydev, 0xa43, 0x12);
- if (val < 0)
- return val;
-
switch (val & RTLGEN_SPEED_MASK) {
case 0x0000:
phydev->speed = SPEED_10;
@@ -576,13 +570,17 @@ static int rtlgen_get_speed(struct phy_device *phydev)
static int rtlgen_read_status(struct phy_device *phydev)
{
- int ret;
+ int ret, val;
ret = genphy_read_status(phydev);
if (ret < 0)
return ret;
- return rtlgen_get_speed(phydev);
+ val = phy_read_paged(phydev, 0xa43, 0x12);
+ if (val < 0)
+ return val;
+
+ return rtlgen_get_speed(phydev, val);
}
static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
@@ -705,7 +703,7 @@ static int rtl822x_config_aneg(struct phy_device *phydev)
static int rtl822x_read_status(struct phy_device *phydev)
{
- int ret;
+ int ret, val;
if (phydev->autoneg == AUTONEG_ENABLE) {
int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
@@ -725,7 +723,11 @@ static int rtl822x_read_status(struct phy_device *phydev)
if (ret < 0)
return ret;
- return rtlgen_get_speed(phydev);
+ val = phy_read_paged(phydev, 0xa43, 0x12);
+ if (val < 0)
+ return val;
+
+ return rtlgen_get_speed(phydev, val);
}
static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
--
2.40.1
From 1afa0b08595bd6e227e9370189b31f6658988c1e Mon Sep 17 00:00:00 2001
From: Eric Woudstra <ericwouds@gmail.com>
Date: Wed, 2 Aug 2023 17:08:03 +0200
Subject: [PATCH 4/5] net: phy: realtek: rtlgen_get_speed(): Add 2500-Lite mode
Add 2500-Litemode, reporting 1000Mbps.
---
drivers/net/phy/realtek.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
index e138a93ac..7737e8173 100644
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -561,6 +561,9 @@ static int rtlgen_get_speed(struct phy_device *phydev, int val)
case 0x0220:
phydev->speed = SPEED_5000;
break;
+ case 0x0230:
+ phydev->speed = SPEED_1000;
+ break;
default:
break;
}
--
2.40.1
From 8be25416a6c6e2cffad9fe9f575df9e5d7362a76 Mon Sep 17 00:00:00 2001
From: Eric Woudstra <ericwouds@gmail.com>
Date: Sun, 6 Aug 2023 19:27:35 +0200
Subject: [PATCH 5/6] net: phy: phy_device: Do not probe or read c45_id from
Realtek PHY MDIO_MMD_VEND1
Realtek PHY do not support reading from MDIO_MMD_VEND1 at MDIO_STAT2
or MII_PHYSIDx. The registers are undocumented in the datasheet and
reading from them makes the PHY behave erratically and fail.
---
drivers/net/phy/phy_device.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 3e9909b30..2ae603d16 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -831,6 +831,14 @@ static int get_phy_c45_ids(struct mii_bus *bus, int addr,
if (!(devs_in_pkg & (1 << i)))
continue;
+ /* Realtek PHY's do not support reading from MDIO_MMD_VEND1
+ * at MDIO_STAT2 or MII_PHYSIDx. It makes them fail.
+ */
+ if (i == MDIO_MMD_VEND1 && phy_id_compare(
+ c45_ids->device_ids[MDIO_MMD_PMAPMD],
+ 0x001cc800, GENMASK(31, 10)))
+ continue;
+
if (i == MDIO_MMD_VEND1 || i == MDIO_MMD_VEND2) {
/* Probe the "Device Present" bits for the vendor MMDs
* to ignore these if they do not contain IEEE 802.3
--
2.40.1
From a810647e4a2cc627ffb5bffeff9fad6270589cba Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: Tue, 22 Aug 2023 16:14:41 +0200
Subject: [PATCH 6/6] net: phy: realtek: Add driver instance for RTL8221-VB-CG
via C45
Add driver instance for clause-45 communication with RTL8221-VB-CG. The driver
sets up a separate set of function pointers for clause-45 only accessable PHY's.
The C45 driver instance checks if the RTL8221-VB-CG is in, what Realtek
calls, "Rate Adaptor Mode".
This new driver instance will be used by the OEM SFP-2.5G-T module. The
RTL8221-VB-CG registers are setup by the onboard microprocessor that is also
responsible for the RollBall protocol.
---
drivers/net/phy/realtek.c | 317 +++++++++++++++++++++++++++++++++++++-
1 file changed, 315 insertions(+), 2 deletions(-)
diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
index 7737e8173..651eaebfc 100644
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -70,8 +70,46 @@
#define RTLGEN_SPEED_MASK 0x0630
+/* MMC VENDOR 1 */
+
+#define RTL8221B_MMD_SERDES_CTRL MDIO_MMD_VEND1
+#define RTL8221B_SERDES_OPTION 0x697a
+#define RTL8221B_SERDES_OPTION_MODE_MASK GENMASK(5, 0)
+#define RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII 0
+#define RTL8221B_SERDES_OPTION_MODE_HISGMII_SGMII 1
+#define RTL8221B_SERDES_OPTION_MODE_2500BASEX 2
+#define RTL8221B_SERDES_OPTION_MODE_HISGMII 3
+
+#define RTL8221B_SERDES_CTRL1 0x6a04
+#define RTL8221B_SERDES_CTRL1_MDIOSEL_MASK 0x0700
+#define RTL8221B_SERDES_CTRL1_MDIOSEL_SGMII 0x0400
+#define RTL8221B_SERDES_CTRL1_MDIOSEL_HISGMII_2500BASEX 0x0500
+
+#define RTL8221B_SERDES_CTRL3 0x7580
+#define RTL8221B_SERDES_CTRL3_MODE_MASK GENMASK(4, 0)
+#define RTL8221B_SERDES_CTRL3_MODE_SGMII 0x02
+#define RTL8221B_SERDES_CTRL3_MODE_HISGMII 0x12
+#define RTL8221B_SERDES_CTRL3_MODE_2500BASEX 0x16
+#define RTL8221B_SERDES_CTRL3_MODE_OFF 0x1F
+#define RTL8221B_SERDES_CTRL3_FORCE_LINK_DOWN BIT(5)
+
+/* MMC VENDOR 2 */
+
+#define RTL8221B_MMD_PHY_CTRL MDIO_MMD_VEND2
+
+#define RTL822x_GBCR 0xa412
+#define RTL822x_GBCR_1000BASET_FULL_DUPLEX_CAP BIT(9)
+
+#define RTL822x_GBSR 0xa414
+#define RTL822x_GBSR_LP_1000BASET_HALF_DUPLEX_CAP BIT(10)
+#define RTL822x_GBSR_LP_1000BASET_FULL_DUPLEX_CAP BIT(11)
+
+#define RTL822x_PHYSR 0xa434
+
#define RTL_GENERIC_PHYID 0x001cc800
#define RTL_8211FVD_PHYID 0x001cc878
+#define RTL_8221B_VB_CG 0x001cc849
+
MODULE_DESCRIPTION("Realtek PHY driver");
MODULE_AUTHOR("Johnson Leung");
@@ -733,6 +771,143 @@ static int rtl822x_read_status(struct phy_device *phydev)
return rtlgen_get_speed(phydev, val);
}
+static void rtl822x_update_interface(struct phy_device *phydev)
+{
+ int val;
+
+ /* Automatically switch SERDES interface between
+ * SGMII and 2500-BaseX according to vendor register.
+ */
+
+ val = phy_read_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, RTL8221B_SERDES_CTRL3);
+ if (val < 0)
+ return;
+
+ //phydev_dbg(phydev, "rtl822x_update_interface: mode=0x%02lx\n", val & RTL8221B_SERDES_CTRL3_MODE_MASK);
+
+ switch (val & RTL8221B_SERDES_CTRL3_MODE_MASK) {
+ case RTL8221B_SERDES_CTRL3_MODE_2500BASEX:
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ break;
+ case RTL8221B_SERDES_CTRL3_MODE_SGMII:
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ break;
+ default:
+ break;
+ }
+}
+
+static int rtl822x_c45_get_features(struct phy_device *phydev)
+{
+ int ret = 0;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret < 0)
+ return ret;
+
+ linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT,
+ phydev->supported);
+
+ /* sfp_select_interface() only supports 2500baseX */
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->supported))
+ linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+ phydev->supported);
+
+ phydev_dbg(phydev,
+ "rtl822x_c45_get_features: supported=%*pb\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, phydev->supported);
+
+ return ret;
+
+}
+
+static int rtl822x_c45_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ int ret, val;
+
+ /* Do not advertise 2500baseX */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+ phydev->advertising);
+
+ phydev_dbg(phydev,
+ "rtl822x_c45_config_aneg: %*pb\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, phydev->advertising);
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return genphy_c45_pma_setup_forced(phydev);
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ /* Clause 45 has no standardized support for 1000BaseT, therefore
+ * use vendor registers for this mode.
+ */
+ val = 0;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->advertising))
+ val |= RTL822x_GBCR_1000BASET_FULL_DUPLEX_CAP;
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL822x_GBCR,
+ RTL822x_GBCR_1000BASET_FULL_DUPLEX_CAP, val);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int rtl822x_c45_read_status(struct phy_device *phydev)
+{
+ int ret, val;
+
+ ret = genphy_c45_read_status(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* Clause 45 has no standardized support for 1000BaseT, therefore
+ * use vendor registers for this mode.
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822x_GBSR);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->lp_advertising,
+ val & RTL822x_GBSR_LP_1000BASET_HALF_DUPLEX_CAP);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->lp_advertising,
+ val & RTL822x_GBSR_LP_1000BASET_FULL_DUPLEX_CAP);
+
+ /* Read actual speed from vendor register. */
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822x_PHYSR);
+ if (val < 0)
+ return val;
+
+ ret = rtlgen_get_speed(phydev, val);
+ if (ret)
+ return ret;
+
+ if (phydev->is_c45 && phydev->link)
+ rtl822x_update_interface(phydev);
+
+ return 0;
+}
+
+static int rtl822x_c45_suspend(struct phy_device *phydev)
+{
+ return genphy_c45_pma_suspend(phydev);
+}
+
+static int rtl822x_c45_resume(struct phy_device *phydev)
+{
+ return genphy_c45_pma_resume(phydev);
+}
+
static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
{
int val;
@@ -756,6 +931,36 @@ static int rtl8226_match_phy_device(struct phy_device *phydev)
rtlgen_supports_2_5gbps(phydev);
}
+/* Only use Clause 45 if available and Clause 22 is not.
+* This also includes RollBall protocol SFP modules.
+* Could possibly replace this function call with phydev->is_c45
+* to use the Clause 45 driver instance on all capable rtl822x.
+*/
+static bool rtl822x_use_c45(struct phy_device *phydev)
+{
+ return !phydev->mdio.bus->read && !!phydev->mdio.bus->read_c45;
+}
+
+static bool rtl822x_match_id(struct phy_device *phydev, u32 id)
+{
+ if (phydev->is_c45)
+ return id == phydev->c45_ids.device_ids[1];
+ else
+ return id == phydev->phy_id;
+}
+
+static int rtl8221_vb_cg_c45_match_phy_device(struct phy_device *phydev)
+{
+ return rtl822x_match_id(phydev, RTL_8221B_VB_CG) &&
+ rtl822x_use_c45(phydev);
+}
+
+static int rtl8221_vb_cg_c22_match_phy_device(struct phy_device *phydev)
+{
+ return rtl822x_match_id(phydev, RTL_8221B_VB_CG) &&
+ !rtl822x_use_c45(phydev);
+}
+
static int rtlgen_resume(struct phy_device *phydev)
{
int ret = genphy_resume(phydev);
@@ -882,6 +1087,104 @@ static irqreturn_t rtl9000a_handle_interrupt(struct phy_device *phydev)
return IRQ_HANDLED;
}
+static int rtl8221b_config_init(struct phy_device *phydev)
+{
+ u16 option_mode;
+ int val;
+
+ if (!phy_interface_empty(phydev->host_interfaces)) {
+ if (!test_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->host_interfaces))
+ return 0;
+
+ /* check if rate adapter mode is needed */
+ if (!test_bit(PHY_INTERFACE_MODE_SGMII, phydev->host_interfaces))
+ option_mode = RTL8221B_SERDES_OPTION_MODE_2500BASEX;
+ else
+ option_mode = RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII;
+ } else {
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_2500BASEX:
+ /* use rate adapter mode if PHY is connected via C22 */
+ if (!phydev->is_c45) {
+ option_mode = RTL8221B_SERDES_OPTION_MODE_2500BASEX;
+ break;
+ }
+ fallthrough;
+ case PHY_INTERFACE_MODE_SGMII:
+ option_mode = RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII;
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL,
+ 0x75f3, 0);
+
+ phy_modify_mmd_changed(phydev, RTL8221B_MMD_SERDES_CTRL,
+ RTL8221B_SERDES_OPTION,
+ RTL8221B_SERDES_OPTION_MODE_MASK, option_mode);
+
+ switch (option_mode) {
+ case RTL8221B_SERDES_OPTION_MODE_2500BASEX:
+ phydev->rate_matching = RATE_MATCH_PAUSE;
+ fallthrough;
+ case RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII:
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6a04, 0x0503);
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f10, 0xd455);
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f11, 0x8020);
+ break;
+ case RTL8221B_SERDES_OPTION_MODE_HISGMII:
+ phydev->rate_matching = RATE_MATCH_PAUSE;
+ fallthrough;
+ case RTL8221B_SERDES_OPTION_MODE_HISGMII_SGMII:
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6a04, 0x0503);
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f10, 0xd433);
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f11, 0x8020);
+ break;
+ }
+
+ /* Disable SGMII AN */
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x7588, 0x2);
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x7589, 0x71d0);
+ phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x7587, 0x3);
+ phy_read_mmd_poll_timeout(phydev, RTL8221B_MMD_SERDES_CTRL, 0x7587,
+ val, !(val & BIT(0)), 500, 100000, false);
+
+ return 0;
+}
+
+static int rtl822x_c45_config_init(struct phy_device *phydev)
+{
+ genphy_c45_pma_suspend(phydev);
+
+ rtl8221b_config_init(phydev);
+
+ genphy_c45_pma_resume(phydev);
+
+ phydev_dbg(phydev, "rtl822x_c45_config_init\n");
+
+ return 0;
+}
+
+static int rtl822x_get_rate_matching(struct phy_device *phydev,
+ phy_interface_t iface)
+{
+ u32 option_mode;
+ int reg;
+
+ reg = phy_read_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, RTL8221B_SERDES_OPTION);
+ if (reg < 0)
+ return -EIO;
+
+ option_mode = FIELD_GET(RTL8221B_SERDES_OPTION_MODE_MASK, reg);
+ if (option_mode == RTL8221B_SERDES_OPTION_MODE_2500BASEX ||
+ option_mode == RTL8221B_SERDES_OPTION_MODE_HISGMII)
+ return RATE_MATCH_PAUSE;
+
+ return RATE_MATCH_NONE;
+}
+
static struct phy_driver realtek_drvs[] = {
{
PHY_ID_MATCH_EXACT(0x00008201),
@@ -1033,8 +1336,8 @@ static struct phy_driver realtek_drvs[] = {
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
}, {
- PHY_ID_MATCH_EXACT(0x001cc849),
- .name = "RTL8221B-VB-CG 2.5Gbps PHY",
+ .match_phy_device = rtl8221_vb_cg_c22_match_phy_device,
+ .name = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.read_status = rtl822x_read_status,
@@ -1042,6 +1345,16 @@ static struct phy_driver realtek_drvs[] = {
.resume = rtlgen_resume,
.read_page = rtl821x_read_page,
.write_page = rtl821x_write_page,
+ }, {
+ .match_phy_device = rtl8221_vb_cg_c45_match_phy_device,
+ .name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+ .config_init = &rtl822x_c45_config_init,
+ .get_rate_matching = rtl822x_get_rate_matching,
+ .get_features = rtl822x_c45_get_features,
+ .config_aneg = rtl822x_c45_config_aneg,
+ .read_status = rtl822x_c45_read_status,
+ .suspend = rtl822x_c45_suspend,
+ .resume = rtl822x_c45_resume,
}, {
PHY_ID_MATCH_EXACT(0x001cc84a),
.name = "RTL8221B-VM-CG 2.5Gbps PHY",
--
2.40.1
@ericwoud
Copy link
Author

ericwoud commented Aug 8, 2023

To me this would make sense, however, the default seems to be to have it enabled on SFP modules

It is why I have added the rtl8221b ID to the phylink_phy_no_inband() function. I find it is the only code path to get MLO_AN_PHY for the out of band negotiation.

@ericwoud
Copy link
Author

@dangowrt

dangowrt/linux@d1391ca

Instead of using the speed, now reading the appropriate register (and a bit different register naming):

#define RTL822x_V1_SERDES_OPTION		0x697a
#define RTL822x_V1_SERDES_OPTION_MODE_MASK		GENMASK(5, 0)
#define RTL822x_V1_SERDES_OPTION_MODE_2500BASEX_SGMII	0
#define RTL822x_V1_SERDES_OPTION_MODE_HISGMII_SGMII	1
#define RTL822x_V1_SERDES_OPTION_MODE_2500BASEX		2
#define RTL822x_V1_SERDES_OPTION_MODE_HISGMII		3

#define RTL822x_V1_SERDES_CTRL3			0x7580
#define RTL822x_V1_SERDES_CTRL3_MODE_MASK		GENMASK(5, 0)
#define RTL822x_V1_SERDES_CTRL3_MODE_SGMII		0x02
#define RTL822x_V1_SERDES_CTRL3_MODE_HISGMII		0x12
#define RTL822x_V1_SERDES_CTRL3_MODE_2500BASEX		0x16
#define RTL822x_V1_SERDES_CTRL3_MODE_OFF		0x1F

static void rtl822x_update_interface(struct phy_device *phydev)
{
	int val;

	/* Automatically switch SERDES interface between
	 * SGMII and 2500-BaseX according to vendor register.
	 */

	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822x_V1_SERDES_CTRL3);
	if (val < 0)
		return;

	switch (val & RTL822x_V1_SERDES_CTRL3_MODE_MASK) {
	case RTL822x_V1_SERDES_CTRL3_MODE_2500BASEX:
		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
		break;
	case RTL822x_V1_SERDES_CTRL3_MODE_SGMII:
		phydev->interface = PHY_INTERFACE_MODE_SGMII;
		break;
	default:
		break;
	}
}

@dangowrt
Copy link

Why RTL822x_V1_*? Is there a V2?

@ericwoud
Copy link
Author

ericwoud commented Aug 12, 2023

@dangowrt

Why RTL822x_V1_*? Is there a V2?

I used naming similar as in drivers/net/phy/marvell10g.c

	/* Vendor2 MMD registers */
	MV_V2_PORT_CTRL		= 0xf001,
	MV_V2_PORT_CTRL_PWRDOWN					= BIT(11),
	MV_V2_33X0_PORT_CTRL_SWRST				= BIT(15),

V1 To indicate MMD VENDOR 1

I also have:

#define RTL822x_V2_GBCR				0xa412
#define RTL822x_V2_GBCR_1000BASET_FULL_DUPLEX_CAP	BIT(9)

#define RTL822x_V2_GBSR				0xa414
#define RTL822x_V2_GBSR_LP_1000BASET_HALF_DUPLEX_CAP	BIT(10)
#define RTL822x_V2_GBSR_LP_1000BASET_FULL_DUPLEX_CAP	BIT(11)

#define RTL822x_V2_PHYSR			0xa434

Which are at MMD VENDOR 2

@dangowrt
Copy link

Ah ok, I get it, but that was not at all obvious and more confusing than helpful.

Maybe better just put a comment above the registers stating on which MMD device they are located?

@ericwoud
Copy link
Author

Sounds good, removing the V1/V2, makes it shorter also.

I've updated the 0006.patch (except for the message body), to show where it is going...

Almost everything using general c45 functions.

Still stuck in "Rate Adapter Mode" This will be next try switching it off.

@ericwoud
Copy link
Author

Added first steps to implement "phydev->rate_matching"

Updated 0006.patch

@dangowrt
Copy link

Also just merged some of your suggestions and the two commits setting up SerDes mode into a single commit:
dangowrt/linux@0d48b97

@ericwoud
Copy link
Author

ericwoud commented Aug 12, 2023

I believe get_rate_matching reports about parameter iface, not current setting....

Reporting the ability for that interface, or for any interface for PHY_INTERFACE_MODE_NA

https://elixir.bootlin.com/linux/latest/source/drivers/net/phy/phy.c#L135

@ericwoud
Copy link
Author

ericwoud commented Aug 12, 2023

They just set it in aquartia driver phydev->rate_matching = .... to change the current setting

@ericwoud
Copy link
Author

ericwoud commented Aug 16, 2023

@dangowrt

From Russell I understand that the driver cannot overrule the phy-mode interface setting from devicetree. The only exception are sfp
modues, and can use host_interfaces.

But we need to swtch interfaces, also on fixed phy's. Would be nice to have host_interfaces for fixed phy's but it is not possible, we would override the devicetree setting.

Perhapse we could use a devicetree setting realtek,rate-adaptor = On/Off/Auto

It would look like so (untested):

static int rtl822x_get_option_mode(struct phy_device *phydev)
{
	DECLARE_PHY_INTERFACE_MASK(interfaces);
	struct device *dev = &phydev->mdio.dev;
	char *s;

	if (!of_property_read_string(dev->of_node, "realtek,rate-adaptor", (const char **)&s)) {
		if (!strcmp(s, "On"))
			return RTL8221B_SERDES_OPTION_MODE_2500BASEX;
		if (!strcmp(s, "Off"))
			return RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII;;
	}

	if (phy_interface_empty(phydev->host_interfaces)) {
		phy_interface_empty(interfaces);
		__set_bit(phydev->interface, interfaces);
	} else
		bitmap_copy(interfaces, phydev->host_interfaces, PHY_INTERFACE_MODE_MAX);

	if (test_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces)) {
		/* check we can operate without rate-adaptor */
		if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces))
			/* this will only happen on sfp modules */
			return RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII;
		else
			return RTL8221B_SERDES_OPTION_MODE_2500BASEX;
	} else if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces)) {
		return RTL8221B_SERDES_OPTION_MODE_HISGMII_SGMII;
	}

	return -EOPNOTSUPP;
}

static int rtl822x_config_init(struct phy_device *phydev)
{
	int option_mode = rtl822x_get_option_mode(phydev);
	if (option_mode < 0)
		return option_mode;

	phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL,
		      0x75f3, 0);

	phy_modify_mmd_changed(phydev, RTL8221B_MMD_SERDES_CTRL,
			       RTL8221B_SERDES_OPTION,
			       RTL8221B_SERDES_OPTION_MODE_MASK, option_mode);

	switch (option_mode) {
	case RTL8221B_SERDES_OPTION_MODE_2500BASEX:
		phydev->rate_matching = RATE_MATCH_PAUSE;
		fallthrough;
	case RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII:
		phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6a04, 0x0503);
		phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f10, 0xd455);
		phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f11, 0x8020);
		break;
	case RTL8221B_SERDES_OPTION_MODE_HISGMII:
		phydev->rate_matching = RATE_MATCH_PAUSE;
		fallthrough;
	case RTL8221B_SERDES_OPTION_MODE_HISGMII_SGMII:
		phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6a04, 0x0503);
		phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f10, 0xd433);
		phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL, 0x6f11, 0x8020);
		break;
	}

	return 0;
}

Now switching interface will only happen on sfp modules, or when rate-adaptor is explicitly switched off in devicetree.

(Edit 2: moved the comment and fixed ireturn code from get_option_mode() )

@ericwoud
Copy link
Author

ericwoud commented Aug 17, 2023

So I edited the above some more (fixed errors) as I think it could work and perhapse could be accepted in netdev/net-next. Still needs testing...

@dangowrt A littlte oops before, I edited again for what I meant...

Or maybe it can be not vendor specitfic, removing the realtek, from the node name... I believe @frank-w prefers something like that...

@frank-w
Copy link

frank-w commented Aug 20, 2023

My guess that this new dts property gets rejected if it is not generic...you've said we do not need it for our sfp and only for fixed phys so I don't need it...and if it does not break existing devices in mainline it should be no problem.

If we can detemine the phy is on sfp or fixed (i thought russels patch allows this) we can't use this to switch on/off rate-adapt-mode? Btw. I do not see this setting in code yet

@ericwoud
Copy link
Author

Anyway, if dangowrt's patches are submitted, I can adjust and send in my patches to netdev/net-next. Guess I'll send as RFC first. ..

I'll see if I can have some time this week to add one of his patches here and try out interface switching ...

@ericwoud
Copy link
Author

It is now back to the minimal set of patches to run the SFP module.

Can't get this module to run in SGMII mode. Not with @dangowrt 's patches, nor with setting all SERDES registers to SGMII mode manually.

It does no change SERDES mode when in RTL8221B_SERDES_OPTION_MODE_2500BASEX_SGMII.

I cannot succesfully change it manually.

The SFP module does run nicely in rate-adaptor mode, on BPI-R3 on the eth1 and lan4 port.

Apply to netdev/net-next

@dangowrt
Copy link

dangowrt commented Aug 22, 2023

My initial experience was also that SGMII was not working with RTL8221B/RTL8226 PHY, and that's because they enabled SGMII in-band-status by default and Linux assumes SGMII in-band-status to not be enabled when connecting a MAC to a PHY. Hence a patch was needed to switch off in-band-status on the PHY:

dangowrt/linux@32d7431

Edit: For SFP this could be different and maybe even desirable to use in-band-status. I've mostly played with this PHY inside TP-LINK XDR6068 router which combined MT7986+MT7531+2x RTL8221B.

@dangowrt
Copy link

Now switching interface will only happen on sfp modules, or when rate-adaptor is explicitly switched off in devicetree.

Imho it should be off by default and only be used if we don't have any other choice. This is also how it is done for MaxLinear 2.5G PHYs which also do the same switching between 2500Base-X and SGMII.

@ericwoud
Copy link
Author

ericwoud commented Aug 22, 2023

Thanks, this looks better 😄

What is this for:

phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL,
		      0x75f3, 0);

It gives my some trouble at first going up. Only get traffic going after disconnecting + connecting the cable (after ip link set up)
(I thought this was the inband bit, but it is not...)

@ericwoud
Copy link
Author

ericwoud commented Aug 22, 2023

@dangowrt

Imho it should be off by default and only be used if we don't have any other choice. This is also how it is done for MaxLinear 2.5G PHYs which also do the same switching between 2500Base-X and SGMII.

Agree, but what about phy-mode in the devicetree, does it get overridden?

Do you know why

phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL,
		      0x75f3, 0);

Gives trouble as described above?

@dangowrt
Copy link

Agree, but what about phy-mode in the devicetree, does it get overridden?

Yes, it does get overridden by the .read_status function in PHY drivers:

https://github.com/torvalds/linux/blob/master/drivers/net/phy/mxl-gpy.c#L523

https://github.com/torvalds/linux/blob/master/drivers/net/phy/aquantia_main.c#L437

What is this for:

phy_write_mmd(phydev, RTL8221B_MMD_SERDES_CTRL,
		      0x75f3, 0);

It's from a non-public document called "RealTek RTL8226B / RTL8221B / RTL8221BI SERDES Mode Setting Flow
Application Note" which describes the first step in the sequence to switch the SerDes and Rate Adapter mode as "Write MMD 30.0x75F3.0 = 0". The actual meaning of that SerDes control register 0x75f3 is not described in any datasheet I'm aware of.

@ericwoud
Copy link
Author

Indeed I also noticed that aquartia does the some, so one would think it would get accepted......

I was looking for that document, but never found it.

@ericwoud
Copy link
Author

ericwoud commented Aug 22, 2023

I have found a solution for the "reconnecting before it works" problem, or at least something that works:

Enclosing with:

static int rtl822x_c45_config_init(struct phy_device *phydev)
{
	genphy_c45_pma_suspend(phydev);

	rtl8221b_config_init(phydev);

	genphy_c45_pma_resume(phydev);

	phydev_dbg(phydev, "rtl822x_c45_config_init\n"); 

	return 0;
}

@ericwoud
Copy link
Author

ericwoud commented Aug 22, 2023

Now the first version that switches the MAC's interface... Based on Daniel's patches.

As soon as Daniel's patches are submitted with the rest of the patch-set, I will remove that code out of these patches so they will depend on those patches.

One note: Only in debug info can see it switching to sgmii, not in normal dmesg output...

@frank-w
Copy link

frank-w commented Aug 22, 2023

My initial experience was also that SGMII was not working with RTL8221B/RTL8226 PHY, and that's because they enabled SGMII in-band-status by default and Linux assumes SGMII in-band-status to not be enabled when connecting a MAC to a PHY. Hence a patch was needed to switch off in-band-status on the PHY:

Maybe thats the cause i get no traffic with this sfp in sfp-lan slot? Still wonder why it was not recognized in sfp-wan slot on r4

@ericwoud can you make daniels part in extra pavth so i can leave this out when base on daniels tree?

@ericwoud
Copy link
Author

My initial experience was also that SGMII was not working with RTL8221B/RTL8226 PHY, and that's because they enabled SGMII in-band-status by default and Linux assumes SGMII in-band-status to not be enabled when connecting a MAC to a PHY. Hence a patch was needed to switch off in-band-status on the PHY:

Maybe thats the cause i get no traffic with this sfp in sfp-lan slot? Still wonder why it was not recognized in sfp-wan slot on r4

This is about fixing sgmii. The version you tried was still only 2500baseX.

@ericwoud
Copy link
Author

@ericwoud can you make daniels part in extra pavth so i can leave this out when base on daniels tree?

The 2 patches I need I think cannot apply as the are preceded by other patches...

If @dangowrt has a patch-set that can apply on netdev/net-next, then I can make my patch-set apply on his.

@frank-w
Copy link

frank-w commented Aug 22, 2023

My initial experience was also that SGMII was not working with RTL8221B/RTL8226 PHY, and that's because they enabled SGMII in-band-status by default and Linux assumes SGMII in-band-status to not be enabled when connecting a MAC to a PHY. Hence a patch was needed to switch off in-band-status on the PHY:

Maybe thats the cause i get no traffic with this sfp in sfp-lan slot? Still wonder why it was not recognized in sfp-wan slot on r4

This is about fixing sgmii. The version you tried was still only 2500baseX.

So it should be working and you cannot reproduce it...so it is maybe a problem in combination with r4 and i have to test with r3 again

@ericwoud
Copy link
Author

ericwoud commented Aug 22, 2023

Really can't say if it should or shouldn't work on R4.

All I know it works on R3. The recent changes I tested on eth1.

Link goes up and pinging works.

@dangowrt
Copy link

@eicwoud It would be great if you can make a pull request with your changes on top of the tree I have prepared here:
https://github.com/dangowrt/linux/tree/net-phy-realtek-next

Then we can discuss and improve the patches together and submit them once they work fine and look good enough.

@ericwoud
Copy link
Author

ericwoud commented Aug 22, 2023

@dangowrt

I quickly converted... No time to test yet. You can check it out:

https://github.com/ericwoud/linux/commits/net-phy-realtek-next

I have changed the code here a bit to store the id in phydev->c45_ids.device_ids[1] for the early 8221b.

The ID is used to match for no_inband (sfp),

Edit: A bit different then on the repo before, I would like to do something like this, but I do not know for sure if
th id itself on the early 8221b equals zero.

static int rtl8221b_vb_cg_match_phy_device(struct phy_device *phydev)
{
	int val;
	u32 id;

	if (phydev->is_c45) {
		if (phydev->c45_ids.device_ids[1] != 0)
			return phydev->c45_ids.device_ids[1] == RTL_8221B_VB_CG;
	} else {
		if (phydev->phy_id != 0)
			return phydev->phy_id == RTL_8221B_VB_CG;
	}

	if (phydev->mdio.bus->read_c45) {
		val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PKGID1);
		if (val < 0)
			return 0;

		id = val << 16;
		val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PKGID2);
		if (val < 0)
			return 0;

		id |= val;
	} else {
		val = phy_read(phydev, MII_PHYSID1);
		if (val < 0)
			return 0;

		id = val << 16;
		val = phy_read(phydev, MII_PHYSID2);
		if (val < 0)
			return 0;

		id |= val;
	}

	if (id != RTL_8221B_VB_CG) return 0;

	if (phydev->is_c45)
		phydev->c45_ids.device_ids[1] = id;
	else
		phydev->phy_id = id;

	return true;
}

To also reduce the times phy_read_mmd() / phy_read() is being used...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment