diff --git a/target/linux/realtek/patches-6.18/030-v7.2-net-phy-realtek-support-MDI-swapping-for-RTL8226-CG.patch b/target/linux/realtek/patches-6.18/030-v7.2-net-phy-realtek-support-MDI-swapping-for-RTL8226-CG.patch new file mode 100644 index 0000000000..c711077409 --- /dev/null +++ b/target/linux/realtek/patches-6.18/030-v7.2-net-phy-realtek-support-MDI-swapping-for-RTL8226-CG.patch @@ -0,0 +1,215 @@ +From 0765570f330f526dd12a966a0a6a25a99da52fb4 Mon Sep 17 00:00:00 2001 +From: Jan Hoffmann +Date: Sat, 16 May 2026 21:03:45 +0200 +Subject: [PATCH] net: phy: realtek: support MDI swapping for RTL8226-CG + +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, but +this implementation still tries to avoid magic numbers and raw register +numbers where it seems clear what is going on. + +As it is unknown whether the patching step can be safely reversed, only +enabling MDI swapping is fully supported. A value of "0" for the "enet- +phy-pair-order" property is not accepted if the PHY has already been +patched for MDI swapping (however, this should not occur in practice). + +Some other Realtek PHYs also support similar mechanisms: + +- RTL8221B-VB-CG allows to configure MDI swapping via the same register, + but does not need the additional patching step. However, it is unclear + whether a driver implementation for that PHY is necessary, as it is + known to support configuration via strapping pins (which is working + fine at least in Zyxel XGS1210-12 rev B1). + +- The patching step seems to match the one for the integrated PHYs of + some Realtek PCIe/USB NICs (see for example the r8152 driver). + +For now, only implement this for the RTL8226-CG PHY, where it is needed +for the switches Zyxel XGS1010-12 rev A1 and XGS1210-12 rev A1. + +Signed-off-by: Jan Hoffmann +Link: https://patch.msgid.link/20260516190456.387768-1-jan@3e8.eu +Signed-off-by: Jakub Kicinski +--- + drivers/net/phy/realtek/realtek_main.c | 154 +++++++++++++++++++++++++ + 1 file changed, 154 insertions(+) + +--- a/drivers/net/phy/realtek/realtek_main.c ++++ b/drivers/net/phy/realtek/realtek_main.c +@@ -181,6 +181,21 @@ + #define RTL8224_VND1_MDI_PAIR_SWAP 0xa90 + #define RTL8224_VND1_MDI_POLARITY_SWAP 0xa94 + ++#define RTL8226_VND1_UNKNOWN_6A21 0x6a21 ++#define RTL8226_VND1_UNKNOWN_6A21_MDI_SWAP_EN BIT(5) ++ ++#define RTL8226_VND2_UNKNOWN_D068 0xd068 ++#define RTL8226_VND2_UNKNOWN_D068_MDI_SWAP_FLAG BIT(1) ++#define RTL8226_VND2_UNKNOWN_D068_PAIR_SEL GENMASK(4, 3) ++#define RTL8226_VND2_ADCCAL_OFFSET 0xd06a ++ ++#define RTL8226_VND2_RG_LPF_CAP_XG_P0_P1 0xbd5a ++#define RTL8226_VND2_RG_LPF_CAP_XG_P2_P3 0xbd5c ++#define RTL8226_VND2_RG_LPF_CAP_P0_P1 0xbc18 ++#define RTL8226_VND2_RG_LPF_CAP_P2_P3 0xbc1a ++#define RTL8226_RG_LPF_CAP_PAIR_A_MASK GENMASK(4, 0) ++#define RTL8226_RG_LPF_CAP_PAIR_B_MASK GENMASK(12, 8) ++ + #define RTL8366RB_POWER_SAVE 0x15 + #define RTL8366RB_POWER_SAVE_ON BIT(12) + +@@ -1391,6 +1406,144 @@ static int rtl822x_init_phycr1(struct ph + mask, val); + } + ++static int rtl8226_set_mdi_swap(struct phy_device *phydev, bool swap_enable) ++{ ++ u16 val = swap_enable ? RTL8226_VND1_UNKNOWN_6A21_MDI_SWAP_EN : 0; ++ ++ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, RTL8226_VND1_UNKNOWN_6A21, ++ RTL8226_VND1_UNKNOWN_6A21_MDI_SWAP_EN, val); ++} ++ ++static int rtl8226_swap_rg_lpf_cap(struct phy_device *phydev, u32 reg_p0_p1, u32 reg_p2_p3) ++{ ++ u16 val_p0, val_p1, val_p2, val_p3; ++ int ret; ++ ++ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, reg_p0_p1); ++ if (ret < 0) ++ return ret; ++ ++ val_p0 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_A_MASK, ret); ++ val_p1 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_B_MASK, ret); ++ ++ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, reg_p2_p3); ++ if (ret < 0) ++ return ret; ++ ++ val_p2 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_A_MASK, ret); ++ val_p3 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_B_MASK, ret); ++ ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, reg_p0_p1, ++ RTL8226_RG_LPF_CAP_PAIR_A_MASK | RTL8226_RG_LPF_CAP_PAIR_B_MASK, ++ FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_A_MASK, val_p3) | ++ FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_B_MASK, val_p2)); ++ if (ret < 0) ++ return ret; ++ ++ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, reg_p2_p3, ++ RTL8226_RG_LPF_CAP_PAIR_A_MASK | RTL8226_RG_LPF_CAP_PAIR_B_MASK, ++ FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_A_MASK, val_p1) | ++ FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_B_MASK, val_p0)); ++} ++ ++static int rtl8226_patch_mdi_swap(struct phy_device *phydev, bool swap_enable) ++{ ++ u16 adccal_offset[4]; ++ bool is_patched; ++ int ret; ++ ++ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068); ++ if (ret < 0) ++ return ret; ++ ++ is_patched = !(ret & RTL8226_VND2_UNKNOWN_D068_MDI_SWAP_FLAG); ++ ++ if (is_patched == swap_enable) { ++ /* Nothing to do */ ++ return 0; ++ } ++ ++ if (!swap_enable) { ++ /* Patching is only implemented one-way, see next comment. */ ++ phydev_err(phydev, "MDI swapping disabled, but PHY is already patched.\n"); ++ return -EINVAL; ++ } ++ ++ /* The exact meaning of these bits is unknown. We only know that bit 1 ++ * is used as a flag that swapping is already done. ++ */ ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068, 0x7, 0x1); ++ if (ret < 0) ++ return ret; ++ ++ for (int i = 0; i < 4; i++) { ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068, ++ RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, ++ FIELD_PREP(RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, i)); ++ if (ret < 0) ++ return ret; ++ ++ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_ADCCAL_OFFSET); ++ if (ret < 0) ++ return ret; ++ ++ adccal_offset[i] = ret; ++ } ++ ++ for (int i = 0; i < 4; i++) { ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068, ++ RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, ++ FIELD_PREP(RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, i)); ++ if (ret < 0) ++ return ret; ++ ++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_ADCCAL_OFFSET, ++ adccal_offset[3 - i]); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = rtl8226_swap_rg_lpf_cap(phydev, RTL8226_VND2_RG_LPF_CAP_XG_P0_P1, ++ RTL8226_VND2_RG_LPF_CAP_XG_P2_P3); ++ if (ret < 0) ++ return ret; ++ ++ return rtl8226_swap_rg_lpf_cap(phydev, RTL8226_VND2_RG_LPF_CAP_P0_P1, ++ RTL8226_VND2_RG_LPF_CAP_P2_P3); ++} ++ ++static int rtl8226_config_mdi_order(struct phy_device *phydev) ++{ ++ u32 order; ++ bool swap_enable; ++ 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 || ret == -ENOSYS) ++ return 0; ++ ++ if (ret) ++ return ret; ++ ++ if (order & ~1) ++ return -EINVAL; ++ ++ swap_enable = !!(order & 1); ++ ++ ret = rtl8226_set_mdi_swap(phydev, swap_enable); ++ if (ret) ++ return ret; ++ ++ return rtl8226_patch_mdi_swap(phydev, swap_enable); ++} ++ ++static int rtl8226_probe(struct phy_device *phydev) ++{ ++ return rtl8226_config_mdi_order(phydev); ++} ++ + static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1) + { + bool has_2500, has_sgmii; +@@ -3083,6 +3236,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, ++ .probe = rtl8226_probe, + .config_init = rtl822x_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, diff --git a/target/linux/realtek/patches-6.18/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch b/target/linux/realtek/patches-6.18/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch deleted file mode 100644 index 35d95874ee..0000000000 --- a/target/linux/realtek/patches-6.18/740-net-phy-realtek-support-MDI-swapping-for-RTL8226.patch +++ /dev/null @@ -1,190 +0,0 @@ -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 -@@ -1514,6 +1514,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) - { -@@ -3083,7 +3225,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, diff --git a/target/linux/realtek/patches-6.18/743-net-realtek-serdes-configuration.patch b/target/linux/realtek/patches-6.18/743-net-realtek-serdes-configuration.patch index d67d2eb2b4..1fc138a465 100644 --- a/target/linux/realtek/patches-6.18/743-net-realtek-serdes-configuration.patch +++ b/target/linux/realtek/patches-6.18/743-net-realtek-serdes-configuration.patch @@ -42,7 +42,7 @@ #define RTL8221B_PHYCR1 0xa430 #define RTL8221B_PHYCR1_ALDPS_EN BIT(2) #define RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN BIT(12) -@@ -2058,6 +2093,147 @@ exit: +@@ -2069,6 +2104,147 @@ exit: return ret; } @@ -190,7 +190,7 @@ static int rtl8224_mdi_config_order(struct phy_device *phydev) { struct device_node *np = phydev->mdio.dev.of_node; -@@ -2112,6 +2288,10 @@ static int rtl8224_config_init(struct ph +@@ -2123,6 +2299,10 @@ static int rtl8224_config_init(struct ph { int ret;