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 index 0000000000..19d5df2202 --- /dev/null +++ b/target/linux/realtek/patches-6.12/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch @@ -0,0 +1,190 @@ +From 672a9bfb2e01ecaf40e5b92e9cc564589ffc251d Mon Sep 17 00:00:00 2001 +From: Jan Hoffmann +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 +--- + 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,