[PATCH v4 1/3] net: dsa: microchip: implement KSZ87xx Module 3 low-loss cable errata
From: Fidelio Lawson
Date: Fri Apr 17 2026 - 08:45:26 EST
Implement the "Module 3: Equalizer fix for short cables" erratum from
Microchip document DS80000687C for KSZ87xx switches.
The issue affects short or low-loss cable links (e.g. CAT5e/CAT6),
where the PHY receiver equalizer may amplify high-amplitude signals
excessively, resulting in internal distortion and link establishment
failures.
KSZ87xx devices require a workaround for the Module 3 low-loss cable
condition, controlled through the switch TABLE_LINK_MD_V indirect
registers.
This change models the erratum handling as vendor-specific Clause 22 PHY
registers, virtualized by the KSZ8 DSA driver and accessed via
ksz8_r_phy() / ksz8_w_phy(). The following controls are provided:
- A boolean “short-cable” preset, which applies a documented and
conservative configuration (LPF 62 MHz bandwidth and DSP EQ initial
value 0), and is the recommended interface for typical use cases.
- Separate LPF bandwidth and DSP EQ initial value controls intended for
advanced or experimental tuning. These are orthogonal and independent,
and override the corresponding settings without requiring any specific
ordering.
The preset and tunables act as simple setters with no implicit state
machine or invalid combinations, keeping the API predictable and aligned
with the KISS principle.
The erratum affects the shared PHY analog front-end and therefore applies
globally to the switch.
Signed-off-by: Fidelio Lawson <fidelio.lawson@xxxxxxxxxx>
---
drivers/net/dsa/microchip/ksz8.c | 67 ++++++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8.h | 1 +
drivers/net/dsa/microchip/ksz8_reg.h | 21 ++++++++++-
drivers/net/dsa/microchip/ksz_common.h | 4 ++
4 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index c354abdafc1b..0f2b8acee80f 100644
--- a/drivers/net/dsa/microchip/ksz8.c
+++ b/drivers/net/dsa/microchip/ksz8.c
@@ -1058,6 +1058,22 @@ int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
if (ret)
return ret;
+ break;
+ case PHY_REG_KSZ87XX_SHORT_CABLE:
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ data = !!(dev->lpf_bw == KSZ87XX_PHY_LPF_62MHZ &&
+ dev->eq_init == KSZ87XX_DSP_EQ_INIT_LOW_LOSS);
+ break;
+ case PHY_REG_KSZ87XX_LPF_BW:
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ data = dev->lpf_bw;
+ break;
+ case PHY_REG_KSZ87XX_EQ_INIT:
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ data = dev->eq_init;
break;
default:
processed = false;
@@ -1271,6 +1287,29 @@ int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
if (ret)
return ret;
break;
+ case PHY_REG_KSZ87XX_SHORT_CABLE:
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ ret = ksz87xx_apply_low_loss_preset(dev, !!val);
+ if (ret)
+ return ret;
+ break;
+ case PHY_REG_KSZ87XX_LPF_BW:
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ ret = ksz8_ind_write8(dev, TABLE_LINK_MD, KSZ87XX_REG_PHY_LPF, (u8)val);
+ if (ret)
+ return ret;
+ dev->lpf_bw = val;
+ break;
+ case PHY_REG_KSZ87XX_EQ_INIT:
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ ret = ksz8_ind_write8(dev, TABLE_LINK_MD, KSZ87XX_REG_DSP_EQ, (u8)val);
+ if (ret)
+ return ret;
+ dev->eq_init = val;
+ break;
default:
break;
}
@@ -2096,11 +2135,39 @@ int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
return 0;
}
+int ksz87xx_apply_low_loss_preset(struct ksz_device *dev, bool enable)
+{
+ /* Apply the Microchip erratum short-cable preset (LPF 62 MHz, EQ init 0) */
+ /* providing a conservative configuration for short or low-loss cables. */
+ u8 lpf_bw, eq_init;
+ int ret;
+
+ lpf_bw = KSZ87XX_PHY_LPF_62MHZ;
+ eq_init = KSZ87XX_DSP_EQ_INIT_LOW_LOSS;
+
+ if (!ksz_is_ksz87xx(dev))
+ return -EOPNOTSUPP;
+ if (!enable)
+ return 0;
+ ret = ksz8_ind_write8(dev, TABLE_LINK_MD, KSZ87XX_REG_PHY_LPF, lpf_bw);
+ if (ret)
+ return ret;
+ dev->lpf_bw = lpf_bw;
+ ret = ksz8_ind_write8(dev, TABLE_LINK_MD, KSZ87XX_REG_DSP_EQ, eq_init);
+ if (ret)
+ return ret;
+ dev->eq_init = eq_init;
+
+ return ret;
+}
+
int ksz8_switch_init(struct ksz_device *dev)
{
dev->cpu_port = fls(dev->info->cpu_ports) - 1;
dev->phy_port_cnt = dev->info->port_cnt - 1;
dev->port_mask = (BIT(dev->phy_port_cnt) - 1) | dev->info->cpu_ports;
+ dev->lpf_bw = KSZ87XX_PHY_LPF_90MHZ;
+ dev->eq_init = KSZ87XX_DSP_EQ_INIT_FACTORY;
return 0;
}
diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h
index 0f2cd1474b44..5cf7bd90af0f 100644
--- a/drivers/net/dsa/microchip/ksz8.h
+++ b/drivers/net/dsa/microchip/ksz8.h
@@ -66,5 +66,6 @@ int ksz8_all_queues_split(struct ksz_device *dev, int queues);
u32 ksz8463_get_port_addr(int port, int offset);
int ksz8463_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val);
int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val);
+int ksz87xx_apply_low_loss_preset(struct ksz_device *dev, bool enable);
#endif
diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h
index 332408567b47..5df17c463f7c 100644
--- a/drivers/net/dsa/microchip/ksz8_reg.h
+++ b/drivers/net/dsa/microchip/ksz8_reg.h
@@ -202,6 +202,10 @@
#define REG_PORT_3_STATUS_0 0x38
#define REG_PORT_4_STATUS_0 0x48
+/* KSZ87xx LinkMD registers (TABLE_LINK_MD_V) */
+#define KSZ87XX_REG_DSP_EQ 0x08 /* DSP EQ initial value */
+#define KSZ87XX_REG_PHY_LPF 0x4C /* RX LPF bandwidth */
+
/* For KSZ8765. */
#define PORT_REMOTE_ASYM_PAUSE BIT(5)
#define PORT_REMOTE_SYM_PAUSE BIT(4)
@@ -342,7 +346,7 @@
#define TABLE_EEE (TABLE_EEE_V << TABLE_EXT_SELECT_S)
#define TABLE_ACL (TABLE_ACL_V << TABLE_EXT_SELECT_S)
#define TABLE_PME (TABLE_PME_V << TABLE_EXT_SELECT_S)
-#define TABLE_LINK_MD (TABLE_LINK_MD << TABLE_EXT_SELECT_S)
+#define TABLE_LINK_MD (TABLE_LINK_MD_V << TABLE_EXT_SELECT_S)
#define TABLE_READ BIT(4)
#define TABLE_SELECT_S 2
#define TABLE_STATIC_MAC_V 0
@@ -729,6 +733,21 @@
#define PHY_POWER_SAVING_ENABLE BIT(2)
#define PHY_REMOTE_LOOPBACK BIT(1)
+/* Vendor-specific Clause 22 PHY registers (virtualized) */
+#define PHY_REG_KSZ87XX_SHORT_CABLE 0x1A
+#define PHY_REG_KSZ87XX_LPF_BW 0x1B
+#define PHY_REG_KSZ87XX_EQ_INIT 0x1C
+
+/* LPF bandwidth bits [7:6]: 00 = 90MHz (default), 01 = 62MHz, 10 = 55MHz, 11 = 44MHz */
+#define KSZ87XX_PHY_LPF_90MHZ 0x00
+#define KSZ87XX_PHY_LPF_62MHZ 0x40
+#define KSZ87XX_PHY_LPF_55MHZ 0x80
+#define KSZ87XX_PHY_LPF_44MHZ 0xC0
+
+/* Low-loss workaround DSP EQ INIT VALUE */
+#define KSZ87XX_DSP_EQ_INIT_LOW_LOSS 0x00
+#define KSZ87XX_DSP_EQ_INIT_FACTORY 0x0F
+
/* KSZ8463 specific registers. */
#define P1MBCR 0x4C
#define P1MBSR 0x4E
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index 929aff4c55de..482e79cf6ae6 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -219,6 +219,10 @@ struct ksz_device {
* the switch’s internal PHYs, bypassing the main SPI interface.
*/
struct mii_bus *parent_mdio_bus;
+
+ /* KSZ87xx low-loss tuning state */
+ u8 lpf_bw; /* KSZ87XX_PHY_LPF_* */
+ u8 eq_init; /* DSP EQ initial value */
};
/* List of supported models */
--
2.53.0