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);  | 
