From 0be05de57756912973937b25e0b94f4e986bcb73 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20Moj=C3=ADk?= Date: Mon, 16 Oct 2023 17:14:18 +0200 Subject: [PATCH] kernel: 6.1: Backport Turris Omnia LED patches MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This backports patches leds: turris-omnia: Use sysfs_emit() instead of sprintf() leds: turris-omnia: Drop unnecessary mutex locking leds: turris-omnia: Do not use SMBUS calls leds: turris-omnia: Make set_brightness() more efficient leds: turris-omnia: Support HW controlled mode via private trigger leds: turris-omnia: Add support for enabling/disabling HW gamma correction leds: turris-omnia: Fix brightness setting and trigger activating into backport-6.1. The above patches replace: leds: turris-omnia: support HW controlled mode via private trigger leds: turris-omnia: initialize multi-intensity to full leds: turris-omnia: change max brightness from 255 to 1 from mvebu/patches-6.1. Signed-off-by: Marek Mojík --- ...ia-Use-sysfs_emit-instead-of-sprintf.patch | 29 +++ ...omnia-Drop-unnecessary-mutex-locking.patch | 64 +++++ ...-turris-omnia-Do-not-use-SMBUS-calls.patch | 145 +++++++++++ ...a-Make-set_brightness-more-efficient.patch | 207 +++++++++++++++ ...a-Support-HW-controlled-mode-via-pri.patch | 202 +++++++++++++++ ...a-Add-support-for-enabling-disabling.patch | 244 ++++++++++++++++++ ...a-Fix-brightness-setting-and-trigger.patch | 167 ++++++++++++ ...a-support-HW-controlled-mode-via-pri.patch | 118 --------- ...a-initialize-multi-intensity-to-full.patch | 33 --- ...a-change-max-brightness-from-255-to-.patch | 31 --- 10 files changed, 1058 insertions(+), 182 deletions(-) create mode 100644 target/linux/generic/backport-6.1/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch create mode 100644 target/linux/generic/backport-6.1/815-v6.6-2-leds-turris-omnia-Drop-unnecessary-mutex-locking.patch create mode 100644 target/linux/generic/backport-6.1/815-v6.7-1-leds-turris-omnia-Do-not-use-SMBUS-calls.patch create mode 100644 target/linux/generic/backport-6.1/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch create mode 100644 target/linux/generic/backport-6.1/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch create mode 100644 target/linux/generic/backport-6.1/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch create mode 100644 target/linux/generic/backport-6.1/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch delete mode 100644 target/linux/mvebu/patches-6.1/102-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch delete mode 100644 target/linux/mvebu/patches-6.1/103-leds-turris-omnia-initialize-multi-intensity-to-full.patch delete mode 100644 target/linux/mvebu/patches-6.1/104-leds-turris-omnia-change-max-brightness-from-255-to-.patch diff --git a/target/linux/generic/backport-6.1/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch b/target/linux/generic/backport-6.1/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch new file mode 100644 index 0000000000..2ade1025ea --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.6-1-leds-turris-omnia-Use-sysfs_emit-instead-of-sprintf.patch @@ -0,0 +1,29 @@ +From a75b58a46423cfd9b1f73581f4bd2ac2ae743996 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +Date: Wed, 2 Aug 2023 18:07:45 +0200 +Subject: [PATCH 1/6] leds: turris-omnia: Use sysfs_emit() instead of sprintf() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Use the dedicated sysfs_emit() function instead of sprintf() in sysfs +attribute accessor brightness_show(). + +Signed-off-by: Marek Behún +Link: https://lore.kernel.org/r/20230802160748.11208-4-kabel@kernel.org +Signed-off-by: Lee Jones +--- + drivers/leds/leds-turris-omnia.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/leds/leds-turris-omnia.c ++++ b/drivers/leds/leds-turris-omnia.c +@@ -166,7 +166,7 @@ static ssize_t brightness_show(struct de + if (ret < 0) + return ret; + +- return sprintf(buf, "%d\n", ret); ++ return sysfs_emit(buf, "%d\n", ret); + } + + static ssize_t brightness_store(struct device *dev, struct device_attribute *a, diff --git a/target/linux/generic/backport-6.1/815-v6.6-2-leds-turris-omnia-Drop-unnecessary-mutex-locking.patch b/target/linux/generic/backport-6.1/815-v6.6-2-leds-turris-omnia-Drop-unnecessary-mutex-locking.patch new file mode 100644 index 0000000000..74bde4706d --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.6-2-leds-turris-omnia-Drop-unnecessary-mutex-locking.patch @@ -0,0 +1,64 @@ +From 8f3d612a5c949489b2860b74ff34c5914a9216dd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +Date: Wed, 2 Aug 2023 18:07:43 +0200 +Subject: [PATCH 2/6] leds: turris-omnia: Drop unnecessary mutex locking +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Do not lock driver mutex in the global LED panel brightness sysfs +accessors brightness_show() and brightness_store(). + +The mutex locking is unnecessary here. The I2C transfers are guarded by +I2C core locking mechanism, and the LED commands itself do not interfere +with other commands. + +Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs") +Signed-off-by: Marek Behún +Reviewed-by: Lee Jones +Link: https://lore.kernel.org/r/20230802160748.11208-2-kabel@kernel.org +Signed-off-by: Lee Jones +--- + drivers/leds/leds-turris-omnia.c | 11 +---------- + 1 file changed, 1 insertion(+), 10 deletions(-) + +--- a/drivers/leds/leds-turris-omnia.c ++++ b/drivers/leds/leds-turris-omnia.c +@@ -156,12 +156,9 @@ static ssize_t brightness_show(struct de + char *buf) + { + struct i2c_client *client = to_i2c_client(dev); +- struct omnia_leds *leds = i2c_get_clientdata(client); + int ret; + +- mutex_lock(&leds->lock); + ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); +- mutex_unlock(&leds->lock); + + if (ret < 0) + return ret; +@@ -173,7 +170,6 @@ static ssize_t brightness_store(struct d + const char *buf, size_t count) + { + struct i2c_client *client = to_i2c_client(dev); +- struct omnia_leds *leds = i2c_get_clientdata(client); + unsigned long brightness; + int ret; + +@@ -183,15 +179,10 @@ static ssize_t brightness_store(struct d + if (brightness > 100) + return -EINVAL; + +- mutex_lock(&leds->lock); + ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, + (u8)brightness); +- mutex_unlock(&leds->lock); + +- if (ret < 0) +- return ret; +- +- return count; ++ return ret < 0 ? ret : count; + } + static DEVICE_ATTR_RW(brightness); + diff --git a/target/linux/generic/backport-6.1/815-v6.7-1-leds-turris-omnia-Do-not-use-SMBUS-calls.patch b/target/linux/generic/backport-6.1/815-v6.7-1-leds-turris-omnia-Do-not-use-SMBUS-calls.patch new file mode 100644 index 0000000000..6b3fe0d74d --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.7-1-leds-turris-omnia-Do-not-use-SMBUS-calls.patch @@ -0,0 +1,145 @@ +From 1848bb28f0579582f653ae95355b544fd8a51d1e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +Date: Mon, 18 Sep 2023 18:11:01 +0200 +Subject: [PATCH 3/6] leds: turris-omnia: Do not use SMBUS calls +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The leds-turris-omnia driver uses three function for I2C access: +- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which + cause an emulated SMBUS transfer, +- i2c_master_send(), which causes an ordinary I2C transfer. + +The Turris Omnia MCU LED controller is not semantically SMBUS, it +operates as a simple I2C bus. It does not implement any of the SMBUS +specific features, like PEC, or procedure calls, or anything. Moreover +the I2C controller driver also does not implement SMBUS, and so the +emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for +the SMBUS calls, which gives an unnecessary overhead. + +When I first wrote the driver, I was unaware of these facts, and I +simply used the first function that worked. + +Drop the I2C SMBUS calls and instead use simple I2C transfers. + +Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs") +Signed-off-by: Marek Behún +Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org +Signed-off-by: Lee Jones +--- + drivers/leds/leds-turris-omnia.c | 54 +++++++++++++++++++++++++------- + 1 file changed, 42 insertions(+), 12 deletions(-) + +--- a/drivers/leds/leds-turris-omnia.c ++++ b/drivers/leds/leds-turris-omnia.c +@@ -2,7 +2,7 @@ + /* + * CZ.NIC's Turris Omnia LEDs driver + * +- * 2020 by Marek Behún ++ * 2020, 2023 by Marek Behún + */ + + #include +@@ -41,6 +41,37 @@ struct omnia_leds { + struct omnia_led leds[]; + }; + ++static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val) ++{ ++ u8 buf[2] = { cmd, val }; ++ ++ return i2c_master_send(client, buf, sizeof(buf)); ++} ++ ++static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) ++{ ++ struct i2c_msg msgs[2]; ++ u8 reply; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = 0; ++ msgs[0].len = 1; ++ msgs[0].buf = &cmd; ++ msgs[1].addr = client->addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = 1; ++ msgs[1].buf = &reply; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (likely(ret == ARRAY_SIZE(msgs))) ++ return reply; ++ else if (ret < 0) ++ return ret; ++ else ++ return -EIO; ++} ++ + static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, + enum led_brightness brightness) + { +@@ -64,7 +95,7 @@ static int omnia_led_brightness_set_bloc + if (buf[2] || buf[3] || buf[4]) + state |= CMD_LED_STATE_ON; + +- ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state); ++ ret = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state); + if (ret >= 0 && (state & CMD_LED_STATE_ON)) + ret = i2c_master_send(leds->client, buf, 5); + +@@ -114,9 +145,9 @@ static int omnia_led_register(struct i2c + cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; + + /* put the LED into software mode */ +- ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE, +- CMD_LED_MODE_LED(led->reg) | +- CMD_LED_MODE_USER); ++ ret = omnia_cmd_write_u8(client, CMD_LED_MODE, ++ CMD_LED_MODE_LED(led->reg) | ++ CMD_LED_MODE_USER); + if (ret < 0) { + dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, + ret); +@@ -124,8 +155,8 @@ static int omnia_led_register(struct i2c + } + + /* disable the LED */ +- ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, +- CMD_LED_STATE_LED(led->reg)); ++ ret = omnia_cmd_write_u8(client, CMD_LED_STATE, ++ CMD_LED_STATE_LED(led->reg)); + if (ret < 0) { + dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); + return ret; +@@ -158,7 +189,7 @@ static ssize_t brightness_show(struct de + struct i2c_client *client = to_i2c_client(dev); + int ret; + +- ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); ++ ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS); + + if (ret < 0) + return ret; +@@ -179,8 +210,7 @@ static ssize_t brightness_store(struct d + if (brightness > 100) + return -EINVAL; + +- ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, +- (u8)brightness); ++ ret = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness); + + return ret < 0 ? ret : count; + } +@@ -238,8 +268,8 @@ static void omnia_leds_remove(struct i2c + u8 buf[5]; + + /* put all LEDs into default (HW triggered) mode */ +- i2c_smbus_write_byte_data(client, CMD_LED_MODE, +- CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); ++ omnia_cmd_write_u8(client, CMD_LED_MODE, ++ CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); + + /* set all LEDs color to [255, 255, 255] */ + buf[0] = CMD_LED_COLOR; diff --git a/target/linux/generic/backport-6.1/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch b/target/linux/generic/backport-6.1/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch new file mode 100644 index 0000000000..d84ad67125 --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.7-2-leds-turris-omnia-Make-set_brightness-more-efficient.patch @@ -0,0 +1,207 @@ +From a64c3c1357275b1fd61bc9734f638cdb5d8a8bbb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +Date: Mon, 18 Sep 2023 18:11:02 +0200 +Subject: [PATCH 4/6] leds: turris-omnia: Make set_brightness() more efficient +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Implement caching of the LED color and state values that are sent to MCU +in order to make the set_brightness() operation more efficient by +avoiding I2C transactions which are not needed. + +On Turris Omnia's MCU, which acts as the RGB LED controller, each LED +has a RGB color, and a ON/OFF state, which are configurable via I2C +commands CMD_LED_COLOR and CMD_LED_STATE. + +The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2 +bytes over the I2C bus, which operates at 100 kHz. With I2C overhead +this allows ~1670 color changing commands and ~3200 state changing +commands per second (or around 1000 color + state changes per second). +This may seem more than enough, but the issue is that the I2C bus is +shared with another peripheral, the MCU. The MCU exposes an interrupt +interface, and it can trigger hundreds of interrupts per second. Each +time, we need to read the interrupt state register over this I2C bus. +Whenever we are sending a LED color/state changing command, the +interrupt reading is waiting. + +Currently, every time LED brightness or LED multi intensity is changed, +we send a CMD_LED_STATE command, and if the computed color (brightness +adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR +command. + +Consider for example the situation when we have a netdev trigger enabled +for a LED. The netdev trigger does not change the LED color, only the +brightness (either to 0 or to currently configured brightness), and so +there is no need to send the CMD_LED_COLOR command. But each change of +brightness to 0 sends one CMD_LED_STATE command, and each change of +brightness to max_brightness sends one CMD_LED_STATE command and one +CMD_LED_COLOR command: + set_brightness(0) -> CMD_LED_STATE + set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR + (unnecessary) + +We can avoid the unnecessary I2C transactions if we cache the values of +state and color that are sent to the controller. If the color does not +change from the one previously sent, there is no need to do the +CMD_LED_COLOR I2C transaction, and if the state does not change, there +is no need to do the CMD_LED_STATE transaction. + +Because we need to make sure that our cached values are consistent with +the controller state, add explicit setting of the LED color to white at +probe time (this is the default setting when MCU resets, but does not +necessarily need to be the case, for example if U-Boot played with the +LED colors). + +Signed-off-by: Marek Behún +Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org +Signed-off-by: Lee Jones +--- + drivers/leds/leds-turris-omnia.c | 96 ++++++++++++++++++++++++++------ + 1 file changed, 78 insertions(+), 18 deletions(-) + +--- a/drivers/leds/leds-turris-omnia.c ++++ b/drivers/leds/leds-turris-omnia.c +@@ -30,6 +30,8 @@ + 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; + int reg; + }; + +@@ -72,36 +74,82 @@ static int omnia_cmd_read_u8(const struc + return -EIO; + } + ++static int omnia_led_send_color_cmd(const struct i2c_client *client, ++ struct omnia_led *led) ++{ ++ char cmd[5]; ++ int ret; ++ ++ cmd[0] = CMD_LED_COLOR; ++ cmd[1] = led->reg; ++ cmd[2] = led->subled_info[0].brightness; ++ cmd[3] = led->subled_info[1].brightness; ++ cmd[4] = led->subled_info[2].brightness; ++ ++ /* Send the color change command */ ++ ret = i2c_master_send(client, cmd, 5); ++ if (ret < 0) ++ return ret; ++ ++ /* Cache the RGB channel brightnesses */ ++ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) ++ led->cached_channels[i] = led->subled_info[i].brightness; ++ ++ return 0; ++} ++ ++/* Determine if the computed RGB channels are different from the cached ones */ ++static bool omnia_led_channels_changed(struct omnia_led *led) ++{ ++ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) ++ if (led->subled_info[i].brightness != led->cached_channels[i]) ++ return true; ++ ++ return false; ++} ++ + static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, + enum led_brightness brightness) + { + 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); +- u8 buf[5], state; +- int ret; ++ int err = 0; + + mutex_lock(&leds->lock); + +- led_mc_calc_color_components(&led->mc_cdev, brightness); ++ /* ++ * 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). ++ */ ++ if (brightness) { ++ led_mc_calc_color_components(mc_cdev, brightness); ++ ++ /* ++ * Send color command only if brightness is non-zero and the RGB ++ * channel brightnesses changed. ++ */ ++ if (omnia_led_channels_changed(led)) ++ err = omnia_led_send_color_cmd(leds->client, led); ++ } + +- buf[0] = CMD_LED_COLOR; +- buf[1] = led->reg; +- buf[2] = mc_cdev->subled_info[0].brightness; +- buf[3] = mc_cdev->subled_info[1].brightness; +- buf[4] = mc_cdev->subled_info[2].brightness; +- +- state = CMD_LED_STATE_LED(led->reg); +- if (buf[2] || buf[3] || buf[4]) +- state |= CMD_LED_STATE_ON; +- +- ret = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state); +- if (ret >= 0 && (state & CMD_LED_STATE_ON)) +- ret = i2c_master_send(leds->client, buf, 5); ++ /* Send on/off state change only if (bool)brightness changed */ ++ if (!err && !brightness != !led->on) { ++ u8 state = CMD_LED_STATE_LED(led->reg); ++ ++ if (brightness) ++ state |= CMD_LED_STATE_ON; ++ ++ err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state); ++ if (!err) ++ led->on = !!brightness; ++ } + + mutex_unlock(&leds->lock); + +- return ret; ++ return err; + } + + static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, +@@ -129,11 +177,15 @@ static int omnia_led_register(struct i2c + } + + led->subled_info[0].color_index = LED_COLOR_ID_RED; +- led->subled_info[0].channel = 0; + led->subled_info[1].color_index = LED_COLOR_ID_GREEN; +- led->subled_info[1].channel = 1; + led->subled_info[2].color_index = LED_COLOR_ID_BLUE; +- led->subled_info[2].channel = 2; ++ ++ /* Initial color is white */ ++ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) { ++ led->subled_info[i].intensity = 255; ++ led->subled_info[i].brightness = 255; ++ led->subled_info[i].channel = i; ++ } + + led->mc_cdev.subled_info = led->subled_info; + led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS; +@@ -162,6 +214,14 @@ static int omnia_led_register(struct i2c + return ret; + } + ++ /* Set initial color and cache it */ ++ ret = omnia_led_send_color_cmd(client, led); ++ if (ret < 0) { ++ dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np, ++ ret); ++ return ret; ++ } ++ + ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, + &init_data); + if (ret < 0) { diff --git a/target/linux/generic/backport-6.1/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch b/target/linux/generic/backport-6.1/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch new file mode 100644 index 0000000000..f64bbc7782 --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.7-3-leds-turris-omnia-Support-HW-controlled-mode-via-pri.patch @@ -0,0 +1,202 @@ +From e965e0f6de60874fc0a0caed9a9e0122999e0c7b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +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 +Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org +Signed-off-by: Lee Jones +--- + 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); diff --git a/target/linux/generic/backport-6.1/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch b/target/linux/generic/backport-6.1/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch new file mode 100644 index 0000000000..e98397922d --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.7-4-leds-turris-omnia-Add-support-for-enabling-disabling.patch @@ -0,0 +1,244 @@ +From 0efb3f9609d3de5a7d8c31e3835d7eb3e6adce79 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +Date: Mon, 18 Sep 2023 18:11:04 +0200 +Subject: [PATCH 6/6] leds: turris-omnia: Add support for enabling/disabling HW + gamma correction +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If the MCU on Turris Omnia is running newer firmware versions, the LED +controller supports RGB gamma correction (and enables it by default for +newer boards). + +Determine whether the gamma correction setting feature is supported and +add the ability to set it via sysfs attribute file. + +Signed-off-by: Marek Behún +Link: https://lore.kernel.org/r/20230918161104.20860-5-kabel@kernel.org +Signed-off-by: Lee Jones +--- + .../sysfs-class-led-driver-turris-omnia | 14 ++ + drivers/leds/leds-turris-omnia.c | 137 +++++++++++++++--- + 2 files changed, 134 insertions(+), 17 deletions(-) + +--- a/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia ++++ b/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia +@@ -12,3 +12,17 @@ Description: (RW) On the front panel of + able to change this setting from software. + + Format: %i ++ ++What: /sys/class/leds//device/gamma_correction ++Date: August 2023 ++KernelVersion: 6.6 ++Contact: Marek Behún ++Description: (RW) Newer versions of the microcontroller firmware of the ++ Turris Omnia router support gamma correction for the RGB LEDs. ++ This feature can be enabled/disabled by writing to this file. ++ ++ If the feature is not supported because the MCU firmware is too ++ old, the file always reads as 0, and writing to the file results ++ in the EOPNOTSUPP error. ++ ++ Format: %i +--- a/drivers/leds/leds-turris-omnia.c ++++ b/drivers/leds/leds-turris-omnia.c +@@ -15,17 +15,30 @@ + #define OMNIA_BOARD_LEDS 12 + #define OMNIA_LED_NUM_CHANNELS 3 + +-#define CMD_LED_MODE 3 +-#define CMD_LED_MODE_LED(l) ((l) & 0x0f) +-#define CMD_LED_MODE_USER 0x10 +- +-#define CMD_LED_STATE 4 +-#define CMD_LED_STATE_LED(l) ((l) & 0x0f) +-#define CMD_LED_STATE_ON 0x10 +- +-#define CMD_LED_COLOR 5 +-#define CMD_LED_SET_BRIGHTNESS 7 +-#define CMD_LED_GET_BRIGHTNESS 8 ++/* MCU controller commands at I2C address 0x2a */ ++#define OMNIA_MCU_I2C_ADDR 0x2a ++ ++#define CMD_GET_STATUS_WORD 0x01 ++#define STS_FEATURES_SUPPORTED BIT(2) ++ ++#define CMD_GET_FEATURES 0x10 ++#define FEAT_LED_GAMMA_CORRECTION BIT(5) ++ ++/* LED controller commands at I2C address 0x2b */ ++#define CMD_LED_MODE 0x03 ++#define CMD_LED_MODE_LED(l) ((l) & 0x0f) ++#define CMD_LED_MODE_USER 0x10 ++ ++#define CMD_LED_STATE 0x04 ++#define CMD_LED_STATE_LED(l) ((l) & 0x0f) ++#define CMD_LED_STATE_ON 0x10 ++ ++#define CMD_LED_COLOR 0x05 ++#define CMD_LED_SET_BRIGHTNESS 0x07 ++#define CMD_LED_GET_BRIGHTNESS 0x08 ++ ++#define CMD_SET_GAMMA_CORRECTION 0x30 ++#define CMD_GET_GAMMA_CORRECTION 0x31 + + struct omnia_led { + struct led_classdev_mc mc_cdev; +@@ -40,6 +53,7 @@ struct omnia_led { + struct omnia_leds { + struct i2c_client *client; + struct mutex lock; ++ bool has_gamma_correction; + struct omnia_led leds[]; + }; + +@@ -50,30 +64,42 @@ static int omnia_cmd_write_u8(const stru + return i2c_master_send(client, buf, sizeof(buf)); + } + +-static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) ++static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, ++ void *reply, size_t len) + { + struct i2c_msg msgs[2]; +- u8 reply; + int ret; + +- msgs[0].addr = client->addr; ++ msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &cmd; +- msgs[1].addr = client->addr; ++ msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; +- msgs[1].len = 1; +- msgs[1].buf = &reply; ++ msgs[1].len = len; ++ msgs[1].buf = reply; + +- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); + if (likely(ret == ARRAY_SIZE(msgs))) +- return reply; ++ return len; + else if (ret < 0) + return ret; + else + return -EIO; + } + ++static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) ++{ ++ u8 reply; ++ int ret; ++ ++ ret = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); ++ if (ret < 0) ++ return ret; ++ ++ return reply; ++} ++ + static int omnia_led_send_color_cmd(const struct i2c_client *client, + struct omnia_led *led) + { +@@ -352,12 +378,74 @@ static ssize_t brightness_store(struct d + } + static DEVICE_ATTR_RW(brightness); + ++static ssize_t gamma_correction_show(struct device *dev, ++ struct device_attribute *a, char *buf) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct omnia_leds *leds = i2c_get_clientdata(client); ++ int ret; ++ ++ if (leds->has_gamma_correction) { ++ ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION); ++ if (ret < 0) ++ return ret; ++ } else { ++ ret = 0; ++ } ++ ++ return sysfs_emit(buf, "%d\n", !!ret); ++} ++ ++static ssize_t gamma_correction_store(struct device *dev, ++ struct device_attribute *a, ++ const char *buf, size_t count) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct omnia_leds *leds = i2c_get_clientdata(client); ++ bool val; ++ int ret; ++ ++ if (!leds->has_gamma_correction) ++ return -EOPNOTSUPP; ++ ++ if (kstrtobool(buf, &val) < 0) ++ return -EINVAL; ++ ++ ret = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); ++ ++ return ret < 0 ? ret : count; ++} ++static DEVICE_ATTR_RW(gamma_correction); ++ + static struct attribute *omnia_led_controller_attrs[] = { + &dev_attr_brightness.attr, ++ &dev_attr_gamma_correction.attr, + NULL, + }; + ATTRIBUTE_GROUPS(omnia_led_controller); + ++static int omnia_mcu_get_features(const struct i2c_client *client) ++{ ++ u16 reply; ++ int err; ++ ++ err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, ++ CMD_GET_STATUS_WORD, &reply, sizeof(reply)); ++ if (err < 0) ++ return err; ++ ++ /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ ++ if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED)) ++ return 0; ++ ++ err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, ++ CMD_GET_FEATURES, &reply, sizeof(reply)); ++ if (err < 0) ++ return err; ++ ++ return le16_to_cpu(reply); ++} ++ + static int omnia_leds_probe(struct i2c_client *client, + const struct i2c_device_id *id) + { +@@ -383,6 +471,21 @@ static int omnia_leds_probe(struct i2c_c + leds->client = client; + i2c_set_clientdata(client, leds); + ++ ret = omnia_mcu_get_features(client); ++ if (ret < 0) { ++ dev_err(dev, "Cannot determine MCU supported features: %d\n", ++ ret); ++ return ret; ++ } ++ ++ leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION; ++ if (!leds->has_gamma_correction) { ++ dev_info(dev, ++ "Your board's MCU firmware does not support the LED gamma correction feature.\n"); ++ dev_info(dev, ++ "Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); ++ } ++ + mutex_init(&leds->lock); + + ret = devm_led_trigger_register(dev, &omnia_hw_trigger); diff --git a/target/linux/generic/backport-6.1/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch b/target/linux/generic/backport-6.1/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch new file mode 100644 index 0000000000..b0cebdcf14 --- /dev/null +++ b/target/linux/generic/backport-6.1/815-v6.7-5-leds-turris-omnia-Fix-brightness-setting-and-trigger.patch @@ -0,0 +1,167 @@ +From ffec49d391c5f0195360912b216aa24dbc9b53c8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marek=20Beh=C3=BAn?= +Date: Mon, 16 Oct 2023 16:15:38 +0200 +Subject: [PATCH] leds: turris-omnia: Fix brightness setting and trigger + activating +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +I have improperly refactored commits + 4d5ed2621c24 ("leds: turris-omnia: Make set_brightness() more efficient") +and + aaf38273cf76 ("leds: turris-omnia: Support HW controlled mode via private trigger") +after Lee requested a change in API semantics of the new functions I +introduced in commit + 28350bc0ac77 ("leds: turris-omnia: Do not use SMBUS calls"). + +Before the change, the function omnia_cmd_write_u8() returned 0 on +success, and afterwards it returned a positive value (number of bytes +written). The latter version was applied, but the following commits did +not properly account for this change. + +This results in non-functional LED's .brightness_set_blocking() and +trigger's .activate() methods. + +The main reasoning behind the semantics change was that read/write +methods should return the number of read/written bytes on success. +It was pointed to me [1] that this is not always true (for example the +regmap API does not do so), and since the driver never uses this number +of read/written bytes information, I decided to fix this issue by +changing the functions to the original semantics (return 0 on success). + +[1] https://lore.kernel.org/linux-gpio/ZQnn+Gi0xVlsGCYA@smile.fi.intel.com/ + +Fixes: 28350bc0ac77 ("leds: turris-omnia: Do not use SMBUS calls") +Signed-off-by: Marek Behún +--- + drivers/leds/leds-turris-omnia.c | 37 +++++++++++++++++--------------- + 1 file changed, 20 insertions(+), 17 deletions(-) + +--- a/drivers/leds/leds-turris-omnia.c ++++ b/drivers/leds/leds-turris-omnia.c +@@ -60,8 +60,11 @@ struct omnia_leds { + static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val) + { + u8 buf[2] = { cmd, val }; ++ int ret; ++ ++ ret = i2c_master_send(client, buf, sizeof(buf)); + +- return i2c_master_send(client, buf, sizeof(buf)); ++ return ret < 0 ? ret : 0; + } + + static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, +@@ -81,7 +84,7 @@ static int omnia_cmd_read_raw(struct i2c + + ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); + if (likely(ret == ARRAY_SIZE(msgs))) +- return len; ++ return 0; + else if (ret < 0) + return ret; + else +@@ -91,11 +94,11 @@ static int omnia_cmd_read_raw(struct i2c + static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) + { + u8 reply; +- int ret; ++ int err; + +- ret = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); +- if (ret < 0) +- return ret; ++ err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); ++ if (err) ++ return err; + + return reply; + } +@@ -236,7 +239,7 @@ static void omnia_hwtrig_deactivate(stru + + mutex_unlock(&leds->lock); + +- if (err < 0) ++ if (err) + dev_err(cdev->dev, "Cannot put LED to software mode: %i\n", + err); + } +@@ -302,7 +305,7 @@ static int omnia_led_register(struct i2c + ret = omnia_cmd_write_u8(client, CMD_LED_MODE, + CMD_LED_MODE_LED(led->reg) | + CMD_LED_MODE_USER); +- if (ret < 0) { ++ if (ret) { + dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, + ret); + return ret; +@@ -311,7 +314,7 @@ static int omnia_led_register(struct i2c + /* disable the LED */ + ret = omnia_cmd_write_u8(client, CMD_LED_STATE, + CMD_LED_STATE_LED(led->reg)); +- if (ret < 0) { ++ if (ret) { + dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); + return ret; + } +@@ -364,7 +367,7 @@ static ssize_t brightness_store(struct d + { + struct i2c_client *client = to_i2c_client(dev); + unsigned long brightness; +- int ret; ++ int err; + + if (kstrtoul(buf, 10, &brightness)) + return -EINVAL; +@@ -372,9 +375,9 @@ static ssize_t brightness_store(struct d + if (brightness > 100) + return -EINVAL; + +- ret = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness); ++ err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness); + +- return ret < 0 ? ret : count; ++ return err ?: count; + } + static DEVICE_ATTR_RW(brightness); + +@@ -403,7 +406,7 @@ static ssize_t gamma_correction_store(st + struct i2c_client *client = to_i2c_client(dev); + struct omnia_leds *leds = i2c_get_clientdata(client); + bool val; +- int ret; ++ int err; + + if (!leds->has_gamma_correction) + return -EOPNOTSUPP; +@@ -411,9 +414,9 @@ static ssize_t gamma_correction_store(st + if (kstrtobool(buf, &val) < 0) + return -EINVAL; + +- ret = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); ++ err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); + +- return ret < 0 ? ret : count; ++ return err ?: count; + } + static DEVICE_ATTR_RW(gamma_correction); + +@@ -431,7 +434,7 @@ static int omnia_mcu_get_features(const + + err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, + CMD_GET_STATUS_WORD, &reply, sizeof(reply)); +- if (err < 0) ++ if (err) + return err; + + /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ +@@ -440,7 +443,7 @@ static int omnia_mcu_get_features(const + + err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, + CMD_GET_FEATURES, &reply, sizeof(reply)); +- if (err < 0) ++ if (err) + return err; + + return le16_to_cpu(reply); diff --git a/target/linux/mvebu/patches-6.1/102-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch b/target/linux/mvebu/patches-6.1/102-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch deleted file mode 100644 index c14469460a..0000000000 --- a/target/linux/mvebu/patches-6.1/102-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 80e643510cb14f116f687e992210c0008a09d869 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Marek=20Beh=C3=BAn?= -Date: Mon, 4 Jul 2022 12:59:53 +0200 -Subject: [PATCH] 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". - -When in MCU controlled mode, the user can still set LED color, but the -blinking is done by MCU, which does different things for various 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 corresponding - port of the LAN switch -- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port - LED pins - -For a long time I wanted to actually do this differently: I wanted to -make the netdev trigger to transparently offload the blinking to the HW -if user set compatible settings for the netdev trigger. -There was some work on this, and hopefully we will be able to complete -it sometime, but since there are various complications, it will probably -not be soon. - -In the meantime let's support HW controlled mode via this private LED -trigger. If, in the future, we manage to complete the netdev trigger -offloading, we can still keep this private trigger for backwards -compatiblity, 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 ---- - drivers/leds/Kconfig | 1 + - drivers/leds/leds-turris-omnia.c | 41 ++++++++++++++++++++++++++++++++ - 2 files changed, 42 insertions(+) - ---- 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 -@@ -41,6 +41,39 @@ struct omnia_leds { - struct omnia_led leds[]; - }; - -+static struct led_hw_trigger_type omnia_hw_trigger_type; -+ -+static int omnia_hwtrig_activate(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)); -+ -+ /* put the LED into MCU controlled mode */ -+ return i2c_smbus_write_byte_data(leds->client, CMD_LED_MODE, -+ CMD_LED_MODE_LED(led->reg)); -+} -+ -+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 ret; -+ -+ /* put the LED into software mode */ -+ ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_MODE, -+ CMD_LED_MODE_LED(led->reg) | -+ CMD_LED_MODE_USER); -+ if (ret < 0) -+ dev_err(cdev->dev, "Cannot put to software mode: %i\n", ret); -+} -+ -+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_brightness_set_blocking(struct led_classdev *cdev, - enum led_brightness brightness) - { -@@ -112,6 +145,8 @@ 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; -+ cdev->default_trigger = omnia_hw_trigger.name; - - /* put the LED into software mode */ - ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE, -@@ -228,6 +263,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); diff --git a/target/linux/mvebu/patches-6.1/103-leds-turris-omnia-initialize-multi-intensity-to-full.patch b/target/linux/mvebu/patches-6.1/103-leds-turris-omnia-initialize-multi-intensity-to-full.patch deleted file mode 100644 index 1fe76b8f2c..0000000000 --- a/target/linux/mvebu/patches-6.1/103-leds-turris-omnia-initialize-multi-intensity-to-full.patch +++ /dev/null @@ -1,33 +0,0 @@ -From bda176cceb735b9b46c1900658b6486c34e13ae6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Marek=20Beh=C3=BAn?= -Date: Mon, 4 Jul 2022 12:59:54 +0200 -Subject: [PATCH] leds: turris-omnia: initialize multi-intensity to full -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The default color of each LED before driver probe (255, 255, 255). -Initialize multi_intensity to this value, so that it corresponds to the -reality. - -Signed-off-by: Marek Behún ---- - drivers/leds/leds-turris-omnia.c | 3 +++ - 1 file changed, 3 insertions(+) - ---- a/drivers/leds/leds-turris-omnia.c -+++ b/drivers/leds/leds-turris-omnia.c -@@ -131,10 +131,13 @@ static int omnia_led_register(struct i2c - } - - led->subled_info[0].color_index = LED_COLOR_ID_RED; -+ led->subled_info[0].intensity = 255; - led->subled_info[0].channel = 0; - led->subled_info[1].color_index = LED_COLOR_ID_GREEN; -+ led->subled_info[1].intensity = 255; - led->subled_info[1].channel = 1; - led->subled_info[2].color_index = LED_COLOR_ID_BLUE; -+ led->subled_info[2].intensity = 255; - led->subled_info[2].channel = 2; - - led->mc_cdev.subled_info = led->subled_info; diff --git a/target/linux/mvebu/patches-6.1/104-leds-turris-omnia-change-max-brightness-from-255-to-.patch b/target/linux/mvebu/patches-6.1/104-leds-turris-omnia-change-max-brightness-from-255-to-.patch deleted file mode 100644 index fb8864dfd1..0000000000 --- a/target/linux/mvebu/patches-6.1/104-leds-turris-omnia-change-max-brightness-from-255-to-.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 349cbe949b9622cc05b148ecfa6268cbbae35b45 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Marek=20Beh=C3=BAn?= -Date: Mon, 4 Jul 2022 12:59:55 +0200 -Subject: [PATCH] leds: turris-omnia: change max brightness from 255 to 1 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Using binary brightness makes more sense for this controller, because -internally in the MCU it works that way: the LED has a color, and a -state whether it is ON or OFF. - -The resulting brightness computation with led_mc_calc_color_components() -will now always result in either (0, 0, 0) or the multi_intensity value. - -Signed-off-by: Marek Behún ---- - drivers/leds/leds-turris-omnia.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - ---- a/drivers/leds/leds-turris-omnia.c -+++ b/drivers/leds/leds-turris-omnia.c -@@ -146,7 +146,7 @@ static int omnia_led_register(struct i2c - init_data.fwnode = &np->fwnode; - - cdev = &led->mc_cdev.led_cdev; -- cdev->max_brightness = 255; -+ cdev->max_brightness = 1; - cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; - cdev->trigger_type = &omnia_hw_trigger_type; - cdev->default_trigger = omnia_hw_trigger.name; -- 2.30.2