123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- From e965e0f6de60874fc0a0caed9a9e0122999e0c7b Mon Sep 17 00:00:00 2001
- From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <[email protected]>
- Date: Mon, 18 Sep 2023 18:11:03 +0200
- Subject: [PATCH 5/6] leds: turris-omnia: Support HW controlled mode via
- private trigger
- MIME-Version: 1.0
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- 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 <[email protected]>
- Link: https://lore.kernel.org/r/[email protected]
- Signed-off-by: Lee Jones <[email protected]>
- ---
- drivers/leds/Kconfig | 1 +
- drivers/leds/leds-turris-omnia.c | 98 +++++++++++++++++++++++++++++---
- 2 files changed, 91 insertions(+), 8 deletions(-)
- --- a/drivers/leds/Kconfig
- +++ b/drivers/leds/Kconfig
- @@ -164,6 +164,7 @@ config LEDS_TURRIS_OMNIA
- depends on I2C
- depends on MACH_ARMADA_38X || COMPILE_TEST
- depends on OF
- + select LEDS_TRIGGERS
- help
- This option enables basic support for the LEDs found on the front
- side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
- --- a/drivers/leds/leds-turris-omnia.c
- +++ b/drivers/leds/leds-turris-omnia.c
- @@ -31,7 +31,7 @@ struct omnia_led {
- struct led_classdev_mc mc_cdev;
- struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
- u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
- - bool on;
- + bool on, hwtrig;
- int reg;
- };
-
- @@ -120,12 +120,14 @@ static int omnia_led_brightness_set_bloc
-
- /*
- * Only recalculate RGB brightnesses from intensities if brightness is
- - * non-zero. Otherwise we won't be using them and we can save ourselves
- - * some software divisions (Omnia's CPU does not implement the division
- - * instruction).
- + * non-zero (if it is zero and the LED is in HW blinking mode, we use
- + * max_brightness as brightness). Otherwise we won't be using them and
- + * we can save ourselves some software divisions (Omnia's CPU does not
- + * implement the division instruction).
- */
- - if (brightness) {
- - led_mc_calc_color_components(mc_cdev, brightness);
- + if (brightness || led->hwtrig) {
- + led_mc_calc_color_components(mc_cdev, brightness ?:
- + cdev->max_brightness);
-
- /*
- * Send color command only if brightness is non-zero and the RGB
- @@ -135,8 +137,11 @@ static int omnia_led_brightness_set_bloc
- err = omnia_led_send_color_cmd(leds->client, led);
- }
-
- - /* Send on/off state change only if (bool)brightness changed */
- - if (!err && !brightness != !led->on) {
- + /*
- + * Send on/off state change only if (bool)brightness changed and the LED
- + * is not being blinked by HW.
- + */
- + if (!err && !led->hwtrig && !brightness != !led->on) {
- u8 state = CMD_LED_STATE_LED(led->reg);
-
- if (brightness)
- @@ -152,6 +157,71 @@ static int omnia_led_brightness_set_bloc
- return err;
- }
-
- +static struct led_hw_trigger_type omnia_hw_trigger_type;
- +
- +static int omnia_hwtrig_activate(struct led_classdev *cdev)
- +{
- + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
- + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
- + struct omnia_led *led = to_omnia_led(mc_cdev);
- + int err = 0;
- +
- + mutex_lock(&leds->lock);
- +
- + if (!led->on) {
- + /*
- + * If the LED is off (brightness was set to 0), the last
- + * configured color was not necessarily sent to the MCU.
- + * Recompute with max_brightness and send if needed.
- + */
- + led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
- +
- + if (omnia_led_channels_changed(led))
- + err = omnia_led_send_color_cmd(leds->client, led);
- + }
- +
- + if (!err) {
- + /* Put the LED into MCU controlled mode */
- + err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
- + CMD_LED_MODE_LED(led->reg));
- + if (!err)
- + led->hwtrig = true;
- + }
- +
- + mutex_unlock(&leds->lock);
- +
- + return err;
- +}
- +
- +static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
- +{
- + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
- + struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
- + int err;
- +
- + mutex_lock(&leds->lock);
- +
- + led->hwtrig = false;
- +
- + /* Put the LED into software mode */
- + err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
- + CMD_LED_MODE_LED(led->reg) |
- + CMD_LED_MODE_USER);
- +
- + mutex_unlock(&leds->lock);
- +
- + if (err < 0)
- + dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
- + err);
- +}
- +
- +static struct led_trigger omnia_hw_trigger = {
- + .name = "omnia-mcu",
- + .activate = omnia_hwtrig_activate,
- + .deactivate = omnia_hwtrig_deactivate,
- + .trigger_type = &omnia_hw_trigger_type,
- +};
- +
- static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
- struct device_node *np)
- {
- @@ -195,6 +265,12 @@ static int omnia_led_register(struct i2c
- cdev = &led->mc_cdev.led_cdev;
- cdev->max_brightness = 255;
- cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
- + cdev->trigger_type = &omnia_hw_trigger_type;
- + /*
- + * Use the omnia-mcu trigger as the default trigger. It may be rewritten
- + * by LED class from the linux,default-trigger property.
- + */
- + cdev->default_trigger = omnia_hw_trigger.name;
-
- /* put the LED into software mode */
- ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
- @@ -309,6 +385,12 @@ static int omnia_leds_probe(struct i2c_c
-
- mutex_init(&leds->lock);
-
- + ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
- + if (ret < 0) {
- + dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
- + return ret;
- + }
- +
- led = &leds->leds[0];
- for_each_available_child_of_node(np, child) {
- ret = omnia_led_register(client, led, child);
|