public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG
@ 2026-03-27 19:32 Jan Hoffmann
  2026-03-31 13:24 ` Paolo Abeni
  2026-03-31 13:38 ` Russell King (Oracle)
  0 siblings, 2 replies; 4+ messages in thread
From: Jan Hoffmann @ 2026-03-27 19:32 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Daniel Golle,
	Markus Stockhausen, Damien Dejean
  Cc: netdev, linux-kernel, Jan Hoffmann

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
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>
---
v2:
 - Added register/field definitions where possible. For some registers,
   it seems hard to find a proper name, but I still defined them where
   some of the fields are known.
 - Introduced new helper function to swap RG_LPF_CAP and RG_LPF_CAP_XG
   values, in order to reduce duplication.

 drivers/net/phy/realtek/realtek_main.c | 149 ++++++++++++++++++++++++-
 1 file changed, 148 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 023e47ad605b..2f007dc8ec8c 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -174,6 +174,20 @@
 #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_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)
 
@@ -1447,6 +1461,139 @@ 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 ? 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)
+{
+	u16 adccal_offset[4];
+	int ret;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068);
+	if (ret < 0)
+		return ret;
+
+	if (!(ret & BIT(1))) {
+		/* already swapped */
+		return 0;
+	}
+
+	/* 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;
+	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 +2531,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


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG
  2026-03-27 19:32 [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG Jan Hoffmann
@ 2026-03-31 13:24 ` Paolo Abeni
  2026-03-31 13:38 ` Russell King (Oracle)
  1 sibling, 0 replies; 4+ messages in thread
From: Paolo Abeni @ 2026-03-31 13:24 UTC (permalink / raw)
  To: Jan Hoffmann, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Daniel Golle,
	Markus Stockhausen, Damien Dejean
  Cc: netdev, linux-kernel

On 3/27/26 8:32 PM, Jan Hoffmann wrote:
> +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;

I guess some existing setup may have "enet-phy-pair-order" == 0, and the
above will make them fail. I think you should just pass with no action
in such a case.

/P


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG
  2026-03-27 19:32 [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG Jan Hoffmann
  2026-03-31 13:24 ` Paolo Abeni
@ 2026-03-31 13:38 ` Russell King (Oracle)
  2026-04-03 18:49   ` Jan Hoffmann
  1 sibling, 1 reply; 4+ messages in thread
From: Russell King (Oracle) @ 2026-03-31 13:38 UTC (permalink / raw)
  To: Jan Hoffmann
  Cc: Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Daniel Golle, Markus Stockhausen,
	Damien Dejean, netdev, linux-kernel

On Fri, Mar 27, 2026 at 08:32:28PM +0100, Jan Hoffmann wrote:
> +static int rtl8226_config_init(struct phy_device *phydev)
> +{
> +	int ret;
> +
> +	ret = rtl8226_config_mdi_order(phydev);
> +	if (ret)
> +		return ret;

As I've been pointing out in other patches, putting this "defaulting"
here is not a good idea - this path gets called when the PHY is attached
to the netdev and also when the PHY is being resumed - which will wipe
out the user's configuration just because their system suspended.

I don't think that gives the user a good experience.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG
  2026-03-31 13:38 ` Russell King (Oracle)
@ 2026-04-03 18:49   ` Jan Hoffmann
  0 siblings, 0 replies; 4+ messages in thread
From: Jan Hoffmann @ 2026-04-03 18:49 UTC (permalink / raw)
  To: Russell King (Oracle)
  Cc: Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Daniel Golle, Markus Stockhausen,
	Damien Dejean, netdev, linux-kernel

Hi Russell,

On 2026-03-31 at 15:38, Russell King (Oracle) wrote:
> On Fri, Mar 27, 2026 at 08:32:28PM +0100, Jan Hoffmann wrote:
>> +static int rtl8226_config_init(struct phy_device *phydev)
>> +{
>> +	int ret;
>> +
>> +	ret = rtl8226_config_mdi_order(phydev);
>> +	if (ret)
>> +		return ret;
> 
> As I've been pointing out in other patches, putting this "defaulting"
> here is not a good idea - this path gets called when the PHY is attached
> to the netdev and also when the PHY is being resumed - which will wipe
> out the user's configuration just because their system suspended.
> 
> I don't think that gives the user a good experience.

In this case, there is no user configuration which could be overwritten. 
Whether MDI swapping needs to be enabled depends only on the board 
layout (the feature exists to avoid PCB traces having to cross).

But there is indeed no need to configure it on every resume, just once 
should be enough. Do I understand correctly that .probe would be the 
right place to do this then?

Thanks,
Jan

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-04-03 18:50 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-27 19:32 [PATCH net-next v2] net: phy: realtek: support MDI swapping for RTL8226-CG Jan Hoffmann
2026-03-31 13:24 ` Paolo Abeni
2026-03-31 13:38 ` Russell King (Oracle)
2026-04-03 18:49   ` Jan Hoffmann

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox