--- /dev/null
+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,