19af9a816SDavid Yang // SPDX-License-Identifier: GPL-2.0-or-later 29af9a816SDavid Yang /* 39af9a816SDavid Yang * Copyright (c) 2026 David Yang 49af9a816SDavid Yang */ 59af9a816SDavid Yang 69af9a816SDavid Yang #include <linux/uleds.h> 79af9a816SDavid Yang 89af9a816SDavid Yang #include "chip.h" 99af9a816SDavid Yang #include "leds.h" 109af9a816SDavid Yang #include "smi.h" 119af9a816SDavid Yang 129af9a816SDavid Yang #define to_yt921x_led(led_cdev) \ 139af9a816SDavid Yang container_of_const((led_cdev), struct yt921x_led, cdev) 149af9a816SDavid Yang #define to_yt921x_port(led) ((led)->port) 159af9a816SDavid Yang 169af9a816SDavid Yang static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group) 179af9a816SDavid Yang { 189af9a816SDavid Yang switch (group) { 199af9a816SDavid Yang case 0: 209af9a816SDavid Yang default: 219af9a816SDavid Yang return YT921X_LED0_PORTn(port); 229af9a816SDavid Yang case 1: 239af9a816SDavid Yang return YT921X_LED1_PORTn(port); 249af9a816SDavid Yang case 2: 259af9a816SDavid Yang return YT921X_LED2_PORTn(port); 269af9a816SDavid Yang } 279af9a816SDavid Yang } 289af9a816SDavid Yang 299af9a816SDavid Yang static int 309af9a816SDavid Yang yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on) 319af9a816SDavid Yang { 329af9a816SDavid Yang struct yt921x_port *pp = &priv->ports[port]; 339af9a816SDavid Yang u32 ctrl; 349af9a816SDavid Yang u32 mask; 359af9a816SDavid Yang 369af9a816SDavid Yang /* No reversion - LED is sacrificed in case of IO error */ 379af9a816SDavid Yang pp->led_duty_mask &= ~BIT(group); 389af9a816SDavid Yang pp->led_cycle_mask &= ~BIT(group); 399af9a816SDavid Yang 409af9a816SDavid Yang mask = YT921X_LED2_PORT_FORCEn_M(group); 419af9a816SDavid Yang ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) : 429af9a816SDavid Yang YT921X_LED2_PORT_FORCEn_OFF(group); 439af9a816SDavid Yang return yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask, 449af9a816SDavid Yang ctrl); 459af9a816SDavid Yang } 469af9a816SDavid Yang 479af9a816SDavid Yang static int 489af9a816SDavid Yang yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on, 499af9a816SDavid Yang unsigned long off, unsigned short *cyclep, 509af9a816SDavid Yang unsigned char *dutyp) 519af9a816SDavid Yang { 529af9a816SDavid Yang static const unsigned char dutys[] = { 539af9a816SDavid Yang YT921X_LED_DUTY(1, 6), 549af9a816SDavid Yang YT921X_LED_DUTY(1, 4), 559af9a816SDavid Yang YT921X_LED_DUTY(1, 3), 569af9a816SDavid Yang YT921X_LED_DUTY(1, 2), 579af9a816SDavid Yang }; 589af9a816SDavid Yang unsigned int cycle_upper; 599af9a816SDavid Yang unsigned int cycle_req; 609af9a816SDavid Yang unsigned int duty_req; 619af9a816SDavid Yang unsigned int cycle; 629af9a816SDavid Yang unsigned int duty; 639af9a816SDavid Yang 649af9a816SDavid Yang cycle = YT921X_LED_BLINK_MAX; 659af9a816SDavid Yang cycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */ 669af9a816SDavid Yang if (check_add_overflow(on, off, &cycle_req) || cycle_req >= cycle_upper) 679af9a816SDavid Yang return -EOPNOTSUPP; 689af9a816SDavid Yang 699af9a816SDavid Yang for (; cycle > YT921X_LED_BLINK_MIN; cycle_upper >>= 1, cycle >>= 1) 709af9a816SDavid Yang if (cycle_req >= cycle_upper >> 1) 719af9a816SDavid Yang break; 729af9a816SDavid Yang *cyclep = cycle; 739af9a816SDavid Yang 749af9a816SDavid Yang duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM * 759af9a816SDavid Yang (on > off ? off : on), cycle_req); 769af9a816SDavid Yang for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--) 779af9a816SDavid Yang if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) { 789af9a816SDavid Yang duty = dutys[i]; 799af9a816SDavid Yang break; 809af9a816SDavid Yang } 819af9a816SDavid Yang if (on > off) 829af9a816SDavid Yang duty = YT921X_LED_DUTY_DENOM - duty; 839af9a816SDavid Yang *dutyp = duty; 849af9a816SDavid Yang 859af9a816SDavid Yang return 0; 869af9a816SDavid Yang } 879af9a816SDavid Yang 889af9a816SDavid Yang static int 899af9a816SDavid Yang yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group, 909af9a816SDavid Yang unsigned long *onp, unsigned long *offp) 919af9a816SDavid Yang { 929af9a816SDavid Yang struct yt921x_port *pp = &priv->ports[port]; 939af9a816SDavid Yang unsigned short cycle; 949af9a816SDavid Yang unsigned char duty; 959af9a816SDavid Yang bool use_cycle; 969af9a816SDavid Yang u32 ctrl; 979af9a816SDavid Yang u32 mask; 989af9a816SDavid Yang u32 val; 999af9a816SDavid Yang int res; 1009af9a816SDavid Yang 1019af9a816SDavid Yang if (!*onp && !*offp) { 1029af9a816SDavid Yang cycle = YT921X_LED_BLINK_DEF; 1039af9a816SDavid Yang duty = (pp->led_duty_mask & ~BIT(group)) ? pp->led_duty : 1049af9a816SDavid Yang YT921X_LED_DUTY(1, 2); 1059af9a816SDavid Yang 1069af9a816SDavid Yang use_cycle = false; 10710973152SDavid Yang } else if (!*onp || !*offp) { 10810973152SDavid Yang return yt921x_led_force_set(priv, port, group, *onp); 1099af9a816SDavid Yang } else { 1109af9a816SDavid Yang res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty); 1119af9a816SDavid Yang if (res) 1129af9a816SDavid Yang return res; 1139af9a816SDavid Yang 1149af9a816SDavid Yang use_cycle = cycle < YT921X_LED_BLINK_DEF; 1159af9a816SDavid Yang 1169af9a816SDavid Yang if (use_cycle && cycle != pp->led_cycle && 1179af9a816SDavid Yang (pp->led_cycle_mask & ~BIT(group))) 1189af9a816SDavid Yang return -EOPNOTSUPP; 1199af9a816SDavid Yang if (duty != pp->led_duty && (pp->led_duty_mask & ~BIT(group))) 1209af9a816SDavid Yang return -EOPNOTSUPP; 1219af9a816SDavid Yang } 1229af9a816SDavid Yang 1239af9a816SDavid Yang pp->led_cycle_mask &= ~BIT(group); 1249af9a816SDavid Yang pp->led_duty_mask &= ~BIT(group); 1259af9a816SDavid Yang 1269af9a816SDavid Yang /* The chip seems to jam a while if changing duty directly */ 1279af9a816SDavid Yang res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val); 1289af9a816SDavid Yang if (res) 1299af9a816SDavid Yang return res; 1309af9a816SDavid Yang 1319af9a816SDavid Yang ctrl = val & ~YT921X_LED2_PORT_FORCEn_M(group); 1329af9a816SDavid Yang ctrl |= YT921X_LED2_PORT_FORCEn_OFF(group); 1339af9a816SDavid Yang if (val != ctrl) { 1349af9a816SDavid Yang res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl); 1359af9a816SDavid Yang if (res) 1369af9a816SDavid Yang return res; 1379af9a816SDavid Yang } 1389af9a816SDavid Yang 1399af9a816SDavid Yang mask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP; 1409af9a816SDavid Yang switch (duty >= YT921X_LED_DUTY(1, 2) ? duty : 1419af9a816SDavid Yang YT921X_LED_DUTY_DENOM - duty) { 1429af9a816SDavid Yang default: 1439af9a816SDavid Yang duty = YT921X_LED_DUTY(1, 2); 1449af9a816SDavid Yang fallthrough; 1459af9a816SDavid Yang case YT921X_LED_DUTY(1, 2): 1469af9a816SDavid Yang ctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2; 1479af9a816SDavid Yang break; 1489af9a816SDavid Yang case YT921X_LED_DUTY(2, 3): 1499af9a816SDavid Yang ctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3; 1509af9a816SDavid Yang break; 1519af9a816SDavid Yang case YT921X_LED_DUTY(3, 4): 1529af9a816SDavid Yang ctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4; 1539af9a816SDavid Yang break; 1549af9a816SDavid Yang case YT921X_LED_DUTY(5, 6): 1559af9a816SDavid Yang ctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6; 1569af9a816SDavid Yang break; 1579af9a816SDavid Yang } 1589af9a816SDavid Yang if (duty < YT921X_LED_DUTY(1, 2)) 1599af9a816SDavid Yang ctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP; 1609af9a816SDavid Yang if (use_cycle) { 1619af9a816SDavid Yang mask |= YT921X_LED1_PORT_OTHER_BLINK_M; 1629af9a816SDavid Yang ctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle)); 1639af9a816SDavid Yang } 1649af9a816SDavid Yang res = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl); 1659af9a816SDavid Yang if (res) 1669af9a816SDavid Yang return res; 1679af9a816SDavid Yang 1689af9a816SDavid Yang ctrl = val & ~(YT921X_LED2_PORT_FORCEn_M(group) | 1699af9a816SDavid Yang YT921X_LED2_PORT_FORCE_BLINKn_M(group)); 1709af9a816SDavid Yang ctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group); 1719af9a816SDavid Yang if (use_cycle) 1729af9a816SDavid Yang ctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group); 1739af9a816SDavid Yang else 1749af9a816SDavid Yang ctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9); 1759af9a816SDavid Yang res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl); 1769af9a816SDavid Yang if (res) 1779af9a816SDavid Yang return res; 1789af9a816SDavid Yang 1799af9a816SDavid Yang if (use_cycle) { 1809af9a816SDavid Yang pp->led_cycle_mask |= BIT(group); 1819af9a816SDavid Yang pp->led_cycle = cycle; 1829af9a816SDavid Yang } 1839af9a816SDavid Yang pp->led_duty_mask |= BIT(group); 1849af9a816SDavid Yang pp->led_duty = duty; 1859af9a816SDavid Yang 1869af9a816SDavid Yang *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM); 1879af9a816SDavid Yang *offp = cycle - *onp; 1889af9a816SDavid Yang return 0; 1899af9a816SDavid Yang } 1909af9a816SDavid Yang 1919af9a816SDavid Yang struct yt921x_led_trigger_map { 1929af9a816SDavid Yang unsigned long flags; 1939af9a816SDavid Yang u32 mask; 1949af9a816SDavid Yang }; 1959af9a816SDavid Yang 1969af9a816SDavid Yang static const struct yt921x_led_trigger_map yt921x_led_trigger_maps[] = { 1979af9a816SDavid Yang {BIT(TRIGGER_NETDEV_LINK), 1989af9a816SDavid Yang YT921X_LEDx_PORT_ACT_DUPLEX_HALF | YT921X_LEDx_PORT_ACT_DUPLEX_FULL}, 1999af9a816SDavid Yang {BIT(TRIGGER_NETDEV_LINK_10), YT921X_LEDx_PORT_ACT_10M}, 2009af9a816SDavid Yang {BIT(TRIGGER_NETDEV_LINK_100), YT921X_LEDx_PORT_ACT_100M}, 2019af9a816SDavid Yang {BIT(TRIGGER_NETDEV_LINK_1000), YT921X_LEDx_PORT_ACT_1000M}, 2029af9a816SDavid Yang {BIT(TRIGGER_NETDEV_HALF_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_HALF}, 2039af9a816SDavid Yang {BIT(TRIGGER_NETDEV_FULL_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_FULL}, 2049af9a816SDavid Yang {BIT(TRIGGER_NETDEV_TX), YT921X_LEDx_PORT_ACT_TX_BLINK}, 2059af9a816SDavid Yang {BIT(TRIGGER_NETDEV_RX), YT921X_LEDx_PORT_ACT_RX_BLINK}, 2069af9a816SDavid Yang {BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR), 2079af9a816SDavid Yang YT921X_LEDx_PORT_ACT_COLLISION_BLINK}, 2089af9a816SDavid Yang }; 2099af9a816SDavid Yang 2109af9a816SDavid Yang static bool 2119af9a816SDavid Yang yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port, 2129af9a816SDavid Yang int group, unsigned long flags) 2139af9a816SDavid Yang { 2149af9a816SDavid Yang if (!flags) 2159af9a816SDavid Yang return true; 2169af9a816SDavid Yang 2179af9a816SDavid Yang for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) { 2189af9a816SDavid Yang const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i]; 2199af9a816SDavid Yang 2209af9a816SDavid Yang if ((flags & map->flags) == map->flags) { 2219af9a816SDavid Yang flags &= ~map->flags; 2229af9a816SDavid Yang if (!flags) 2239af9a816SDavid Yang return true; 2249af9a816SDavid Yang } 2259af9a816SDavid Yang } 2269af9a816SDavid Yang 2279af9a816SDavid Yang return false; 2289af9a816SDavid Yang } 2299af9a816SDavid Yang 2309af9a816SDavid Yang static int 2319af9a816SDavid Yang yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group, 2329af9a816SDavid Yang unsigned long *flagsp) 2339af9a816SDavid Yang { 2349af9a816SDavid Yang u32 addr; 2359af9a816SDavid Yang u32 val; 2369af9a816SDavid Yang int res; 2379af9a816SDavid Yang 2389af9a816SDavid Yang res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val); 2399af9a816SDavid Yang if (res) 2409af9a816SDavid Yang return res; 2419af9a816SDavid Yang 2429af9a816SDavid Yang if ((val & YT921X_LED2_PORT_FORCEn_M(group)) != 2439af9a816SDavid Yang YT921X_LED2_PORT_FORCEn_DONTCARE(group)) { 2449af9a816SDavid Yang *flagsp = 0; 2459af9a816SDavid Yang return 0; 2469af9a816SDavid Yang } 2479af9a816SDavid Yang 2489af9a816SDavid Yang if (group != 2) { 2499af9a816SDavid Yang addr = yt921x_led_regaddr(priv, port, group); 2509af9a816SDavid Yang res = yt921x_reg_read(priv, addr, &val); 2519af9a816SDavid Yang if (res) 2529af9a816SDavid Yang return res; 2539af9a816SDavid Yang } 2549af9a816SDavid Yang 2559af9a816SDavid Yang *flagsp = 0; 2569af9a816SDavid Yang for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) { 2579af9a816SDavid Yang const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i]; 2589af9a816SDavid Yang 2599af9a816SDavid Yang if ((val & map->mask) == map->mask) 2609af9a816SDavid Yang *flagsp |= map->flags; 2619af9a816SDavid Yang } 2629af9a816SDavid Yang 2639af9a816SDavid Yang return 0; 2649af9a816SDavid Yang } 2659af9a816SDavid Yang 2669af9a816SDavid Yang static int 2679af9a816SDavid Yang yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group, 2689af9a816SDavid Yang unsigned long flags) 2699af9a816SDavid Yang { 2709af9a816SDavid Yang struct yt921x_port *pp = &priv->ports[port]; 2719af9a816SDavid Yang u32 addr; 2729af9a816SDavid Yang u32 ctrl; 2739af9a816SDavid Yang u32 mask; 2749af9a816SDavid Yang int res; 2759af9a816SDavid Yang 2769af9a816SDavid Yang ctrl = 0; 2779af9a816SDavid Yang for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) { 2789af9a816SDavid Yang const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i]; 2799af9a816SDavid Yang 2809af9a816SDavid Yang if ((flags & map->flags) == map->flags) { 2819af9a816SDavid Yang flags &= ~map->flags; 2829af9a816SDavid Yang ctrl |= map->mask; 2839af9a816SDavid Yang if (!flags) 2849af9a816SDavid Yang break; 2859af9a816SDavid Yang } 2869af9a816SDavid Yang } 2879af9a816SDavid Yang if (flags) 2889af9a816SDavid Yang return -EOPNOTSUPP; 2899af9a816SDavid Yang 2909af9a816SDavid Yang pp->led_duty_mask &= ~BIT(group); 2919af9a816SDavid Yang pp->led_cycle_mask &= ~BIT(group); 2929af9a816SDavid Yang 2939af9a816SDavid Yang mask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M; 2949af9a816SDavid Yang if (group == 2) { 2959af9a816SDavid Yang mask |= YT921X_LED2_PORT_FORCEn_M(group); 2969af9a816SDavid Yang ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group); 2979af9a816SDavid Yang } 2989af9a816SDavid Yang addr = yt921x_led_regaddr(priv, port, group); 2999af9a816SDavid Yang res = yt921x_reg_update_bits(priv, addr, mask, ctrl); 3009af9a816SDavid Yang if (res) 3019af9a816SDavid Yang return res; 3029af9a816SDavid Yang 3039af9a816SDavid Yang if (group != 2) { 3049af9a816SDavid Yang mask = YT921X_LED2_PORT_FORCEn_M(group); 3059af9a816SDavid Yang ctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group); 3069af9a816SDavid Yang res = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), 3079af9a816SDavid Yang mask, ctrl); 3089af9a816SDavid Yang if (res) 3099af9a816SDavid Yang return res; 3109af9a816SDavid Yang } 3119af9a816SDavid Yang 3129af9a816SDavid Yang return 0; 3139af9a816SDavid Yang } 3149af9a816SDavid Yang 3159af9a816SDavid Yang static int 3169af9a816SDavid Yang yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev, 3179af9a816SDavid Yang enum led_brightness brightness) 3189af9a816SDavid Yang { 3199af9a816SDavid Yang struct yt921x_led *led = to_yt921x_led(led_cdev); 3209af9a816SDavid Yang struct yt921x_port *pp = to_yt921x_port(led); 321c9996cf7SDavid Yang struct yt921x_priv *priv = yt921x_port_to_priv(pp); 3229af9a816SDavid Yang int res; 3239af9a816SDavid Yang 3249af9a816SDavid Yang mutex_lock(&priv->reg_lock); 3259af9a816SDavid Yang res = yt921x_led_force_set(priv, pp->index, led->group, brightness); 3269af9a816SDavid Yang mutex_unlock(&priv->reg_lock); 3279af9a816SDavid Yang 3289af9a816SDavid Yang return res; 3299af9a816SDavid Yang } 3309af9a816SDavid Yang 3319af9a816SDavid Yang static int 3329af9a816SDavid Yang yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, 3339af9a816SDavid Yang unsigned long *delay_off) 3349af9a816SDavid Yang { 3359af9a816SDavid Yang struct yt921x_led *led = to_yt921x_led(led_cdev); 3369af9a816SDavid Yang struct yt921x_port *pp = to_yt921x_port(led); 337c9996cf7SDavid Yang struct yt921x_priv *priv = yt921x_port_to_priv(pp); 3389af9a816SDavid Yang int res; 3399af9a816SDavid Yang 3409af9a816SDavid Yang mutex_lock(&priv->reg_lock); 3419af9a816SDavid Yang res = yt921x_led_blink_set(priv, pp->index, led->group, delay_on, 3429af9a816SDavid Yang delay_off); 3439af9a816SDavid Yang mutex_unlock(&priv->reg_lock); 3449af9a816SDavid Yang 3459af9a816SDavid Yang return res; 3469af9a816SDavid Yang } 3479af9a816SDavid Yang 348cc2d2738SArnd Bergmann static struct device __maybe_unused * 3499af9a816SDavid Yang yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev) 3509af9a816SDavid Yang { 3519af9a816SDavid Yang struct yt921x_led *led = to_yt921x_led(led_cdev); 3529af9a816SDavid Yang struct yt921x_port *pp = to_yt921x_port(led); 353c9996cf7SDavid Yang struct yt921x_priv *priv = yt921x_port_to_priv(pp); 3549af9a816SDavid Yang struct dsa_port *dp; 3559af9a816SDavid Yang 3569af9a816SDavid Yang dp = dsa_to_port(&priv->ds, pp->index); 3579af9a816SDavid Yang if (!dp) 3589af9a816SDavid Yang return NULL; 3599af9a816SDavid Yang 3609af9a816SDavid Yang if (dsa_port_is_user(dp)) 3619af9a816SDavid Yang return !dp->user ? NULL : &dp->user->dev; 3629af9a816SDavid Yang if (dsa_port_is_cpu(dp)) 3639af9a816SDavid Yang return !dp->conduit ? NULL : &dp->conduit->dev; 3649af9a816SDavid Yang 3659af9a816SDavid Yang return NULL; 3669af9a816SDavid Yang } 3679af9a816SDavid Yang 3689af9a816SDavid Yang static int __maybe_unused 3699af9a816SDavid Yang yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev, 3709af9a816SDavid Yang unsigned long flags) 3719af9a816SDavid Yang { 3729af9a816SDavid Yang struct yt921x_led *led = to_yt921x_led(led_cdev); 3739af9a816SDavid Yang struct yt921x_port *pp = to_yt921x_port(led); 374c9996cf7SDavid Yang struct yt921x_priv *priv = yt921x_port_to_priv(pp); 3759af9a816SDavid Yang 3769af9a816SDavid Yang if (yt921x_led_trigger_is_supported(priv, pp->index, led->group, flags)) 3779af9a816SDavid Yang return 0; 3789af9a816SDavid Yang return -EOPNOTSUPP; 3799af9a816SDavid Yang } 3809af9a816SDavid Yang 3819af9a816SDavid Yang static int __maybe_unused 3829af9a816SDavid Yang yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp) 3839af9a816SDavid Yang { 3849af9a816SDavid Yang struct yt921x_led *led = to_yt921x_led(led_cdev); 3859af9a816SDavid Yang struct yt921x_port *pp = to_yt921x_port(led); 386c9996cf7SDavid Yang struct yt921x_priv *priv = yt921x_port_to_priv(pp); 3879af9a816SDavid Yang int res; 3889af9a816SDavid Yang 3899af9a816SDavid Yang mutex_lock(&priv->reg_lock); 3909af9a816SDavid Yang res = yt921x_led_trigger_get(priv, pp->index, led->group, flagsp); 3919af9a816SDavid Yang mutex_unlock(&priv->reg_lock); 3929af9a816SDavid Yang 3939af9a816SDavid Yang return res; 3949af9a816SDavid Yang } 3959af9a816SDavid Yang 3969af9a816SDavid Yang static int __maybe_unused 3979af9a816SDavid Yang yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags) 3989af9a816SDavid Yang { 3999af9a816SDavid Yang struct yt921x_led *led = to_yt921x_led(led_cdev); 4009af9a816SDavid Yang struct yt921x_port *pp = to_yt921x_port(led); 401c9996cf7SDavid Yang struct yt921x_priv *priv = yt921x_port_to_priv(pp); 4029af9a816SDavid Yang int res; 4039af9a816SDavid Yang 4049af9a816SDavid Yang mutex_lock(&priv->reg_lock); 4059af9a816SDavid Yang res = yt921x_led_trigger_set(priv, pp->index, led->group, flags); 4069af9a816SDavid Yang mutex_unlock(&priv->reg_lock); 4079af9a816SDavid Yang 4089af9a816SDavid Yang return res; 4099af9a816SDavid Yang } 4109af9a816SDavid Yang 4119af9a816SDavid Yang static int 4129af9a816SDavid Yang yt921x_led_setup(struct yt921x_priv *priv, int port, 4139af9a816SDavid Yang struct fwnode_handle *fwnode) 4149af9a816SDavid Yang { 415*cece0b57SDavid Yang struct device *dev = yt921x_priv_to_device(priv); 4169af9a816SDavid Yang struct yt921x_port *pp = &priv->ports[port]; 4179af9a816SDavid Yang struct led_init_data init_data; 4189af9a816SDavid Yang struct led_classdev *led_cdev; 4199af9a816SDavid Yang char name[LED_MAX_NAME_SIZE]; 4209af9a816SDavid Yang enum led_default_state state; 4219af9a816SDavid Yang struct yt921x_led *led; 4221a2dbdb6SDavid Yang int polarity_orig; 4239af9a816SDavid Yang bool force_high; 4249af9a816SDavid Yang bool force_low; 4251a2dbdb6SDavid Yang u32 led2_orig; 4261a2dbdb6SDavid Yang int polarity; 4279af9a816SDavid Yang u32 group; 4289af9a816SDavid Yang u32 mask; 4299af9a816SDavid Yang u32 ctrl; 4301a2dbdb6SDavid Yang u32 val; 4319af9a816SDavid Yang bool on; 4329af9a816SDavid Yang int res; 4339af9a816SDavid Yang int ret; 4349af9a816SDavid Yang 4359af9a816SDavid Yang res = fwnode_property_read_u32(fwnode, "reg", &group); 4369af9a816SDavid Yang if (res) { 4379af9a816SDavid Yang dev_err(dev, "Missing LED reg for %pfw\n", fwnode); 4389af9a816SDavid Yang return res; 4399af9a816SDavid Yang } 4409af9a816SDavid Yang if (group >= YT921X_LED_GROUP_NUM) { 4419af9a816SDavid Yang dev_err(dev, "Invalid LED reg %u for port %d\n", group, port); 4429af9a816SDavid Yang return -EINVAL; 4439af9a816SDavid Yang } 4449af9a816SDavid Yang if (pp->leds[group]) { 4459af9a816SDavid Yang res = -EEXIST; 4469af9a816SDavid Yang goto err; 4479af9a816SDavid Yang } 4489af9a816SDavid Yang 4499af9a816SDavid Yang force_high = fwnode_property_read_bool(fwnode, "active-high"); 4509af9a816SDavid Yang force_low = fwnode_property_read_bool(fwnode, "active-low"); 4519af9a816SDavid Yang if (force_high && force_low) { 4529af9a816SDavid Yang dev_err(dev, "Duplicate polarities for LED %02d:%02u\n", 4539af9a816SDavid Yang port, group); 4549af9a816SDavid Yang return -EINVAL; 4559af9a816SDavid Yang } 4561a2dbdb6SDavid Yang polarity = force_high ? 1 : force_low ? -1 : 0; 4579af9a816SDavid Yang 4589af9a816SDavid Yang led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 4599af9a816SDavid Yang if (!led) { 4609af9a816SDavid Yang res = -ENOMEM; 4619af9a816SDavid Yang goto err; 4629af9a816SDavid Yang } 4639af9a816SDavid Yang pp->leds[group] = led; 4649af9a816SDavid Yang 4659af9a816SDavid Yang led->port = pp; 4669af9a816SDavid Yang led->group = group; 4679af9a816SDavid Yang 4689af9a816SDavid Yang state = led_init_default_state_get(fwnode); 4699af9a816SDavid Yang 4709af9a816SDavid Yang mutex_lock(&priv->reg_lock); 4719af9a816SDavid Yang 4729af9a816SDavid Yang /* Inversion is internal - force on will give low logic. 4739af9a816SDavid Yang * In the rest of the file, treat LEDs as if active-low. 4749af9a816SDavid Yang */ 4751a2dbdb6SDavid Yang polarity_orig = 0; 4761a2dbdb6SDavid Yang if (polarity) { 4771a2dbdb6SDavid Yang res = yt921x_reg_read(priv, YT921X_LED_PAR_INV, &val); 4789af9a816SDavid Yang if (res) 4791a2dbdb6SDavid Yang goto err_inv; 4809af9a816SDavid Yang 4819af9a816SDavid Yang mask = YT921X_LED_PAR_INV_INVnm(group, port); 4821a2dbdb6SDavid Yang ctrl = polarity > 0 ? val | mask : val & ~mask; 4831a2dbdb6SDavid Yang if (ctrl != val) { 4849af9a816SDavid Yang res = yt921x_reg_write(priv, YT921X_LED_PAR_INV, ctrl); 4859af9a816SDavid Yang if (res) 4861a2dbdb6SDavid Yang goto err_inv; 4871a2dbdb6SDavid Yang 4881a2dbdb6SDavid Yang polarity_orig = -polarity; 4891a2dbdb6SDavid Yang } 4909af9a816SDavid Yang } 4919af9a816SDavid Yang 4921a2dbdb6SDavid Yang led2_orig = U32_MAX; 4931a2dbdb6SDavid Yang res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val); 4949af9a816SDavid Yang if (res) 4951a2dbdb6SDavid Yang goto err_led2; 4969af9a816SDavid Yang mask = YT921X_LED2_PORT_FORCEn_M(group); 4979af9a816SDavid Yang 4989af9a816SDavid Yang switch (state) { 4999af9a816SDavid Yang case LEDS_DEFSTATE_OFF: 5009af9a816SDavid Yang case LEDS_DEFSTATE_ON: 5019af9a816SDavid Yang default: 5029af9a816SDavid Yang on = state == LEDS_DEFSTATE_ON; 5031a2dbdb6SDavid Yang ctrl = (val & ~mask) | 5041a2dbdb6SDavid Yang (on ? YT921X_LED2_PORT_FORCEn_ON(group) : 5051a2dbdb6SDavid Yang YT921X_LED2_PORT_FORCEn_OFF(group)); 5061a2dbdb6SDavid Yang if (ctrl != val) { 5079af9a816SDavid Yang res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), 5081a2dbdb6SDavid Yang ctrl); 5099af9a816SDavid Yang if (res) 5101a2dbdb6SDavid Yang goto err_led2; 5111a2dbdb6SDavid Yang 5121a2dbdb6SDavid Yang led2_orig = val & mask; 5131a2dbdb6SDavid Yang } 5149af9a816SDavid Yang break; 5159af9a816SDavid Yang case LEDS_DEFSTATE_KEEP: 5161a2dbdb6SDavid Yang on = (val & mask) == YT921X_LED2_PORT_FORCEn_ON(group); 5179af9a816SDavid Yang break; 5189af9a816SDavid Yang } 5199af9a816SDavid Yang 5209af9a816SDavid Yang mutex_unlock(&priv->reg_lock); 5219af9a816SDavid Yang 5229af9a816SDavid Yang led_cdev = &led->cdev; 5239af9a816SDavid Yang led_cdev->brightness = on; 5249af9a816SDavid Yang led_cdev->max_brightness = 1; 5259af9a816SDavid Yang led_cdev->flags = LED_RETAIN_AT_SHUTDOWN; 5269af9a816SDavid Yang led_cdev->brightness_set_blocking = yt921x_cled_brightness_set_blocking; 5279af9a816SDavid Yang led_cdev->blink_set = yt921x_cled_blink_set; 5289af9a816SDavid Yang #ifdef CONFIG_LEDS_TRIGGERS 5299af9a816SDavid Yang led_cdev->hw_control_trigger = "netdev"; 5309af9a816SDavid Yang led_cdev->hw_control_get_device = yt921x_cled_hw_control_get_device; 5319af9a816SDavid Yang led_cdev->hw_control_is_supported = yt921x_cled_hw_control_is_supported; 5329af9a816SDavid Yang led_cdev->hw_control_get = yt921x_cled_hw_control_get; 5339af9a816SDavid Yang led_cdev->hw_control_set = yt921x_cled_hw_control_set; 5349af9a816SDavid Yang #endif 5359af9a816SDavid Yang 5369af9a816SDavid Yang snprintf(name, sizeof(name), YT921X_NAME "-%u:%02d:%02u", 5379af9a816SDavid Yang priv->ds.index, port, group); 5389af9a816SDavid Yang init_data = (typeof(init_data)){ 5399af9a816SDavid Yang .fwnode = fwnode, 5409af9a816SDavid Yang .default_label = ":port", 5419af9a816SDavid Yang .devicename = name, 5429af9a816SDavid Yang .devname_mandatory = true, 5439af9a816SDavid Yang }; 5449af9a816SDavid Yang res = devm_led_classdev_register_ext(dev, led_cdev, &init_data); 5459af9a816SDavid Yang if (res) 5469af9a816SDavid Yang goto revoke; 5479af9a816SDavid Yang 5489af9a816SDavid Yang return 0; 5499af9a816SDavid Yang 5509af9a816SDavid Yang revoke: 5519af9a816SDavid Yang mutex_lock(&priv->reg_lock); 5521a2dbdb6SDavid Yang if (led2_orig != U32_MAX) { 5531a2dbdb6SDavid Yang ret = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), 5541a2dbdb6SDavid Yang YT921X_LED2_PORT_FORCEn_M(group), 5551a2dbdb6SDavid Yang led2_orig); 5569af9a816SDavid Yang if (ret) 5579af9a816SDavid Yang dev_err(dev, 5589af9a816SDavid Yang "Failed to restore %s for LED %02d:%02u: %d\n", 5599af9a816SDavid Yang "LED2_PORT", port, group, ret); 5609af9a816SDavid Yang } 5611a2dbdb6SDavid Yang err_led2: 5621a2dbdb6SDavid Yang if (polarity_orig) { 5631a2dbdb6SDavid Yang ret = yt921x_reg_toggle_bits(priv, YT921X_LED_PAR_INV, 5641a2dbdb6SDavid Yang YT921X_LED_PAR_INV_INVnm(group, port), 5651a2dbdb6SDavid Yang polarity_orig > 0); 5669af9a816SDavid Yang if (ret) 5679af9a816SDavid Yang dev_err(dev, 5689af9a816SDavid Yang "Failed to restore %s for LED %02d:%02u: %d\n", 5699af9a816SDavid Yang "LED_PAR_INV", port, group, ret); 5709af9a816SDavid Yang } 5711a2dbdb6SDavid Yang err_inv: 5729af9a816SDavid Yang mutex_unlock(&priv->reg_lock); 5739af9a816SDavid Yang 5749af9a816SDavid Yang pp->leds[group] = NULL; 5759af9a816SDavid Yang devm_kfree(dev, led); 5769af9a816SDavid Yang 5779af9a816SDavid Yang err: 5789af9a816SDavid Yang dev_err(dev, "Failed to initialize LED %02d:%02u: %d\n", 5799af9a816SDavid Yang port, group, res); 5809af9a816SDavid Yang return res; 5819af9a816SDavid Yang } 5829af9a816SDavid Yang 5839af9a816SDavid Yang static void yt921x_leds_remove_port(struct yt921x_priv *priv, int port) 5849af9a816SDavid Yang { 585*cece0b57SDavid Yang struct device *dev = yt921x_priv_to_device(priv); 5869af9a816SDavid Yang struct yt921x_port *pp = &priv->ports[port]; 5879af9a816SDavid Yang 5889af9a816SDavid Yang for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) { 5899af9a816SDavid Yang struct yt921x_led *led = pp->leds[group]; 5909af9a816SDavid Yang 5919af9a816SDavid Yang if (led) { 5929af9a816SDavid Yang devm_led_classdev_unregister(dev, &led->cdev); 5939af9a816SDavid Yang pp->leds[group] = NULL; 5949af9a816SDavid Yang devm_kfree(dev, led); 5959af9a816SDavid Yang } 5969af9a816SDavid Yang } 5979af9a816SDavid Yang 5989af9a816SDavid Yang pp->led_duty_mask = 0; 5999af9a816SDavid Yang pp->led_cycle_mask = 0; 6009af9a816SDavid Yang } 6019af9a816SDavid Yang 6029af9a816SDavid Yang static int yt921x_leds_setup_port(struct yt921x_priv *priv, int port) 6039af9a816SDavid Yang { 604*cece0b57SDavid Yang struct device *dev = yt921x_priv_to_device(priv); 6059af9a816SDavid Yang struct dsa_switch *ds = &priv->ds; 6069af9a816SDavid Yang struct device_node *leds_np; 6079af9a816SDavid Yang struct dsa_port *dp; 6089af9a816SDavid Yang 6099af9a816SDavid Yang dp = dsa_to_port(ds, port); 6109af9a816SDavid Yang leds_np = of_get_child_by_name(dp->dn, "leds"); 6119af9a816SDavid Yang if (!leds_np) 6129af9a816SDavid Yang return 0; 6139af9a816SDavid Yang 6149af9a816SDavid Yang if (port >= YT921X_LED_PORT_NUM) { 6159af9a816SDavid Yang dev_err(dev, "Cannot configure LEDs for port %d\n", port); 6169af9a816SDavid Yang of_node_put(leds_np); 6179af9a816SDavid Yang return -EINVAL; 6189af9a816SDavid Yang } 6199af9a816SDavid Yang 6209af9a816SDavid Yang for_each_child_of_node_scoped(leds_np, led_np) { 6219af9a816SDavid Yang yt921x_led_setup(priv, port, of_fwnode_handle(led_np)); 6229af9a816SDavid Yang /* Allow partial configuration: LEDs are optional */ 6239af9a816SDavid Yang } 6249af9a816SDavid Yang 6259af9a816SDavid Yang of_node_put(leds_np); 6269af9a816SDavid Yang return 0; 6279af9a816SDavid Yang } 6289af9a816SDavid Yang 6299af9a816SDavid Yang void yt921x_leds_remove(struct yt921x_priv *priv) 6309af9a816SDavid Yang { 6319af9a816SDavid Yang for (int port = 0; port < YT921X_LED_PORT_NUM; port++) 6329af9a816SDavid Yang yt921x_leds_remove_port(priv, port); 6339af9a816SDavid Yang } 6349af9a816SDavid Yang 6359af9a816SDavid Yang int yt921x_leds_setup(struct yt921x_priv *priv) 6369af9a816SDavid Yang { 6379af9a816SDavid Yang struct dsa_switch *ds = &priv->ds; 6389af9a816SDavid Yang struct dsa_port *dp; 6399af9a816SDavid Yang 6409af9a816SDavid Yang /* LEDs are always enabled. There is no way to disable them altogether 6419af9a816SDavid Yang * (as far as I know). 6429af9a816SDavid Yang */ 6439af9a816SDavid Yang 6449af9a816SDavid Yang dsa_switch_for_each_port(dp, ds) { 6459af9a816SDavid Yang int port = dp->index; 6469af9a816SDavid Yang 6479af9a816SDavid Yang if (!dp->dn) 6489af9a816SDavid Yang continue; 6499af9a816SDavid Yang 6509af9a816SDavid Yang yt921x_leds_setup_port(priv, port); 6519af9a816SDavid Yang /* Allow partial configuration: LEDs are optional */ 6529af9a816SDavid Yang } 6539af9a816SDavid Yang 6549af9a816SDavid Yang return 0; 6559af9a816SDavid Yang } 656