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

net: phy: mscc: Add support for PHY LED control

Add support for the PHY LED controller in the MSCC VSC85xx driver. The
implementation provides LED brightness and hardware control through the
LED subsystem and integrates with the standard 'netdev' trigger.

Introduce new register definitions for the LED behavior register
(MSCC_PHY_LED_BEHAVIOR = 30) and the LED combine disable bits, which
control whether LEDs indicate link-only or combined link and activity
status. Implement a helper, vsc8541_led_combine_disable_set(), to update
these bits safely using phy_modify().

Add support for LED brightness control and hardware mode configuration.
The new callbacks implement the standard LED class operations, allowing
user control through sysfs. The brightness control maps to PHY LED force
on/off modes. The hardware control get and set functions translate
between the PHY-specific LED mode encodings and the LED subsystem
TRIGGER_NETDEV_* rules.

The combine feature is managed automatically based on the selected
rules. When both RX and TX activity are disabled, the combine feature is
turned off, causing LEDs to indicate link-only status. When either RX or
TX activity is enabled, the combine feature remains active and LEDs
indicate combined link and activity.

Register the LED callbacks for all VSC85xx PHY variants so that the LED
subsystem can manage their indicators consistently. Existing device tree
LED configuration and default behavior are preserved.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20251112135715.1017117-4-prabhakar.mahadev-lad.rj@bp.renesas.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Lad Prabhakar and committed by
Jakub Kicinski
eb47c5c4 217eb2d6

+250
+4
drivers/net/phy/mscc/mscc.h
··· 85 85 #define LED_MODE_SEL_MASK(x) (GENMASK(3, 0) << LED_MODE_SEL_POS(x)) 86 86 #define LED_MODE_SEL(x, mode) (((mode) << LED_MODE_SEL_POS(x)) & LED_MODE_SEL_MASK(x)) 87 87 88 + #define MSCC_PHY_LED_BEHAVIOR 30 89 + #define LED_COMBINE_DIS_MASK(x) BIT(x) 90 + #define LED_COMBINE_DIS(x, dis) (((dis) ? 1 : 0) << (x)) 91 + 88 92 #define MSCC_EXT_PAGE_CSR_CNTL_17 17 89 93 #define MSCC_EXT_PAGE_CSR_CNTL_18 18 90 94
+246
drivers/net/phy/mscc/mscc_main.c
··· 201 201 return phy_modify(phydev, MSCC_PHY_LED_MODE_SEL, mask, val); 202 202 } 203 203 204 + static int vsc85xx_led_combine_disable_set(struct phy_device *phydev, 205 + u8 led_num, bool combine_disable) 206 + { 207 + u16 val = LED_COMBINE_DIS(led_num, combine_disable); 208 + u16 mask = LED_COMBINE_DIS_MASK(led_num); 209 + 210 + return phy_modify(phydev, MSCC_PHY_LED_BEHAVIOR, mask, val); 211 + } 212 + 204 213 static int vsc85xx_mdix_get(struct phy_device *phydev, u8 *mdix) 205 214 { 206 215 u16 reg_val; ··· 2243 2234 const u32 *default_led_mode) 2244 2235 { 2245 2236 struct vsc8531_private *vsc8531; 2237 + struct device_node *np; 2246 2238 int ret; 2247 2239 2248 2240 vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL); ··· 2293 2283 return ret; 2294 2284 } 2295 2285 2286 + /* 2287 + * Check for LED configuration in device tree if available 2288 + * or fall back to default `vsc8531,led-x-mode` DT properties. 2289 + */ 2290 + np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds"); 2291 + if (np) { 2292 + of_node_put(np); 2293 + 2294 + /* Force to defaults */ 2295 + for (unsigned int i = 0; i < vsc8531->nleds; i++) 2296 + vsc8531->leds_mode[i] = default_led_mode[i]; 2297 + 2298 + return 0; 2299 + } 2300 + 2296 2301 /* Parse LED modes from device tree */ 2297 2302 return vsc85xx_dt_led_modes_get(phydev, default_led_mode); 2303 + } 2304 + 2305 + static int vsc85xx_led_brightness_set(struct phy_device *phydev, 2306 + u8 index, enum led_brightness value) 2307 + { 2308 + struct vsc8531_private *vsc8531 = phydev->priv; 2309 + 2310 + if (index >= vsc8531->nleds) 2311 + return -EINVAL; 2312 + 2313 + return vsc85xx_led_cntl_set(phydev, index, value == LED_OFF ? 2314 + VSC8531_FORCE_LED_OFF : VSC8531_FORCE_LED_ON); 2315 + } 2316 + 2317 + static int vsc85xx_led_hw_is_supported(struct phy_device *phydev, u8 index, 2318 + unsigned long rules) 2319 + { 2320 + static const unsigned long supported = BIT(TRIGGER_NETDEV_LINK_1000) | 2321 + BIT(TRIGGER_NETDEV_LINK_100) | 2322 + BIT(TRIGGER_NETDEV_LINK_10) | 2323 + BIT(TRIGGER_NETDEV_LINK) | 2324 + BIT(TRIGGER_NETDEV_RX) | 2325 + BIT(TRIGGER_NETDEV_TX); 2326 + struct vsc8531_private *vsc8531 = phydev->priv; 2327 + 2328 + if (index >= vsc8531->nleds) 2329 + return -EINVAL; 2330 + 2331 + if (rules & ~supported) 2332 + return -EOPNOTSUPP; 2333 + 2334 + return 0; 2335 + } 2336 + 2337 + static int vsc85xx_led_hw_control_get(struct phy_device *phydev, u8 index, 2338 + unsigned long *rules) 2339 + { 2340 + struct vsc8531_private *vsc8531 = phydev->priv; 2341 + u8 mode, behavior; 2342 + int rc; 2343 + 2344 + if (index >= vsc8531->nleds) 2345 + return -EINVAL; 2346 + 2347 + rc = phy_read(phydev, MSCC_PHY_LED_MODE_SEL); 2348 + if (rc < 0) 2349 + return rc; 2350 + mode = (rc & LED_MODE_SEL_MASK(index)) >> LED_MODE_SEL_POS(index); 2351 + 2352 + rc = phy_read(phydev, MSCC_PHY_LED_BEHAVIOR); 2353 + if (rc < 0) 2354 + return rc; 2355 + behavior = (rc & LED_COMBINE_DIS_MASK(index)) >> index; 2356 + 2357 + switch (mode) { 2358 + case VSC8531_LINK_ACTIVITY: 2359 + case VSC8531_ACTIVITY: 2360 + *rules = BIT(TRIGGER_NETDEV_LINK); 2361 + break; 2362 + 2363 + case VSC8531_LINK_1000_ACTIVITY: 2364 + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | 2365 + BIT(TRIGGER_NETDEV_LINK); 2366 + break; 2367 + 2368 + case VSC8531_LINK_100_ACTIVITY: 2369 + *rules = BIT(TRIGGER_NETDEV_LINK_100) | 2370 + BIT(TRIGGER_NETDEV_LINK); 2371 + break; 2372 + 2373 + case VSC8531_LINK_10_ACTIVITY: 2374 + *rules = BIT(TRIGGER_NETDEV_LINK_10) | 2375 + BIT(TRIGGER_NETDEV_LINK); 2376 + break; 2377 + 2378 + case VSC8531_LINK_100_1000_ACTIVITY: 2379 + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | 2380 + BIT(TRIGGER_NETDEV_LINK_100) | 2381 + BIT(TRIGGER_NETDEV_LINK); 2382 + break; 2383 + 2384 + case VSC8531_LINK_10_1000_ACTIVITY: 2385 + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | 2386 + BIT(TRIGGER_NETDEV_LINK_10) | 2387 + BIT(TRIGGER_NETDEV_LINK); 2388 + break; 2389 + 2390 + case VSC8531_LINK_10_100_ACTIVITY: 2391 + *rules = BIT(TRIGGER_NETDEV_LINK_100) | 2392 + BIT(TRIGGER_NETDEV_LINK_10) | 2393 + BIT(TRIGGER_NETDEV_LINK); 2394 + break; 2395 + 2396 + default: 2397 + *rules = 0; 2398 + break; 2399 + } 2400 + 2401 + if (!behavior && *rules) 2402 + *rules |= BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX); 2403 + 2404 + return 0; 2405 + } 2406 + 2407 + static int vsc85xx_led_hw_control_set(struct phy_device *phydev, u8 index, 2408 + unsigned long rules) 2409 + { 2410 + struct vsc8531_private *vsc8531 = phydev->priv; 2411 + u8 mode = VSC8531_FORCE_LED_ON; 2412 + bool combine_disable = false; 2413 + bool has_rx, has_tx; 2414 + int ret; 2415 + 2416 + if (index >= vsc8531->nleds) 2417 + return -EINVAL; 2418 + 2419 + if (rules & BIT(TRIGGER_NETDEV_LINK)) 2420 + mode = VSC8531_LINK_ACTIVITY; 2421 + 2422 + if (rules & BIT(TRIGGER_NETDEV_LINK_10)) 2423 + mode = VSC8531_LINK_10_ACTIVITY; 2424 + 2425 + if (rules & BIT(TRIGGER_NETDEV_LINK_100)) 2426 + mode = VSC8531_LINK_100_ACTIVITY; 2427 + 2428 + if (rules & BIT(TRIGGER_NETDEV_LINK_1000)) 2429 + mode = VSC8531_LINK_1000_ACTIVITY; 2430 + 2431 + if (rules & BIT(TRIGGER_NETDEV_LINK_100) && 2432 + rules & BIT(TRIGGER_NETDEV_LINK_1000)) 2433 + mode = VSC8531_LINK_100_1000_ACTIVITY; 2434 + 2435 + if (rules & BIT(TRIGGER_NETDEV_LINK_10) && 2436 + rules & BIT(TRIGGER_NETDEV_LINK_1000)) 2437 + mode = VSC8531_LINK_10_1000_ACTIVITY; 2438 + 2439 + if (rules & BIT(TRIGGER_NETDEV_LINK_10) && 2440 + rules & BIT(TRIGGER_NETDEV_LINK_100)) 2441 + mode = VSC8531_LINK_10_100_ACTIVITY; 2442 + 2443 + /* 2444 + * The VSC85xx PHYs provides an option to control LED behavior. By 2445 + * default, the LEDx combine function is enabled, meaning the LED 2446 + * will be on when there is link/activity or duplex/collision. If 2447 + * the combine function is disabled, the LED will be on only for 2448 + * link or duplex. 2449 + * 2450 + * To control this behavior, we check the selected rules. If both 2451 + * RX and TX activity are not selected, the LED combine function 2452 + * is disabled; otherwise, it remains enabled. 2453 + */ 2454 + has_rx = !!(rules & BIT(TRIGGER_NETDEV_RX)); 2455 + has_tx = !!(rules & BIT(TRIGGER_NETDEV_TX)); 2456 + if (!has_rx && !has_tx) 2457 + combine_disable = true; 2458 + 2459 + ret = vsc85xx_led_combine_disable_set(phydev, index, combine_disable); 2460 + if (ret < 0) 2461 + return ret; 2462 + 2463 + return vsc85xx_led_cntl_set(phydev, index, mode); 2298 2464 } 2299 2465 2300 2466 static int vsc8514_probe(struct phy_device *phydev) ··· 2566 2380 .get_sset_count = &vsc85xx_get_sset_count, 2567 2381 .get_strings = &vsc85xx_get_strings, 2568 2382 .get_stats = &vsc85xx_get_stats, 2383 + .led_brightness_set = vsc85xx_led_brightness_set, 2384 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2385 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2386 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2569 2387 }, 2570 2388 { 2571 2389 .phy_id = PHY_ID_VSC8502, ··· 2594 2404 .get_sset_count = &vsc85xx_get_sset_count, 2595 2405 .get_strings = &vsc85xx_get_strings, 2596 2406 .get_stats = &vsc85xx_get_stats, 2407 + .led_brightness_set = vsc85xx_led_brightness_set, 2408 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2409 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2410 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2597 2411 }, 2598 2412 { 2599 2413 .phy_id = PHY_ID_VSC8504, ··· 2625 2431 .get_stats = &vsc85xx_get_stats, 2626 2432 .inband_caps = vsc85xx_inband_caps, 2627 2433 .config_inband = vsc85xx_config_inband, 2434 + .led_brightness_set = vsc85xx_led_brightness_set, 2435 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2436 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2437 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2628 2438 }, 2629 2439 { 2630 2440 .phy_id = PHY_ID_VSC8514, ··· 2654 2456 .get_stats = &vsc85xx_get_stats, 2655 2457 .inband_caps = vsc85xx_inband_caps, 2656 2458 .config_inband = vsc85xx_config_inband, 2459 + .led_brightness_set = vsc85xx_led_brightness_set, 2460 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2461 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2462 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2657 2463 }, 2658 2464 { 2659 2465 .phy_id = PHY_ID_VSC8530, ··· 2682 2480 .get_sset_count = &vsc85xx_get_sset_count, 2683 2481 .get_strings = &vsc85xx_get_strings, 2684 2482 .get_stats = &vsc85xx_get_stats, 2483 + .led_brightness_set = vsc85xx_led_brightness_set, 2484 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2485 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2486 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2685 2487 }, 2686 2488 { 2687 2489 .phy_id = PHY_ID_VSC8531, ··· 2710 2504 .get_sset_count = &vsc85xx_get_sset_count, 2711 2505 .get_strings = &vsc85xx_get_strings, 2712 2506 .get_stats = &vsc85xx_get_stats, 2507 + .led_brightness_set = vsc85xx_led_brightness_set, 2508 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2509 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2510 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2713 2511 }, 2714 2512 { 2715 2513 .phy_id = PHY_ID_VSC8540, ··· 2738 2528 .get_sset_count = &vsc85xx_get_sset_count, 2739 2529 .get_strings = &vsc85xx_get_strings, 2740 2530 .get_stats = &vsc85xx_get_stats, 2531 + .led_brightness_set = vsc85xx_led_brightness_set, 2532 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2533 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2534 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2741 2535 }, 2742 2536 { 2743 2537 .phy_id = PHY_ID_VSC8541, ··· 2766 2552 .get_sset_count = &vsc85xx_get_sset_count, 2767 2553 .get_strings = &vsc85xx_get_strings, 2768 2554 .get_stats = &vsc85xx_get_stats, 2555 + .led_brightness_set = vsc85xx_led_brightness_set, 2556 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2557 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2558 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2769 2559 }, 2770 2560 { 2771 2561 .phy_id = PHY_ID_VSC8552, ··· 2796 2578 .get_stats = &vsc85xx_get_stats, 2797 2579 .inband_caps = vsc85xx_inband_caps, 2798 2580 .config_inband = vsc85xx_config_inband, 2581 + .led_brightness_set = vsc85xx_led_brightness_set, 2582 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2583 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2584 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2799 2585 }, 2800 2586 { 2801 2587 PHY_ID_MATCH_EXACT(PHY_ID_VSC856X), ··· 2823 2601 .get_stats = &vsc85xx_get_stats, 2824 2602 .inband_caps = vsc85xx_inband_caps, 2825 2603 .config_inband = vsc85xx_config_inband, 2604 + .led_brightness_set = vsc85xx_led_brightness_set, 2605 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2606 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2607 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2826 2608 }, 2827 2609 { 2828 2610 .phy_id = PHY_ID_VSC8572, ··· 2855 2629 .get_stats = &vsc85xx_get_stats, 2856 2630 .inband_caps = vsc85xx_inband_caps, 2857 2631 .config_inband = vsc85xx_config_inband, 2632 + .led_brightness_set = vsc85xx_led_brightness_set, 2633 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2634 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2635 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2858 2636 }, 2859 2637 { 2860 2638 .phy_id = PHY_ID_VSC8574, ··· 2887 2657 .get_stats = &vsc85xx_get_stats, 2888 2658 .inband_caps = vsc85xx_inband_caps, 2889 2659 .config_inband = vsc85xx_config_inband, 2660 + .led_brightness_set = vsc85xx_led_brightness_set, 2661 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2662 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2663 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2890 2664 }, 2891 2665 { 2892 2666 PHY_ID_MATCH_EXACT(PHY_ID_VSC8575), ··· 2916 2682 .get_stats = &vsc85xx_get_stats, 2917 2683 .inband_caps = vsc85xx_inband_caps, 2918 2684 .config_inband = vsc85xx_config_inband, 2685 + .led_brightness_set = vsc85xx_led_brightness_set, 2686 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2687 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2688 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2919 2689 }, 2920 2690 { 2921 2691 PHY_ID_MATCH_EXACT(PHY_ID_VSC8582), ··· 2945 2707 .get_stats = &vsc85xx_get_stats, 2946 2708 .inband_caps = vsc85xx_inband_caps, 2947 2709 .config_inband = vsc85xx_config_inband, 2710 + .led_brightness_set = vsc85xx_led_brightness_set, 2711 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2712 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2713 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2948 2714 }, 2949 2715 { 2950 2716 PHY_ID_MATCH_EXACT(PHY_ID_VSC8584), ··· 2975 2733 .link_change_notify = &vsc85xx_link_change_notify, 2976 2734 .inband_caps = vsc85xx_inband_caps, 2977 2735 .config_inband = vsc85xx_config_inband, 2736 + .led_brightness_set = vsc85xx_led_brightness_set, 2737 + .led_hw_is_supported = vsc85xx_led_hw_is_supported, 2738 + .led_hw_control_get = vsc85xx_led_hw_control_get, 2739 + .led_hw_control_set = vsc85xx_led_hw_control_set, 2978 2740 } 2979 2741 2980 2742 };