Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <o.rempel@pengutronix.de>
Link: https://patch.msgid.link/20250627112539.895255-1-o.rempel@pengutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Oleksij Rempel and committed by
Jakub Kicinski
f461c7a8 be75d319

+132
+132
drivers/net/phy/micrel.c
··· 2173 2173 stats->rx_errors = priv->phy_stats.rx_err_pkt_cnt; 2174 2174 } 2175 2175 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 + 2176 2306 static void kszphy_enable_clk(struct phy_device *phydev) 2177 2307 { 2178 2308 struct kszphy_priv *priv = phydev->priv; ··· 5931 5801 .update_stats = kszphy_update_stats, 5932 5802 .cable_test_start = ksz9x31_cable_test_start, 5933 5803 .cable_test_get_status = ksz9x31_cable_test_get_status, 5804 + .get_sqi = kszphy_get_sqi, 5805 + .get_sqi_max = kszphy_get_sqi_max, 5934 5806 } }; 5935 5807 5936 5808 module_phy_driver(ksphy_driver);