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

leds: turris-omnia: Support HW controlled mode via private trigger

Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).

When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins

In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.

In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.

We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.

Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Marek Behún and committed by
Lee Jones
cbd6954f 9f028c9e

+91 -8
+1
drivers/leds/Kconfig
··· 187 187 depends on I2C 188 188 depends on MACH_ARMADA_38X || COMPILE_TEST 189 189 depends on OF 190 + select LEDS_TRIGGERS 190 191 help 191 192 This option enables basic support for the LEDs found on the front 192 193 side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
+90 -8
drivers/leds/leds-turris-omnia.c
··· 31 31 struct led_classdev_mc mc_cdev; 32 32 struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; 33 33 u8 cached_channels[OMNIA_LED_NUM_CHANNELS]; 34 - bool on; 34 + bool on, hwtrig; 35 35 int reg; 36 36 }; 37 37 ··· 120 120 121 121 /* 122 122 * Only recalculate RGB brightnesses from intensities if brightness is 123 - * non-zero. Otherwise we won't be using them and we can save ourselves 124 - * some software divisions (Omnia's CPU does not implement the division 125 - * instruction). 123 + * non-zero (if it is zero and the LED is in HW blinking mode, we use 124 + * max_brightness as brightness). Otherwise we won't be using them and 125 + * we can save ourselves some software divisions (Omnia's CPU does not 126 + * implement the division instruction). 126 127 */ 127 - if (brightness) { 128 - led_mc_calc_color_components(mc_cdev, brightness); 128 + if (brightness || led->hwtrig) { 129 + led_mc_calc_color_components(mc_cdev, brightness ?: 130 + cdev->max_brightness); 129 131 130 132 /* 131 133 * Send color command only if brightness is non-zero and the RGB ··· 137 135 err = omnia_led_send_color_cmd(leds->client, led); 138 136 } 139 137 140 - /* Send on/off state change only if (bool)brightness changed */ 141 - if (!err && !brightness != !led->on) { 138 + /* 139 + * Send on/off state change only if (bool)brightness changed and the LED 140 + * is not being blinked by HW. 141 + */ 142 + if (!err && !led->hwtrig && !brightness != !led->on) { 142 143 u8 state = CMD_LED_STATE_LED(led->reg); 143 144 144 145 if (brightness) ··· 156 151 157 152 return err; 158 153 } 154 + 155 + static struct led_hw_trigger_type omnia_hw_trigger_type; 156 + 157 + static int omnia_hwtrig_activate(struct led_classdev *cdev) 158 + { 159 + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 160 + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); 161 + struct omnia_led *led = to_omnia_led(mc_cdev); 162 + int err = 0; 163 + 164 + mutex_lock(&leds->lock); 165 + 166 + if (!led->on) { 167 + /* 168 + * If the LED is off (brightness was set to 0), the last 169 + * configured color was not necessarily sent to the MCU. 170 + * Recompute with max_brightness and send if needed. 171 + */ 172 + led_mc_calc_color_components(mc_cdev, cdev->max_brightness); 173 + 174 + if (omnia_led_channels_changed(led)) 175 + err = omnia_led_send_color_cmd(leds->client, led); 176 + } 177 + 178 + if (!err) { 179 + /* Put the LED into MCU controlled mode */ 180 + err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE, 181 + CMD_LED_MODE_LED(led->reg)); 182 + if (!err) 183 + led->hwtrig = true; 184 + } 185 + 186 + mutex_unlock(&leds->lock); 187 + 188 + return err; 189 + } 190 + 191 + static void omnia_hwtrig_deactivate(struct led_classdev *cdev) 192 + { 193 + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); 194 + struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev)); 195 + int err; 196 + 197 + mutex_lock(&leds->lock); 198 + 199 + led->hwtrig = false; 200 + 201 + /* Put the LED into software mode */ 202 + err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE, 203 + CMD_LED_MODE_LED(led->reg) | 204 + CMD_LED_MODE_USER); 205 + 206 + mutex_unlock(&leds->lock); 207 + 208 + if (err < 0) 209 + dev_err(cdev->dev, "Cannot put LED to software mode: %i\n", 210 + err); 211 + } 212 + 213 + static struct led_trigger omnia_hw_trigger = { 214 + .name = "omnia-mcu", 215 + .activate = omnia_hwtrig_activate, 216 + .deactivate = omnia_hwtrig_deactivate, 217 + .trigger_type = &omnia_hw_trigger_type, 218 + }; 159 219 160 220 static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, 161 221 struct device_node *np) ··· 265 195 cdev = &led->mc_cdev.led_cdev; 266 196 cdev->max_brightness = 255; 267 197 cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; 198 + cdev->trigger_type = &omnia_hw_trigger_type; 199 + /* 200 + * Use the omnia-mcu trigger as the default trigger. It may be rewritten 201 + * by LED class from the linux,default-trigger property. 202 + */ 203 + cdev->default_trigger = omnia_hw_trigger.name; 268 204 269 205 /* put the LED into software mode */ 270 206 ret = omnia_cmd_write_u8(client, CMD_LED_MODE, ··· 383 307 i2c_set_clientdata(client, leds); 384 308 385 309 mutex_init(&leds->lock); 310 + 311 + ret = devm_led_trigger_register(dev, &omnia_hw_trigger); 312 + if (ret < 0) { 313 + dev_err(dev, "Cannot register private LED trigger: %d\n", ret); 314 + return ret; 315 + } 386 316 387 317 led = &leds->leds[0]; 388 318 for_each_available_child_of_node(np, child) {