diff options
Diffstat (limited to 'drivers/net/phy/micrel.c')
-rw-r--r-- | drivers/net/phy/micrel.c | 255 |
1 files changed, 252 insertions, 3 deletions
diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 64aa03aed770..605b0315b4cb 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -431,6 +431,10 @@ struct kszphy_ptp_priv { spinlock_t seconds_lock; }; +struct kszphy_phy_stats { + u64 rx_err_pkt_cnt; +}; + struct kszphy_priv { struct kszphy_ptp_priv ptp_priv; const struct kszphy_type *type; @@ -441,6 +445,7 @@ struct kszphy_priv { bool rmii_ref_clk_sel_val; bool clk_enable; u64 stats[ARRAY_SIZE(kszphy_hw_stats)]; + struct kszphy_phy_stats phy_stats; }; static const struct kszphy_type lan8814_type = { @@ -472,6 +477,8 @@ static const struct kszphy_type ksz8051_type = { static const struct kszphy_type ksz8081_type = { .led_mode_reg = MII_KSZPHY_CTRL_2, + .cable_diag_reg = KSZ8081_LMD, + .pair_mask = KSZPHY_WIRE_PAIR_MASK, .has_broadcast_disable = true, .has_nand_tree_disable = true, .has_rmii_ref_clk_sel = true, @@ -1718,7 +1725,8 @@ static int ksz9x31_cable_test_fault_length(struct phy_device *phydev, u16 stat) * * distance to fault = (VCT_DATA - 22) * 4 / cable propagation velocity */ - if (phydev_id_compare(phydev, PHY_ID_KSZ9131)) + if (phydev_id_compare(phydev, PHY_ID_KSZ9131) || + phydev_id_compare(phydev, PHY_ID_KSZ9477)) dt = clamp(dt - 22, 0, 255); return (dt * 400) / 10; @@ -1792,12 +1800,20 @@ static int ksz9x31_cable_test_get_status(struct phy_device *phydev, bool *finished) { struct kszphy_priv *priv = phydev->priv; - unsigned long pair_mask = 0xf; + unsigned long pair_mask; int retries = 20; int pair, ret, rv; *finished = false; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->supported)) + pair_mask = 0xf; /* All pairs */ + else + pair_mask = 0x3; /* Pairs A and B only */ + /* Try harder if link partner is active */ while (pair_mask && retries--) { for_each_set_bit(pair, &pair_mask, 4) { @@ -1948,6 +1964,56 @@ static int ksz886x_read_status(struct phy_device *phydev) return genphy_read_status(phydev); } +static int ksz9477_mdix_update(struct phy_device *phydev) +{ + if (phydev->mdix_ctrl != ETH_TP_MDI_AUTO) + phydev->mdix = phydev->mdix_ctrl; + else + phydev->mdix = ETH_TP_MDI_INVALID; + + return 0; +} + +static int ksz9477_read_mdix_ctrl(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MII_KSZ9131_AUTO_MDIX); + if (val < 0) + return val; + + if (!(val & MII_KSZ9131_AUTO_MDIX_SWAP_OFF)) + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + else if (val & MII_KSZ9131_AUTO_MDI_SET) + phydev->mdix_ctrl = ETH_TP_MDI; + else + phydev->mdix_ctrl = ETH_TP_MDI_X; + + return 0; +} + +static int ksz9477_read_status(struct phy_device *phydev) +{ + int ret; + + ret = ksz9477_mdix_update(phydev); + if (ret) + return ret; + + return genphy_read_status(phydev); +} + +static int ksz9477_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = ksz9131_config_mdix(phydev, phydev->mdix_ctrl); + if (ret) + return ret; + + return genphy_config_aneg(phydev); +} + struct ksz9477_errata_write { u8 dev_addr; u8 reg_addr; @@ -2029,6 +2095,13 @@ static int ksz9477_config_init(struct phy_device *phydev) return err; } + /* Read initial MDI-X config state. So, we do not need to poll it + * later on. + */ + err = ksz9477_read_mdix_ctrl(phydev); + if (err) + return err; + return kszphy_config_init(phydev); } @@ -2073,6 +2146,165 @@ static void kszphy_get_stats(struct phy_device *phydev, data[i] = kszphy_get_stat(phydev, i); } +/* KSZ9477 PHY RXER Counter. Probably supported by other PHYs like KSZ9313, + * etc. The counter is incremented when the PHY receives a frame with one or + * more symbol errors. The counter is cleared when the register is read. + */ +#define MII_KSZ9477_PHY_RXER_COUNTER 0x15 + +static int kszphy_update_stats(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + int ret; + + ret = phy_read(phydev, MII_KSZ9477_PHY_RXER_COUNTER); + if (ret < 0) + return ret; + + priv->phy_stats.rx_err_pkt_cnt += ret; + + return 0; +} + +static void kszphy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct kszphy_priv *priv = phydev->priv; + + stats->rx_errors = priv->phy_stats.rx_err_pkt_cnt; +} + +/* Base register for Signal Quality Indicator (SQI) - Channel A + * + * MMD Address: MDIO_MMD_PMAPMD (0x01) + * Register: 0xAC (Channel A) + * Each channel (pair) has its own register: + * Channel A: 0xAC + * Channel B: 0xAD + * Channel C: 0xAE + * Channel D: 0xAF + */ +#define KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A 0xac + +/* SQI field mask for bits [14:8] + * + * SQI indicates relative quality of the signal. + * A lower value indicates better signal quality. + */ +#define KSZ9477_MMD_SQI_MASK GENMASK(14, 8) + +#define KSZ9477_MAX_CHANNELS 4 +#define KSZ9477_SQI_MAX 7 + +/* Number of SQI samples to average for a stable result. + * + * Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26) + * For noisy environments, a minimum of 30–50 readings is recommended. + */ +#define KSZ9477_SQI_SAMPLE_COUNT 40 + +/* The hardware SQI register provides a raw value from 0-127, where a lower + * value indicates better signal quality. However, empirical testing has + * shown that only the 0-7 range is relevant for a functional link. A raw + * value of 8 or higher was measured directly before link drop. This aligns + * with the OPEN Alliance recommendation that SQI=0 should represent the + * pre-failure state. + * + * This table provides a non-linear mapping from the useful raw hardware + * values (0-7) to the standard 0-7 SQI scale, where higher is better. + */ +static const u8 ksz_sqi_mapping[] = { + 7, /* raw 0 -> SQI 7 */ + 7, /* raw 1 -> SQI 7 */ + 6, /* raw 2 -> SQI 6 */ + 5, /* raw 3 -> SQI 5 */ + 4, /* raw 4 -> SQI 4 */ + 3, /* raw 5 -> SQI 3 */ + 2, /* raw 6 -> SQI 2 */ + 1, /* raw 7 -> SQI 1 */ +}; + +/** + * kszphy_get_sqi - Read, average, and map Signal Quality Index (SQI) + * @phydev: the PHY device + * + * This function reads and processes the raw Signal Quality Index from the + * PHY. Based on empirical testing, a raw value of 8 or higher indicates a + * pre-failure state and is mapped to SQI 0. Raw values from 0-7 are + * mapped to the standard 0-7 SQI scale via a lookup table. + * + * Return: SQI value (0–7), or a negative errno on failure. + */ +static int kszphy_get_sqi(struct phy_device *phydev) +{ + int sum[KSZ9477_MAX_CHANNELS] = { 0 }; + int worst_sqi = KSZ9477_SQI_MAX; + int i, val, raw_sqi, ch; + u8 channels; + + /* Determine applicable channels based on link speed */ + if (phydev->speed == SPEED_1000) + channels = 4; + else if (phydev->speed == SPEED_100) + channels = 1; + else + return -EOPNOTSUPP; + + /* Sample and accumulate SQI readings for each pair (currently only one). + * + * Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26) + * - The SQI register is updated every 2 µs. + * - Values may fluctuate significantly, even in low-noise environments. + * - For reliable estimation, average a minimum of 30–50 samples + * (recommended for noisy environments) + * - In noisy environments, individual readings are highly unreliable. + * + * We use 40 samples per pair with a delay of 3 µs between each + * read to ensure new values are captured (2 µs update interval). + */ + for (i = 0; i < KSZ9477_SQI_SAMPLE_COUNT; i++) { + for (ch = 0; ch < channels; ch++) { + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + ch); + if (val < 0) + return val; + + raw_sqi = FIELD_GET(KSZ9477_MMD_SQI_MASK, val); + sum[ch] += raw_sqi; + + /* We communicate with the PHY via MDIO via SPI or + * I2C, which is relatively slow. At least slower than + * the update interval of the SQI register. + * So, we can skip the delay between reads. + */ + } + } + + /* Calculate average for each channel and find the worst SQI */ + for (ch = 0; ch < channels; ch++) { + int avg_raw_sqi = sum[ch] / KSZ9477_SQI_SAMPLE_COUNT; + int mapped_sqi; + + /* Handle the pre-fail/failed state first. */ + if (avg_raw_sqi >= ARRAY_SIZE(ksz_sqi_mapping)) + mapped_sqi = 0; + else + /* Use the lookup table for the good signal range. */ + mapped_sqi = ksz_sqi_mapping[avg_raw_sqi]; + + if (mapped_sqi < worst_sqi) + worst_sqi = mapped_sqi; + } + + return worst_sqi; +} + +static int kszphy_get_sqi_max(struct phy_device *phydev) +{ + return KSZ9477_SQI_MAX; +} + static void kszphy_enable_clk(struct phy_device *phydev) { struct kszphy_priv *priv = phydev->priv; @@ -5403,6 +5635,14 @@ static int lan8841_suspend(struct phy_device *phydev) return kszphy_generic_suspend(phydev); } +static int ksz9131_resume(struct phy_device *phydev) +{ + if (phydev->suspended && phy_interface_is_rgmii(phydev)) + ksz9131_config_rgmii_delay(phydev); + + return kszphy_resume(phydev); +} + static struct phy_driver ksphy_driver[] = { { .phy_id = PHY_ID_KS8737, @@ -5649,7 +5889,7 @@ static struct phy_driver ksphy_driver[] = { .get_strings = kszphy_get_strings, .get_stats = kszphy_get_stats, .suspend = kszphy_suspend, - .resume = kszphy_resume, + .resume = ksz9131_resume, .cable_test_start = ksz9x31_cable_test_start, .cable_test_get_status = ksz9x31_cable_test_get_status, .get_features = ksz9477_get_features, @@ -5688,12 +5928,21 @@ static struct phy_driver ksphy_driver[] = { .phy_id = PHY_ID_KSZ9477, .phy_id_mask = MICREL_PHY_ID_MASK, .name = "Microchip KSZ9477", + .probe = kszphy_probe, /* PHY_GBIT_FEATURES */ .config_init = ksz9477_config_init, .config_intr = kszphy_config_intr, + .config_aneg = ksz9477_config_aneg, + .read_status = ksz9477_read_status, .handle_interrupt = kszphy_handle_interrupt, .suspend = genphy_suspend, .resume = ksz9477_resume, + .get_phy_stats = kszphy_get_phy_stats, + .update_stats = kszphy_update_stats, + .cable_test_start = ksz9x31_cable_test_start, + .cable_test_get_status = ksz9x31_cable_test_get_status, + .get_sqi = kszphy_get_sqi, + .get_sqi_max = kszphy_get_sqi_max, } }; module_phy_driver(ksphy_driver); |