1
0
Fork 0
forked from mirror/openwrt

realtek: support MDI swapping for RTL8226 PHY

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>
This commit is contained in:
Jan Hoffmann 2025-12-23 20:38:31 +01:00 committed by Hauke Mehrtens
parent eda2630ceb
commit acedb7e2d3

View file

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