1
1

kernel: realtek: replace RTL8226 MDI swap patch by upstream version

A version of this patch has been accepted upstream, so use it here.

Link: https://lore.kernel.org/netdev/177932162564.3801238.2549776951847746974.git-patchwork-notify@kernel.org/
Signed-off-by: Jan Hoffmann <jan@3e8.eu>
Link: https://github.com/openwrt/openwrt/pull/23493
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
This commit is contained in:
Jan Hoffmann 2026-05-22 21:39:52 +02:00 committed by Jonas Jelonek
parent 9a143bf7ff
commit b20068672b
No known key found for this signature in database
3 changed files with 217 additions and 192 deletions

View File

@ -0,0 +1,215 @@
From 0765570f330f526dd12a966a0a6a25a99da52fb4 Mon Sep 17 00:00:00 2001
From: Jan Hoffmann <jan@3e8.eu>
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 <jan@3e8.eu>
Link: https://patch.msgid.link/20260516190456.387768-1-jan@3e8.eu
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
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,

View File

@ -1,190 +0,0 @@
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
@@ -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,

View File

@ -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;