Skip to content

Commit f461c7a

Browse files
oleremkuba-moo
authored andcommitted
phy: micrel: add Signal Quality Indicator (SQI) support for KSZ9477 switch PHYs
Add support for the Signal Quality Indicator (SQI) feature on KSZ9477 family switches, providing a relative measure of receive signal quality. The hardware exposes separate SQI readings per channel. For 1000BASE-T, all four channels are read. For 100BASE-TX, only one channel is reported, but which receive pair is active depends on Auto MDI-X negotiation, which is not exposed by the hardware. Therefore, it is not possible to reliably map the measured channel to a specific wire pair. This resolves an earlier discussion about how to handle multi-channel SQI. Originally, the plan was to expose all channels individually. However, since pair mapping is sometimes unavailable, this implementation treats SQI as a per-link metric instead. This fallback avoids ambiguity and ensures consistent behavior. The existing get_sqi() UAPI was designed for single-pair Ethernet (SPE), where per-pair and per-link are effectively equivalent. Restricting its use to per-link metrics does not introduce regressions for existing users. The raw 7-bit SQI value (0–127, lower is better) is converted to the standard 0–7 (high is better) scale. Empirical testing showed that the link becomes unstable around a raw value of 8. The SQI raw value remains zero if no data is received, even if noise is present. This confirms that the measurement reflects the "quality" during active data reception rather than the passive line state. User space must ensure that traffic is present on the link to obtain valid SQI readings. Signed-off-by: Oleksij Rempel <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent be75d31 commit f461c7a

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

drivers/net/phy/micrel.c

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2173,6 +2173,136 @@ static void kszphy_get_phy_stats(struct phy_device *phydev,
21732173
stats->rx_errors = priv->phy_stats.rx_err_pkt_cnt;
21742174
}
21752175

2176+
/* Base register for Signal Quality Indicator (SQI) - Channel A
2177+
*
2178+
* MMD Address: MDIO_MMD_PMAPMD (0x01)
2179+
* Register: 0xAC (Channel A)
2180+
* Each channel (pair) has its own register:
2181+
* Channel A: 0xAC
2182+
* Channel B: 0xAD
2183+
* Channel C: 0xAE
2184+
* Channel D: 0xAF
2185+
*/
2186+
#define KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A 0xac
2187+
2188+
/* SQI field mask for bits [14:8]
2189+
*
2190+
* SQI indicates relative quality of the signal.
2191+
* A lower value indicates better signal quality.
2192+
*/
2193+
#define KSZ9477_MMD_SQI_MASK GENMASK(14, 8)
2194+
2195+
#define KSZ9477_MAX_CHANNELS 4
2196+
#define KSZ9477_SQI_MAX 7
2197+
2198+
/* Number of SQI samples to average for a stable result.
2199+
*
2200+
* Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26)
2201+
* For noisy environments, a minimum of 30–50 readings is recommended.
2202+
*/
2203+
#define KSZ9477_SQI_SAMPLE_COUNT 40
2204+
2205+
/* The hardware SQI register provides a raw value from 0-127, where a lower
2206+
* value indicates better signal quality. However, empirical testing has
2207+
* shown that only the 0-7 range is relevant for a functional link. A raw
2208+
* value of 8 or higher was measured directly before link drop. This aligns
2209+
* with the OPEN Alliance recommendation that SQI=0 should represent the
2210+
* pre-failure state.
2211+
*
2212+
* This table provides a non-linear mapping from the useful raw hardware
2213+
* values (0-7) to the standard 0-7 SQI scale, where higher is better.
2214+
*/
2215+
static const u8 ksz_sqi_mapping[] = {
2216+
7, /* raw 0 -> SQI 7 */
2217+
7, /* raw 1 -> SQI 7 */
2218+
6, /* raw 2 -> SQI 6 */
2219+
5, /* raw 3 -> SQI 5 */
2220+
4, /* raw 4 -> SQI 4 */
2221+
3, /* raw 5 -> SQI 3 */
2222+
2, /* raw 6 -> SQI 2 */
2223+
1, /* raw 7 -> SQI 1 */
2224+
};
2225+
2226+
/**
2227+
* kszphy_get_sqi - Read, average, and map Signal Quality Index (SQI)
2228+
* @phydev: the PHY device
2229+
*
2230+
* This function reads and processes the raw Signal Quality Index from the
2231+
* PHY. Based on empirical testing, a raw value of 8 or higher indicates a
2232+
* pre-failure state and is mapped to SQI 0. Raw values from 0-7 are
2233+
* mapped to the standard 0-7 SQI scale via a lookup table.
2234+
*
2235+
* Return: SQI value (0–7), or a negative errno on failure.
2236+
*/
2237+
static int kszphy_get_sqi(struct phy_device *phydev)
2238+
{
2239+
int sum[KSZ9477_MAX_CHANNELS] = { 0 };
2240+
int worst_sqi = KSZ9477_SQI_MAX;
2241+
int i, val, raw_sqi, ch;
2242+
u8 channels;
2243+
2244+
/* Determine applicable channels based on link speed */
2245+
if (phydev->speed == SPEED_1000)
2246+
channels = 4;
2247+
else if (phydev->speed == SPEED_100)
2248+
channels = 1;
2249+
else
2250+
return -EOPNOTSUPP;
2251+
2252+
/* Sample and accumulate SQI readings for each pair (currently only one).
2253+
*
2254+
* Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26)
2255+
* - The SQI register is updated every 2 µs.
2256+
* - Values may fluctuate significantly, even in low-noise environments.
2257+
* - For reliable estimation, average a minimum of 30–50 samples
2258+
* (recommended for noisy environments)
2259+
* - In noisy environments, individual readings are highly unreliable.
2260+
*
2261+
* We use 40 samples per pair with a delay of 3 µs between each
2262+
* read to ensure new values are captured (2 µs update interval).
2263+
*/
2264+
for (i = 0; i < KSZ9477_SQI_SAMPLE_COUNT; i++) {
2265+
for (ch = 0; ch < channels; ch++) {
2266+
val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
2267+
KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + ch);
2268+
if (val < 0)
2269+
return val;
2270+
2271+
raw_sqi = FIELD_GET(KSZ9477_MMD_SQI_MASK, val);
2272+
sum[ch] += raw_sqi;
2273+
2274+
/* We communicate with the PHY via MDIO via SPI or
2275+
* I2C, which is relatively slow. At least slower than
2276+
* the update interval of the SQI register.
2277+
* So, we can skip the delay between reads.
2278+
*/
2279+
}
2280+
}
2281+
2282+
/* Calculate average for each channel and find the worst SQI */
2283+
for (ch = 0; ch < channels; ch++) {
2284+
int avg_raw_sqi = sum[ch] / KSZ9477_SQI_SAMPLE_COUNT;
2285+
int mapped_sqi;
2286+
2287+
/* Handle the pre-fail/failed state first. */
2288+
if (avg_raw_sqi >= ARRAY_SIZE(ksz_sqi_mapping))
2289+
mapped_sqi = 0;
2290+
else
2291+
/* Use the lookup table for the good signal range. */
2292+
mapped_sqi = ksz_sqi_mapping[avg_raw_sqi];
2293+
2294+
if (mapped_sqi < worst_sqi)
2295+
worst_sqi = mapped_sqi;
2296+
}
2297+
2298+
return worst_sqi;
2299+
}
2300+
2301+
static int kszphy_get_sqi_max(struct phy_device *phydev)
2302+
{
2303+
return KSZ9477_SQI_MAX;
2304+
}
2305+
21762306
static void kszphy_enable_clk(struct phy_device *phydev)
21772307
{
21782308
struct kszphy_priv *priv = phydev->priv;
@@ -5801,6 +5931,8 @@ static struct phy_driver ksphy_driver[] = {
58015931
.update_stats = kszphy_update_stats,
58025932
.cable_test_start = ksz9x31_cable_test_start,
58035933
.cable_test_get_status = ksz9x31_cable_test_get_status,
5934+
.get_sqi = kszphy_get_sqi,
5935+
.get_sqi_max = kszphy_get_sqi_max,
58045936
} };
58055937

58065938
module_phy_driver(ksphy_driver);

0 commit comments

Comments
 (0)