]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: support MDI swapping for RTL8226 PHY
authorJan Hoffmann <jan@3e8.eu>
Tue, 23 Dec 2025 19:38:31 +0000 (20:38 +0100)
committerHauke Mehrtens <hauke@hauke-m.de>
Sat, 14 Mar 2026 23:08:32 +0000 (00:08 +0100)
The PHY supports swapping the MDI pairs (ABCD->DCBA) to simplify board
layout. On devices making use of this, it needs to be configured in the
driver, otherwise the PHY won't work properly.

Signed-off-by: Jan Hoffmann <jan@3e8.eu>
Link: https://github.com/openwrt/openwrt/pull/21261
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
target/linux/realtek/patches-6.12/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch [new file with mode: 0644]

diff --git a/target/linux/realtek/patches-6.12/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch b/target/linux/realtek/patches-6.12/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch
new file mode 100644 (file)
index 0000000..19d5df2
--- /dev/null
@@ -0,0 +1,190 @@
+From 672a9bfb2e01ecaf40e5b92e9cc564589ffc251d Mon Sep 17 00:00:00 2001
+From: Jan Hoffmann <jan@3e8.eu>
+Date: Tue, 23 Dec 2025 20:07:53 +0100
+Subject: [PATCH] net: phy: realtek: support MDI swapping for RTL8226
+
+Add support for configuring swapping of MDI pairs (ABCD->DCBA) when the
+property "enet-phy-pair-order" is specified.
+
+Unfortunately, no documentation about this feature is available, so the
+configuration involves magic values. Only enabling MDI swapping is
+supported, as it is unknown whether the patching step can be safely
+reversed.
+
+For now, only implement it for RTL8226, where it is needed to make the
+PHYs in Zyxel XGS1010-12 rev A1 work. However, parts of this code might
+also be useful for other PHYs in the future:
+
+RTL8221B also allows to configure MDI swapping via the same register,
+but does not need the additional patching step. Since it also supports
+configuration via strapping pins, there might not be any need for driver
+support on that PHY, though.
+
+The patching step itself seems to be the same which is also used by the
+integrated PHY of some Realtek PCIe/USB NICs.
+
+Signed-off-by: Jan Hoffmann <jan@3e8.eu>
+---
+ drivers/net/phy/realtek/realtek_main.c | 159 ++++++++++++++++++++++++-
+ 1 file changed, 158 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1486,6 +1486,148 @@ static unsigned int rtl822x_inband_caps(
+       }
+ }
++static int rtl8226_set_mdi_swap(struct phy_device *phydev, bool swap_enable)
++{
++      u16 val = swap_enable ? BIT(5) : 0;
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND1, 0x6a21, BIT(5), val);
++}
++
++static int rtl8226_patch_mdi_swap(struct phy_device *phydev)
++{
++      int ret;
++      u16 vals[4];
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xd068);
++      if (ret < 0)
++              return ret;
++
++      if (!(ret & BIT(1))) {
++              /* already swapped */
++              return 0;
++      }
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x7, 0x1);
++      if (ret < 0)
++              return ret;
++
++      /* swap adccal_offset */
++
++      for (int i = 0; i < 4; i++) {
++              ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x3 << 3, i << 3);
++              if (ret < 0)
++                      return ret;
++
++              ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xd06a);
++              if (ret < 0)
++                      return ret;
++
++              vals[i] = ret;
++      }
++
++      for (int i = 0; i < 4; i++) {
++              ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x3 << 3, i << 3);
++              if (ret < 0)
++                      return ret;
++
++              ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xd06a, vals[3 - i]);
++              if (ret < 0)
++                      return ret;
++      }
++
++      /* swap rg_lpf_cap_xg */
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbd5a);
++      if (ret < 0)
++              return ret;
++
++      vals[0] = ret & 0x1f;
++      vals[1] = (ret >> 8) & 0x1f;
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c);
++      if (ret < 0)
++              return ret;
++
++      vals[2] = ret & 0x1f;
++      vals[3] = (ret >> 8) & 0x1f;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5a, 0x1f1f,
++              vals[3] | (vals[2] << 8));
++      if (ret < 0)
++              return ret;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c, 0x1f1f,
++              vals[1] | (vals[0] << 8));
++      if (ret < 0)
++              return ret;
++
++      /* swap rg_lpf_cap */
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbc18);
++      if (ret < 0)
++              return ret;
++
++      vals[0] = ret & 0x1f;
++      vals[1] = (ret >> 8) & 0x1f;
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbc1a);
++      if (ret < 0)
++              return ret;
++
++      vals[2] = ret & 0x1f;
++      vals[3] = (ret >> 8) & 0x1f;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc18, 0x1f1f,
++              vals[3] | (vals[2] << 8));
++      if (ret < 0)
++              return ret;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc1a, 0x1f1f,
++              vals[1] | (vals[0] << 8));
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++static int rtl8226_config_mdi_order(struct phy_device *phydev)
++{
++      u32 order;
++      int ret;
++
++      ret = of_property_read_u32(phydev->mdio.dev.of_node, "enet-phy-pair-order", &order);
++
++      /* Property not present, nothing to do */
++      if (ret == -EINVAL)
++              return 0;
++
++      if (ret)
++              return ret;
++
++      /* Only enabling MDI swapping is supported */
++      if (order != 1)
++              return -EINVAL;
++
++      ret = rtl8226_set_mdi_swap(phydev, true);
++      if (ret)
++              return ret;
++
++      ret = rtl8226_patch_mdi_swap(phydev);
++      return ret;
++}
++
++static int rtl8226_config_init(struct phy_device *phydev)
++{
++      int ret;
++
++      ret = rtl8226_config_mdi_order(phydev);
++      if (ret)
++              return ret;
++
++      return rtl822x_config_init(phydev);
++}
++
++
+ static int rtl822xb_get_rate_matching(struct phy_device *phydev,
+                                     phy_interface_t iface)
+ {
+@@ -2358,7 +2500,7 @@ static struct phy_driver realtek_drvs[]
+               .soft_reset     = rtl822x_c45_soft_reset,
+               .get_features   = rtl822x_c45_get_features,
+               .config_aneg    = rtl822x_c45_config_aneg,
+-              .config_init    = rtl822x_config_init,
++              .config_init    = rtl8226_config_init,
+               .inband_caps    = rtl822x_inband_caps,
+               .config_inband  = rtl822x_config_inband,
+               .read_status    = rtl822xb_c45_read_status,