mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
Merge branch 'ksz87xx-add-support-for-low-loss-cable-equalizer-errata'
Fidelio Lawson says: ==================== ksz87xx: add support for low-loss cable equalizer errata This patch implements the KSZ87xx short cable erratum described in Microchip document DS80000687C for KSZ87xx switches and the following support article: Link: https://support.microchip.com/s/article/Solution-for-Using-CAT-5E-or-CAT-6-Short-Cable-with-a-Link-Issue-for-the-KSZ8795-Family According to the erratum, the embedded PHY receiver in KSZ87xx switches is tuned by default for long, high-loss Ethernet cables. When operating with short or low-loss cables (for example CAT5e or CAT6), the PHY equalizer may over-amplify the incoming signal, leading to internal distortion and link establishment failures. Microchip documents two independent mechanisms to mitigate this issue: adjusting the receiver low‑pass filter bandwidth and reducing the DSP equalizer initial value. These registers are located in the switch’s internal LinkMD table and cannot be accessed directly through a stand‑alone PHY driver. To keep the PHY‑facing API clean, this series models the erratum handling as vendor‑specific Clause 22 PHY registers, virtualized by the KSZ8 DSA driver. Accesses are intercepted by ksz8_r_phy() / ksz8_w_phy() and translated into the appropriate indirect LinkMD register writes. The erratum affects the shared PHY analog front‑end and therefore applies globally to the switch. Based on review feedback, the user‑visible interface is kept deliberately simple and predictable: - A boolean “short‑cable” PHY tunable applies a documented and conservative preset (LPF bandwidth 62MHz, DSP EQ initial value 0). This is the recommended KISS interface for the common short‑cable scenario. - Two additional integer PHY tunables allow advanced or experimental tuning of the LPF bandwidth and the DSP EQ initial value. These controls are orthogonal, have no ordering requirements, and simply override the corresponding setting when written. The tunables act as simple setters with no implicit state machine or invalid combinations, avoiding surprises for userspace and not relying on extended error reporting or netlink ethtool support. This series contains: 1. Support for the KSZ87xx low‑loss cable erratum in the KSZ8 DSA driver, including the short‑cable preset and orthogonal tuning controls. 2. Addition of vendor‑specific PHY tunable identifiers for the short‑cable preset, LPF bandwidth, and DSP EQ initial value. 3. Exposure of these tunables through the Micrel PHY driver via get_tunable / set_tunable callbacks. This version follows the design agreed upon during v3 review and reworks the interface accordingly. ==================== Link: https://patch.msgid.link/20260609-ksz87xx_errata_low_loss_connections-v10-0-9ba4418cf3db@exotec.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
e9bcf842b3
|
|
@ -798,6 +798,40 @@ static int ksz879x_set_loopback(struct ksz_device *dev, u16 port, u16 val)
|
|||
stat3);
|
||||
}
|
||||
|
||||
static 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) {
|
||||
/* Restore default values (LPF 90 MHz, EQ init 15). */
|
||||
lpf_bw = KSZ87XX_PHY_LPF_90MHZ;
|
||||
eq_init = KSZ87XX_DSP_EQ_INIT_FACTORY;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ksz8_r_phy_ctrl - Translates and reads from the SMI interface to a MIIM PHY
|
||||
* Control register (Reg. 31).
|
||||
|
|
@ -1046,6 +1080,22 @@ static 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;
|
||||
|
|
@ -1272,6 +1322,41 @@ static 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;
|
||||
dev_info_once(dev->dev,
|
||||
"KSZ87xx low-loss tuning is global, applied switch-wide\n");
|
||||
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;
|
||||
dev_info_once(dev->dev,
|
||||
"KSZ87xx low-loss tuning is global, applied switch-wide\n");
|
||||
/* Only accept LPF bandwidth bits [7:6] */
|
||||
if (val & ~KSZ87XX_PHY_LPF_MASK)
|
||||
return -EINVAL;
|
||||
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;
|
||||
dev_info_once(dev->dev,
|
||||
"KSZ87xx low-loss tuning is global, applied switch-wide\n");
|
||||
/* Only accept DSP EQ initial value bits [5:0] */
|
||||
if (val & ~KSZ87XX_DSP_EQ_VALID_MASK)
|
||||
return -EINVAL;
|
||||
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;
|
||||
}
|
||||
|
|
@ -2070,6 +2155,10 @@ static int ksz8_setup(struct dsa_switch *ds)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Initialize KSZ87xx short-cable preset control */
|
||||
dev->eq_init = KSZ87XX_DSP_EQ_INIT_FACTORY;
|
||||
dev->lpf_bw = KSZ87XX_PHY_LPF_90MHZ;
|
||||
|
||||
ret = ksz8_handle_global_errata(ds);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -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,23 @@
|
|||
#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_MASK GENMASK(7, 6)
|
||||
#define KSZ87XX_PHY_LPF_90MHZ FIELD_PREP(KSZ87XX_PHY_LPF_MASK, 0)
|
||||
#define KSZ87XX_PHY_LPF_62MHZ FIELD_PREP(KSZ87XX_PHY_LPF_MASK, 1)
|
||||
#define KSZ87XX_PHY_LPF_55MHZ FIELD_PREP(KSZ87XX_PHY_LPF_MASK, 2)
|
||||
#define KSZ87XX_PHY_LPF_44MHZ FIELD_PREP(KSZ87XX_PHY_LPF_MASK, 3)
|
||||
|
||||
/* Low-loss workaround DSP EQ INIT VALUE */
|
||||
#define KSZ87XX_DSP_EQ_VALID_MASK GENMASK(5, 0)
|
||||
#define KSZ87XX_DSP_EQ_INIT_LOW_LOSS 0x00
|
||||
#define KSZ87XX_DSP_EQ_INIT_FACTORY 0x0F
|
||||
|
||||
/* KSZ8463 specific registers. */
|
||||
#define P1MBCR 0x4C
|
||||
#define P1MBSR 0x4E
|
||||
|
|
|
|||
|
|
@ -222,6 +222,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 */
|
||||
|
|
|
|||
|
|
@ -287,6 +287,12 @@
|
|||
/* PHY Control 2 / PHY Control (if no PHY Control 1) */
|
||||
#define MII_KSZPHY_CTRL_2 0x1f
|
||||
#define MII_KSZPHY_CTRL MII_KSZPHY_CTRL_2
|
||||
|
||||
/* Vendor-specific Clause 22 register, virtualized by KSZ87xx embedded PHYs DSA driver */
|
||||
#define MII_KSZ87XX_SHORT_CABLE 0x1a
|
||||
#define MII_KSZ87XX_LPF_BW 0x1b
|
||||
#define MII_KSZ87XX_EQ_INIT 0x1c
|
||||
|
||||
/* bitmap of PHY register to set interrupt mode */
|
||||
#define KSZ8081_CTRL2_HP_MDIX BIT(15)
|
||||
#define KSZ8081_CTRL2_MDI_MDI_X_SELECT BIT(14)
|
||||
|
|
@ -940,6 +946,59 @@ static int ksz8795_match_phy_device(struct phy_device *phydev,
|
|||
return ksz8051_ksz8795_match_phy_device(phydev, false);
|
||||
}
|
||||
|
||||
static int ksz8795_get_tunable(struct phy_device *phydev,
|
||||
struct ethtool_tunable *tuna, void *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (tuna->id) {
|
||||
case ETHTOOL_PHY_SHORT_CABLE_PRESET:
|
||||
ret = phy_read(phydev, MII_KSZ87XX_SHORT_CABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*(u8 *)data = ret;
|
||||
return 0;
|
||||
case ETHTOOL_PHY_LPF_BW:
|
||||
ret = phy_read(phydev, MII_KSZ87XX_LPF_BW);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*(u32 *)data = ret & 0xff;
|
||||
return 0;
|
||||
case ETHTOOL_PHY_DSP_EQ_INIT_VALUE:
|
||||
ret = phy_read(phydev, MII_KSZ87XX_EQ_INIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*(u32 *)data = ret & 0xff;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int ksz8795_set_tunable(struct phy_device *phydev,
|
||||
struct ethtool_tunable *tuna, const void *data)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
switch (tuna->id) {
|
||||
case ETHTOOL_PHY_SHORT_CABLE_PRESET:
|
||||
return phy_write(phydev, MII_KSZ87XX_SHORT_CABLE,
|
||||
*(const u8 *)data);
|
||||
case ETHTOOL_PHY_LPF_BW:
|
||||
val = *(const u32 *)data;
|
||||
if (val > 0xff)
|
||||
return -EINVAL;
|
||||
return phy_write(phydev, MII_KSZ87XX_LPF_BW, (u8)val);
|
||||
case ETHTOOL_PHY_DSP_EQ_INIT_VALUE:
|
||||
val = *(const u32 *)data;
|
||||
if (val > 0xff)
|
||||
return -EINVAL;
|
||||
return phy_write(phydev, MII_KSZ87XX_EQ_INIT, (u8)val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int ksz9021_load_values_from_of(struct phy_device *phydev,
|
||||
const struct device_node *of_node,
|
||||
u16 reg,
|
||||
|
|
@ -6961,6 +7020,8 @@ static struct phy_driver ksphy_driver[] = {
|
|||
/* PHY_BASIC_FEATURES */
|
||||
.config_init = kszphy_config_init,
|
||||
.match_phy_device = ksz8795_match_phy_device,
|
||||
.get_tunable = ksz8795_get_tunable,
|
||||
.set_tunable = ksz8795_set_tunable,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
}, {
|
||||
|
|
|
|||
|
|
@ -291,6 +291,9 @@ enum phy_tunable_id {
|
|||
ETHTOOL_PHY_DOWNSHIFT,
|
||||
ETHTOOL_PHY_FAST_LINK_DOWN,
|
||||
ETHTOOL_PHY_EDPD,
|
||||
ETHTOOL_PHY_SHORT_CABLE_PRESET,
|
||||
ETHTOOL_PHY_LPF_BW,
|
||||
ETHTOOL_PHY_DSP_EQ_INIT_VALUE,
|
||||
/*
|
||||
* Add your fresh new phy tunable attribute above and remember to update
|
||||
* phy_tunable_strings[] in net/ethtool/common.c
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
|
|||
[ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift",
|
||||
[ETHTOOL_PHY_FAST_LINK_DOWN] = "phy-fast-link-down",
|
||||
[ETHTOOL_PHY_EDPD] = "phy-energy-detect-power-down",
|
||||
[ETHTOOL_PHY_SHORT_CABLE_PRESET] = "phy-short-cable-preset",
|
||||
[ETHTOOL_PHY_LPF_BW] = "phy-lpf-bandwidth",
|
||||
[ETHTOOL_PHY_DSP_EQ_INIT_VALUE] = "phy-dsp-eq-init-value",
|
||||
};
|
||||
|
||||
#define __LINK_MODE_NAME(speed, type, duplex) \
|
||||
|
|
|
|||
|
|
@ -3133,6 +3133,7 @@ static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna)
|
|||
switch (tuna->id) {
|
||||
case ETHTOOL_PHY_DOWNSHIFT:
|
||||
case ETHTOOL_PHY_FAST_LINK_DOWN:
|
||||
case ETHTOOL_PHY_SHORT_CABLE_PRESET:
|
||||
if (tuna->len != sizeof(u8) ||
|
||||
tuna->type_id != ETHTOOL_TUNABLE_U8)
|
||||
return -EINVAL;
|
||||
|
|
@ -3142,6 +3143,12 @@ static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna)
|
|||
tuna->type_id != ETHTOOL_TUNABLE_U16)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case ETHTOOL_PHY_LPF_BW:
|
||||
case ETHTOOL_PHY_DSP_EQ_INIT_VALUE:
|
||||
if (tuna->len != sizeof(u32) ||
|
||||
tuna->type_id != ETHTOOL_TUNABLE_U32)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user