[PATCH net-next] net: phy: realtek: support MDI swapping for RTL8226-CG

Jan Hoffmann posted 1 patch 1 week, 5 days ago
There is a newer version of this series
drivers/net/phy/realtek/realtek_main.c | 142 ++++++++++++++++++++++++-
1 file changed, 141 insertions(+), 1 deletion(-)
[PATCH net-next] net: phy: realtek: support MDI swapping for RTL8226-CG
Posted by Jan Hoffmann 1 week, 5 days ago
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. As it is unknown whether the
patching step can be safely reversed, only enabling MDI swapping is
supported.

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>
---
 drivers/net/phy/realtek/realtek_main.c | 142 ++++++++++++++++++++++++-
 1 file changed, 141 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 023e47ad605b..2472f54502bd 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1447,6 +1447,146 @@ static unsigned int rtl822x_inband_caps(struct phy_device *phydev,
 	}
 }
 
+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)
+{
+	u16 values[4];
+	int ret;
+
+	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;
+
+		values[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, values[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;
+
+	values[0] = ret & 0x1f;
+	values[1] = (ret >> 8) & 0x1f;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c);
+	if (ret < 0)
+		return ret;
+
+	values[2] = ret & 0x1f;
+	values[3] = (ret >> 8) & 0x1f;
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5a, 0x1f1f,
+			     values[3] | (values[2] << 8));
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c, 0x1f1f,
+			     values[1] | (values[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;
+
+	values[0] = ret & 0x1f;
+	values[1] = (ret >> 8) & 0x1f;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbc1a);
+	if (ret < 0)
+		return ret;
+
+	values[2] = ret & 0x1f;
+	values[3] = (ret >> 8) & 0x1f;
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc18, 0x1f1f,
+			     values[3] | (values[2] << 8));
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc1a, 0x1f1f,
+			     values[1] | (values[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 || ret == -ENOSYS)
+		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;
+
+	return rtl8226_patch_mdi_swap(phydev);
+}
+
+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)
 {
@@ -2384,7 +2524,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,
-- 
2.53.0
AW: [PATCH net-next] net: phy: realtek: support MDI swapping for RTL8226-CG
Posted by markus.stockhausen@gmx.de 6 days, 1 hour ago
> Von: Jan Hoffmann <jan@3e8.eu> 
> Betreff: [PATCH net-next] 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.
> ...
>	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x7, 0x1);

No real knowledge here. But from other register combinations around 
the Realtek ecosystem this could make sense:

#define RTL822X_MDI_CAL_CTRL    0xd068   /* MDI pair cal index/control */
#define RTL822X_MDI_CAL_DATA    0xd06a   /* MDI pair cal data */

Markus
Re: [PATCH net-next] net: phy: realtek: support MDI swapping for RTL8226-CG
Posted by Paolo Abeni 1 week, 1 day ago
On 3/22/26 8:31 PM, Jan Hoffmann wrote:
> 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. As it is unknown whether the
> patching step can be safely reversed, only enabling MDI swapping is
> supported.

Still using human readable macros would improve readability, reduce the
change of c&p mistakes. I suggest you use chose some more explanatory
names at least for the more used values.

> 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>
> ---
>  drivers/net/phy/realtek/realtek_main.c | 142 ++++++++++++++++++++++++-
>  1 file changed, 141 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
> index 023e47ad605b..2472f54502bd 100644
> --- a/drivers/net/phy/realtek/realtek_main.c
> +++ b/drivers/net/phy/realtek/realtek_main.c
> @@ -1447,6 +1447,146 @@ static unsigned int rtl822x_inband_caps(struct phy_device *phydev,
>  	}
>  }
>  
> +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)
> +{
> +	u16 values[4];
> +	int ret;
> +
> +	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;
> +
> +		values[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, values[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;
> +
> +	values[0] = ret & 0x1f;
> +	values[1] = (ret >> 8) & 0x1f;
> +
> +	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c);
> +	if (ret < 0)
> +		return ret;
> +
> +	values[2] = ret & 0x1f;
> +	values[3] = (ret >> 8) & 0x1f;
> +
> +	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5a, 0x1f1f,
> +			     values[3] | (values[2] << 8));

It looks like there are a few FIELD_GET/FIELD_PREP use cases above
(which will work better together with define for relevant values).

/P