|
|
@@ -0,0 +1,202 @@
|
|
|
+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
|
|
|
+@@ -163,6 +163,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);
|