summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/dsa/microchip/ksz8.c89
-rw-r--r--drivers/net/dsa/microchip/ksz8_reg.h23
-rw-r--r--drivers/net/dsa/microchip/ksz_common.h4
-rw-r--r--drivers/net/phy/micrel.c61
-rw-r--r--include/uapi/linux/ethtool.h3
-rw-r--r--net/ethtool/common.c3
-rw-r--r--net/ethtool/ioctl.c7
7 files changed, 189 insertions, 1 deletions
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index 0f84b2e7d25d..4ec2d5d2a1bc 100644
--- a/drivers/net/dsa/microchip/ksz8.c
+++ b/drivers/net/dsa/microchip/ksz8.c
@@ -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).
@@ -1047,6 +1081,22 @@ static int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
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;
break;
@@ -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;
diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h
index 332408567b47..981ab441d9b7 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,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
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index b67038cf1bd0..45613b9aeb1e 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -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 */
diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c
index e211a523c258..55df5efcfc86 100644
--- a/drivers/net/phy/micrel.c
+++ b/drivers/net/phy/micrel.c
@@ -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,
}, {
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 1cdfb8341df2..a2091d4e00f3 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -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
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 84ec88dee05c..a24d3a8a9ec1 100644
--- a/net/ethtool/common.c
+++ b/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) \
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index a7bff829b758..4b0bc503f930 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -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;
}