From 0d2e820a86793595e2a776855d04701109e46663 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Thu, 14 Nov 2024 11:13:59 +0100 Subject: leds: lp8860: Write full EEPROM, not only half of it I struggle to explain dividing an ARRAY_SIZE() by the size of an element once again. As the latter equals to 2, only the half of EEPROM was ever written. Drop the unexplainable division and write full ARRAY_SIZE(). Cc: stable@vger.kernel.org Fixes: 7a8685accb95 ("leds: lp8860: Introduce TI lp8860 4 channel LED driver") Signed-off-by: Alexander Sverdlin Link: https://lore.kernel.org/r/20241114101402.2562878-1-alexander.sverdlin@siemens.com Signed-off-by: Lee Jones --- drivers/leds/leds-lp8860.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 7a136fd81720..06196d851ade 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -265,7 +265,7 @@ static int lp8860_init(struct lp8860_led *led) goto out; } - reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs) / sizeof(lp8860_eeprom_disp_regs[0]); + reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs); for (i = 0; i < reg_count; i++) { ret = regmap_write(led->eeprom_regmap, lp8860_eeprom_disp_regs[i].reg, -- cgit From 609bc99a4452ffbce82d10f024a85d911c42e6cd Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 14 Nov 2024 13:44:29 +0100 Subject: dt-bindings: leds: class-multicolor: Fix path to color definitions The LED color definitions have always been in include/dt-bindings/leds/common.h in upstream. Fixes: 5c7f8ffe741daae7 ("dt: bindings: Add multicolor class dt bindings documention") Signed-off-by: Geert Uytterhoeven Acked-by: Conor Dooley Link: https://lore.kernel.org/r/a3c7ea92e90b77032f2e480d46418b087709286d.1731588129.git.geert+renesas@glider.be Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml b/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml index e850a8894758..bb40bb9e036e 100644 --- a/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml +++ b/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml @@ -27,7 +27,7 @@ properties: description: | For multicolor LED support this property should be defined as either LED_COLOR_ID_RGB or LED_COLOR_ID_MULTI which can be found in - include/linux/leds/common.h. + include/dt-bindings/leds/common.h. enum: [ 8, 9 ] required: -- cgit From 6fcafd33d2f85f0ffc1fc64e2b1ea607d0e75037 Mon Sep 17 00:00:00 2001 From: Zhu Jun Date: Thu, 21 Nov 2024 22:46:26 -0800 Subject: leds: ledtrig-activity: Fix the wrong format specifier The format specifier of "signed int" in sprintf() should be "%d", not "%u". Signed-off-by: Zhu Jun Link: https://lore.kernel.org/r/20241122064626.4680-1-zhujun2@cmss.chinamobile.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-activity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c index 33cbf8413658..b3ee33aed36e 100644 --- a/drivers/leds/trigger/ledtrig-activity.c +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -156,7 +156,7 @@ static ssize_t led_invert_show(struct device *dev, { struct activity_data *activity_data = led_trigger_get_drvdata(dev); - return sprintf(buf, "%u\n", activity_data->invert); + return sprintf(buf, "%d\n", activity_data->invert); } static ssize_t led_invert_store(struct device *dev, -- cgit From a1234b215538279c8520481f6d539f1582bd17d8 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Mon, 11 Nov 2024 21:40:52 -0500 Subject: dt-bindings: leds: qcom,spmi-flash-led: Add pm660l compatible Add the compatible for the flash LED controller on the PM660L PMIC. Signed-off-by: Richard Acayan Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20241112024050.669578-7-mailingradian@gmail.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml b/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml index 1ba607685f5f..bcf0ad4ea57e 100644 --- a/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml +++ b/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml @@ -23,6 +23,7 @@ properties: items: - enum: - qcom,pm6150l-flash-led + - qcom,pm660l-flash-led - qcom,pm8150c-flash-led - qcom,pm8150l-flash-led - qcom,pm8350c-flash-led -- cgit From 082e8f6db9092d19ae84549874daaef240c2207b Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:45 +0100 Subject: turris-omnia-mcu-interface.h: Move command execution function to global header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the command execution functions from the turris-omnia-mcu platform driver private header to the global turris-omnia-mcu-interface.h header, so that they can be used by the LED driver. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-2-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/platform/cznic/turris-omnia-mcu-base.c | 1 + drivers/platform/cznic/turris-omnia-mcu.h | 130 ----------------------- include/linux/turris-omnia-mcu-interface.h | 136 ++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 131 deletions(-) diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index 58f9afae2867..bb871226e357 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -52,6 +52,7 @@ int omnia_cmd_write_read(const struct i2c_client *client, return 0; } +EXPORT_SYMBOL_GPL(omnia_cmd_write_read); static int omnia_get_version_hash(struct omnia_mcu *mcu, bool bootloader, char version[static OMNIA_FW_VERSION_HEX_LEN]) diff --git a/drivers/platform/cznic/turris-omnia-mcu.h b/drivers/platform/cznic/turris-omnia-mcu.h index 2b13e28ee323..088541be3f4c 100644 --- a/drivers/platform/cznic/turris-omnia-mcu.h +++ b/drivers/platform/cznic/turris-omnia-mcu.h @@ -8,7 +8,6 @@ #ifndef __TURRIS_OMNIA_MCU_H #define __TURRIS_OMNIA_MCU_H -#include #include #include #include @@ -17,8 +16,6 @@ #include #include #include -#include -#include struct i2c_client; struct rtc_device; @@ -93,133 +90,6 @@ struct omnia_mcu { #endif }; -int omnia_cmd_write_read(const struct i2c_client *client, - void *cmd, unsigned int cmd_len, - void *reply, unsigned int reply_len); - -static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, - unsigned int len) -{ - return omnia_cmd_write_read(client, cmd, len, NULL, 0); -} - -static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, - u8 val) -{ - u8 buf[2] = { cmd, val }; - - return omnia_cmd_write(client, buf, sizeof(buf)); -} - -static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd, - u16 val) -{ - u8 buf[3]; - - buf[0] = cmd; - put_unaligned_le16(val, &buf[1]); - - return omnia_cmd_write(client, buf, sizeof(buf)); -} - -static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd, - u32 val) -{ - u8 buf[5]; - - buf[0] = cmd; - put_unaligned_le32(val, &buf[1]); - - return omnia_cmd_write(client, buf, sizeof(buf)); -} - -static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, - void *reply, unsigned int len) -{ - return omnia_cmd_write_read(client, &cmd, 1, reply, len); -} - -static inline unsigned int -omnia_compute_reply_length(unsigned long mask, bool interleaved, - unsigned int offset) -{ - if (!mask) - return 0; - - return ((__fls(mask) >> 3) << interleaved) + 1 + offset; -} - -/* Returns 0 on success */ -static inline int omnia_cmd_read_bits(const struct i2c_client *client, u8 cmd, - unsigned long bits, unsigned long *dst) -{ - __le32 reply; - int err; - - if (!bits) { - *dst = 0; - return 0; - } - - err = omnia_cmd_read(client, cmd, &reply, - omnia_compute_reply_length(bits, false, 0)); - if (err) - return err; - - *dst = le32_to_cpu(reply) & bits; - - return 0; -} - -static inline int omnia_cmd_read_bit(const struct i2c_client *client, u8 cmd, - unsigned long bit) -{ - unsigned long reply; - int err; - - err = omnia_cmd_read_bits(client, cmd, bit, &reply); - if (err) - return err; - - return !!reply; -} - -static inline int omnia_cmd_read_u32(const struct i2c_client *client, u8 cmd, - u32 *dst) -{ - __le32 reply; - int err; - - err = omnia_cmd_read(client, cmd, &reply, sizeof(reply)); - if (err) - return err; - - *dst = le32_to_cpu(reply); - - return 0; -} - -static inline int omnia_cmd_read_u16(const struct i2c_client *client, u8 cmd, - u16 *dst) -{ - __le16 reply; - int err; - - err = omnia_cmd_read(client, cmd, &reply, sizeof(reply)); - if (err) - return err; - - *dst = le16_to_cpu(reply); - - return 0; -} - -static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd, - u8 *reply) -{ - return omnia_cmd_read(client, cmd, reply, sizeof(*reply)); -} - #ifdef CONFIG_TURRIS_OMNIA_MCU_GPIO extern const u8 omnia_int_to_gpio_idx[32]; extern const struct attribute_group omnia_mcu_gpio_group; diff --git a/include/linux/turris-omnia-mcu-interface.h b/include/linux/turris-omnia-mcu-interface.h index 2da8cbeb158a..7f24cc682780 100644 --- a/include/linux/turris-omnia-mcu-interface.h +++ b/include/linux/turris-omnia-mcu-interface.h @@ -9,7 +9,10 @@ #define __TURRIS_OMNIA_MCU_INTERFACE_H #include -#include +#include +#include +#include +#include enum omnia_commands_e { OMNIA_CMD_GET_STATUS_WORD = 0x01, /* slave sends status word back */ @@ -246,4 +249,135 @@ enum omnia_cmd_usb_ovc_prot_e { OMNIA_CMD_xET_USB_OVC_PROT_ENABLE = BIT(4), }; +/* Command execution functions */ + +struct i2c_client; + +int omnia_cmd_write_read(const struct i2c_client *client, + void *cmd, unsigned int cmd_len, + void *reply, unsigned int reply_len); + +static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, + unsigned int len) +{ + return omnia_cmd_write_read(client, cmd, len, NULL, 0); +} + +static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, + u8 val) +{ + u8 buf[2] = { cmd, val }; + + return omnia_cmd_write(client, buf, sizeof(buf)); +} + +static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd, + u16 val) +{ + u8 buf[3]; + + buf[0] = cmd; + put_unaligned_le16(val, &buf[1]); + + return omnia_cmd_write(client, buf, sizeof(buf)); +} + +static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd, + u32 val) +{ + u8 buf[5]; + + buf[0] = cmd; + put_unaligned_le32(val, &buf[1]); + + return omnia_cmd_write(client, buf, sizeof(buf)); +} + +static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, + void *reply, unsigned int len) +{ + return omnia_cmd_write_read(client, &cmd, 1, reply, len); +} + +static inline unsigned int +omnia_compute_reply_length(unsigned long mask, bool interleaved, + unsigned int offset) +{ + if (!mask) + return 0; + + return ((__fls(mask) >> 3) << interleaved) + 1 + offset; +} + +/* Returns 0 on success */ +static inline int omnia_cmd_read_bits(const struct i2c_client *client, u8 cmd, + unsigned long bits, unsigned long *dst) +{ + __le32 reply; + int err; + + if (!bits) { + *dst = 0; + return 0; + } + + err = omnia_cmd_read(client, cmd, &reply, + omnia_compute_reply_length(bits, false, 0)); + if (err) + return err; + + *dst = le32_to_cpu(reply) & bits; + + return 0; +} + +static inline int omnia_cmd_read_bit(const struct i2c_client *client, u8 cmd, + unsigned long bit) +{ + unsigned long reply; + int err; + + err = omnia_cmd_read_bits(client, cmd, bit, &reply); + if (err) + return err; + + return !!reply; +} + +static inline int omnia_cmd_read_u32(const struct i2c_client *client, u8 cmd, + u32 *dst) +{ + __le32 reply; + int err; + + err = omnia_cmd_read(client, cmd, &reply, sizeof(reply)); + if (err) + return err; + + *dst = le32_to_cpu(reply); + + return 0; +} + +static inline int omnia_cmd_read_u16(const struct i2c_client *client, u8 cmd, + u16 *dst) +{ + __le16 reply; + int err; + + err = omnia_cmd_read(client, cmd, &reply, sizeof(reply)); + if (err) + return err; + + *dst = le16_to_cpu(reply); + + return 0; +} + +static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd, + u8 *reply) +{ + return omnia_cmd_read(client, cmd, reply, sizeof(*reply)); +} + #endif /* __TURRIS_OMNIA_MCU_INTERFACE_H */ -- cgit From b4c3960da27dd91412e04bc263311c16e6c46fb0 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:46 +0100 Subject: leds: turris-omnia: Use command execution functions from the MCU driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the MCU command execution functions from the MCU driver instead of the ad-hoc implementation in the LED driver. This allows as to drop the LED driver implementation, which is a duplicate. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-3-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 1 + drivers/leds/leds-turris-omnia.c | 137 +++++++++++++++------------------------ 2 files changed, 55 insertions(+), 83 deletions(-) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b784bb74a837..fcbe93b61e49 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -217,6 +217,7 @@ config LEDS_TURRIS_OMNIA depends on I2C depends on MACH_ARMADA_38X || COMPILE_TEST depends on OF + depends on TURRIS_OMNIA_MCU select LEDS_TRIGGERS help This option enables basic support for the LEDs found on the front diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 2de825ac08b3..2c36e9ed6835 100644 --- 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, 2023 by Marek Behún + * 2020, 2023, 2024 by Marek Behún */ #include @@ -10,6 +10,7 @@ #include #include #include +#include #define OMNIA_BOARD_LEDS 12 #define OMNIA_LED_NUM_CHANNELS 3 @@ -56,66 +57,21 @@ struct omnia_leds { struct omnia_led leds[]; }; -static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val) +static int omnia_cmd_set_color(const struct i2c_client *client, u8 led, u8 r, u8 g, u8 b) { - u8 buf[2] = { cmd, val }; - int ret; - - ret = i2c_master_send(client, buf, sizeof(buf)); + u8 buf[5] = { CMD_LED_COLOR, led, r, g, b }; - return ret < 0 ? ret : 0; -} - -static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, - void *reply, size_t len) -{ - struct i2c_msg msgs[2]; - int ret; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = 1; - msgs[0].buf = &cmd; - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = len; - msgs[1].buf = reply; - - ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); - if (likely(ret == ARRAY_SIZE(msgs))) - return 0; - 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 err; - - err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); - if (err) - return err; - - return reply; + return omnia_cmd_write(client, buf, sizeof(buf)); } 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); + ret = omnia_cmd_set_color(client, led->reg, led->subled_info[0].brightness, + led->subled_info[1].brightness, led->subled_info[2].brightness); if (ret < 0) return ret; @@ -351,14 +307,14 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf) { struct i2c_client *client = to_i2c_client(dev); - int ret; - - ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS); + u8 reply; + int err; - if (ret < 0) - return ret; + err = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS, &reply); + if (err < 0) + return err; - return sysfs_emit(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", reply); } static ssize_t brightness_store(struct device *dev, struct device_attribute *a, @@ -385,17 +341,16 @@ static ssize_t gamma_correction_show(struct device *dev, { struct i2c_client *client = to_i2c_client(dev); struct omnia_leds *leds = i2c_get_clientdata(client); - int ret; + u8 reply = 0; + int err; if (leds->has_gamma_correction) { - ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION); - if (ret < 0) - return ret; - } else { - ret = 0; + err = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION, &reply); + if (err < 0) + return err; } - return sysfs_emit(buf, "%d\n", !!ret); + return sysfs_emit(buf, "%d\n", !!reply); } static ssize_t gamma_correction_store(struct device *dev, @@ -426,26 +381,51 @@ static struct attribute *omnia_led_controller_attrs[] = { }; ATTRIBUTE_GROUPS(omnia_led_controller); -static int omnia_mcu_get_features(const struct i2c_client *client) +static int omnia_mcu_get_features(const struct i2c_client *mcu_client) { u16 reply; int err; - err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, - CMD_GET_STATUS_WORD, &reply, sizeof(reply)); + err = omnia_cmd_read_u16(mcu_client, CMD_GET_STATUS_WORD, &reply); if (err) return err; /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ - if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED)) + if (!(reply & STS_FEATURES_SUPPORTED)) return 0; - err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, - CMD_GET_FEATURES, &reply, sizeof(reply)); + err = omnia_cmd_read_u16(mcu_client, CMD_GET_FEATURES, &reply); if (err) return err; - return le16_to_cpu(reply); + return reply; +} + +static int omnia_match_mcu_client(struct device *dev, void *data) +{ + struct i2c_client *client; + + client = i2c_verify_client(dev); + if (!client) + return 0; + + return client->addr == OMNIA_MCU_I2C_ADDR; +} + +static int omnia_find_mcu_and_get_features(struct device *dev) +{ + struct device *mcu_dev; + int ret; + + mcu_dev = device_find_child(dev->parent, NULL, omnia_match_mcu_client); + if (!mcu_dev) + return -ENODEV; + + ret = omnia_mcu_get_features(i2c_verify_client(mcu_dev)); + + put_device(mcu_dev); + + return ret; } static int omnia_leds_probe(struct i2c_client *client) @@ -472,7 +452,7 @@ static int omnia_leds_probe(struct i2c_client *client) leds->client = client; i2c_set_clientdata(client, leds); - ret = omnia_mcu_get_features(client); + ret = omnia_find_mcu_and_get_features(dev); if (ret < 0) { dev_err(dev, "Cannot determine MCU supported features: %d\n", ret); @@ -509,20 +489,11 @@ static int omnia_leds_probe(struct i2c_client *client) static void omnia_leds_remove(struct i2c_client *client) { - u8 buf[5]; - /* put all LEDs into default (HW triggered) mode */ - omnia_cmd_write_u8(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; - buf[1] = OMNIA_BOARD_LEDS; - buf[2] = 255; - buf[3] = 255; - buf[4] = 255; - - i2c_master_send(client, buf, 5); + omnia_cmd_set_color(client, OMNIA_BOARD_LEDS, 255, 255, 255); } static const struct of_device_id of_omnia_leds_match[] = { -- cgit From d665d7f2800fff5da9311e4c8c236966ba57d440 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:47 +0100 Subject: turris-omnia-mcu-interface.h: Add LED commands related definitions to global header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add definitions for contents of the OMNIA_CMD_LED_MODE and OMNIA_CMD_LED_STATE commands to the global turris-omnia-mcu-interface.h header. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-4-kabel@kernel.org Signed-off-by: Lee Jones --- include/linux/turris-omnia-mcu-interface.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/linux/turris-omnia-mcu-interface.h b/include/linux/turris-omnia-mcu-interface.h index 7f24cc682780..06c94e032c6f 100644 --- a/include/linux/turris-omnia-mcu-interface.h +++ b/include/linux/turris-omnia-mcu-interface.h @@ -239,6 +239,18 @@ enum omnia_int_e { OMNIA_INT_LAN5_LED1 = BIT(31), }; +enum omnia_cmd_led_mode_e { + OMNIA_CMD_LED_MODE_LED_MASK = GENMASK(3, 0), +#define OMNIA_CMD_LED_MODE_LED(_l) FIELD_PREP(OMNIA_CMD_LED_MODE_LED_MASK, _l) + OMNIA_CMD_LED_MODE_USER = BIT(4), +}; + +enum omnia_cmd_led_state_e { + OMNIA_CMD_LED_STATE_LED_MASK = GENMASK(3, 0), +#define OMNIA_CMD_LED_STATE_LED(_l) FIELD_PREP(OMNIA_CMD_LED_STATE_LED_MASK, _l) + OMNIA_CMD_LED_STATE_ON = BIT(4), +}; + enum omnia_cmd_poweroff_e { OMNIA_CMD_POWER_OFF_POWERON_BUTTON = BIT(0), OMNIA_CMD_POWER_OFF_MAGIC = 0xdead, -- cgit From 5d2f88320c2f7d15830f2f3b51726ef6ba999195 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:48 +0100 Subject: leds: turris-omnia: Use global header for MCU command definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global turris-omnia-mcu-interface.h header file contains the definitions for MCU commands. Drop the driver-internal definitions and use the global ones. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-5-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-turris-omnia.c | 71 +++++++++++++--------------------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 2c36e9ed6835..1212df8315ab 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -15,30 +15,8 @@ #define OMNIA_BOARD_LEDS 12 #define OMNIA_LED_NUM_CHANNELS 3 -/* 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 +/* MCU controller I2C address 0x2a, needed for detecting MCU features */ +#define OMNIA_MCU_I2C_ADDR 0x2a struct omnia_led { struct led_classdev_mc mc_cdev; @@ -59,7 +37,7 @@ struct omnia_leds { static int omnia_cmd_set_color(const struct i2c_client *client, u8 led, u8 r, u8 g, u8 b) { - u8 buf[5] = { CMD_LED_COLOR, led, r, g, b }; + u8 buf[5] = { OMNIA_CMD_LED_COLOR, led, r, g, b }; return omnia_cmd_write(client, buf, sizeof(buf)); } @@ -126,12 +104,12 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, * is not being blinked by HW. */ if (!err && !led->hwtrig && !brightness != !led->on) { - u8 state = CMD_LED_STATE_LED(led->reg); + u8 state = OMNIA_CMD_LED_STATE_LED(led->reg); if (brightness) - state |= CMD_LED_STATE_ON; + state |= OMNIA_CMD_LED_STATE_ON; - err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_STATE, state); if (!err) led->on = !!brightness; } @@ -166,8 +144,8 @@ static int omnia_hwtrig_activate(struct led_classdev *cdev) 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)); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE, + OMNIA_CMD_LED_MODE_LED(led->reg)); if (!err) led->hwtrig = true; } @@ -188,9 +166,8 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev) 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); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE, + OMNIA_CMD_LED_MODE_LED(led->reg) | OMNIA_CMD_LED_MODE_USER); mutex_unlock(&leds->lock); @@ -257,9 +234,8 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, cdev->default_trigger = omnia_hw_trigger.name; /* put the LED into software mode */ - ret = omnia_cmd_write_u8(client, CMD_LED_MODE, - CMD_LED_MODE_LED(led->reg) | - CMD_LED_MODE_USER); + ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) | + OMNIA_CMD_LED_MODE_USER); if (ret) { dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, ret); @@ -267,8 +243,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, } /* disable the LED */ - ret = omnia_cmd_write_u8(client, CMD_LED_STATE, - CMD_LED_STATE_LED(led->reg)); + ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_STATE, OMNIA_CMD_LED_STATE_LED(led->reg)); if (ret) { dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); return ret; @@ -310,7 +285,7 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, u8 reply; int err; - err = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS, &reply); + err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_BRIGHTNESS, &reply); if (err < 0) return err; @@ -330,7 +305,7 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, if (brightness > 100) return -EINVAL; - err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness); + err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_BRIGHTNESS, brightness); return err ?: count; } @@ -345,7 +320,7 @@ static ssize_t gamma_correction_show(struct device *dev, int err; if (leds->has_gamma_correction) { - err = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION, &reply); + err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_GAMMA_CORRECTION, &reply); if (err < 0) return err; } @@ -368,7 +343,7 @@ static ssize_t gamma_correction_store(struct device *dev, if (kstrtobool(buf, &val) < 0) return -EINVAL; - err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); + err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_GAMMA_CORRECTION, val); return err ?: count; } @@ -386,15 +361,15 @@ static int omnia_mcu_get_features(const struct i2c_client *mcu_client) u16 reply; int err; - err = omnia_cmd_read_u16(mcu_client, CMD_GET_STATUS_WORD, &reply); + err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_STATUS_WORD, &reply); if (err) return err; - /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ - if (!(reply & STS_FEATURES_SUPPORTED)) + /* Check whether MCU firmware supports the OMNIA_CMD_GET_FEAUTRES command */ + if (!(reply & OMNIA_STS_FEATURES_SUPPORTED)) return 0; - err = omnia_cmd_read_u16(mcu_client, CMD_GET_FEATURES, &reply); + err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_FEATURES, &reply); if (err) return err; @@ -459,7 +434,7 @@ static int omnia_leds_probe(struct i2c_client *client) return ret; } - leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION; + leds->has_gamma_correction = ret & OMNIA_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"); @@ -490,7 +465,7 @@ static int omnia_leds_probe(struct i2c_client *client) static void omnia_leds_remove(struct i2c_client *client) { /* put all LEDs into default (HW triggered) mode */ - omnia_cmd_write_u8(client, CMD_LED_MODE, CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); + omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); /* set all LEDs color to [255, 255, 255] */ omnia_cmd_set_color(client, OMNIA_BOARD_LEDS, 255, 255, 255); -- cgit From 2b73a24f327070390f1a17570fcab35b61a92c24 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:49 +0100 Subject: dt-bindings: leds: cznic,turris-omnia-leds: Allow interrupts property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the cznic,turris-omnia-leds binding with interrupts property, specifying the global LED brightness changed by button press interrupt. Signed-off-by: Marek Behún Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20241111100355.6978-6-kabel@kernel.org Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/cznic,turris-omnia-leds.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/leds/cznic,turris-omnia-leds.yaml b/Documentation/devicetree/bindings/leds/cznic,turris-omnia-leds.yaml index 34ef5215c150..f52f6304c79e 100644 --- a/Documentation/devicetree/bindings/leds/cznic,turris-omnia-leds.yaml +++ b/Documentation/devicetree/bindings/leds/cznic,turris-omnia-leds.yaml @@ -23,6 +23,12 @@ properties: description: I2C slave address of the microcontroller. maxItems: 1 + interrupts: + description: + Specifier for the global LED brightness changed by front button press + interrupt. + maxItems: 1 + "#address-cells": const: 1 @@ -56,6 +62,7 @@ additionalProperties: false examples: - | + #include #include i2c { @@ -65,6 +72,7 @@ examples: led-controller@2b { compatible = "cznic,turris-omnia-leds"; reg = <0x2b>; + interrupts-extended = <&mcu 11 IRQ_TYPE_NONE>; #address-cells = <1>; #size-cells = <0>; -- cgit From 8ca5bf8ad183e1b05034ccfd96d59e7b802d6335 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:50 +0100 Subject: leds: turris-omnia: Document driver private structures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add documentation for driver private structures, `struct omnia_leds` and `struct omnia_led`. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-7-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-turris-omnia.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 1212df8315ab..83a011e5259c 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -18,6 +18,15 @@ /* MCU controller I2C address 0x2a, needed for detecting MCU features */ #define OMNIA_MCU_I2C_ADDR 0x2a +/** + * struct omnia_led - per-LED part of driver private data structure + * @mc_cdev: multi-color LED class device + * @subled_info: per-channel information + * @cached_channels: cached values of per-channel brightness that was sent to the MCU + * @on: whether the LED was set on + * @hwtrig: whether the LED blinking was offloaded to the MCU + * @reg: LED identifier to the MCU + */ struct omnia_led { struct led_classdev_mc mc_cdev; struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; @@ -28,6 +37,13 @@ struct omnia_led { #define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev) +/** + * struct omnia_leds - driver private data structure + * @client: I2C client device + * @lock: mutex to protect cached state + * @has_gamma_correction: whether the MCU firmware supports gamma correction + * @leds: flexible array of per-LED data + */ struct omnia_leds { struct i2c_client *client; struct mutex lock; -- cgit From d82e09d62b3bdbfa9dac2daf3c3c071b6a79d2aa Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:51 +0100 Subject: leds: turris-omnia: Notify sysfs on MCU global LEDs brightness change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recall that on Turris Omnia, the LED controller has a global brightness property, which allows the user to make the front LED panel dimmer. There is also a button on the front panel, which by default is configured so that pressing it changes the global brightness to a lower value (unless it is at 0%, in which case pressing the button changes the global brightness to 100%). Newer versions of the MCU firmware support informing the SOC that the brightness was changed by button press event via an interrupt. Now that we have the turris-omnia-mcu driver, which adds support for MCU interrupts, add the ability to inform the userspace (via a sysfs notification) that the global brightness was changed. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-8-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 1 + drivers/leds/leds-turris-omnia.c | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index fcbe93b61e49..9ab6b56898c6 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -218,6 +218,7 @@ config LEDS_TURRIS_OMNIA depends on MACH_ARMADA_38X || COMPILE_TEST depends on OF depends on TURRIS_OMNIA_MCU + depends on TURRIS_OMNIA_MCU_GPIO select LEDS_TRIGGERS help This option enables basic support for the LEDs found on the front diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 83a011e5259c..4f7b529781ce 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -42,12 +42,15 @@ struct omnia_led { * @client: I2C client device * @lock: mutex to protect cached state * @has_gamma_correction: whether the MCU firmware supports gamma correction + * @brightness_knode: kernel node of the "brightness" device sysfs attribute (this is the + * driver specific global brightness, not the LED classdev brightness) * @leds: flexible array of per-LED data */ struct omnia_leds { struct i2c_client *client; struct mutex lock; bool has_gamma_correction; + struct kernfs_node *brightness_knode; struct omnia_led leds[]; }; @@ -372,6 +375,59 @@ static struct attribute *omnia_led_controller_attrs[] = { }; ATTRIBUTE_GROUPS(omnia_led_controller); +static irqreturn_t omnia_brightness_changed_threaded_fn(int irq, void *data) +{ + struct omnia_leds *leds = data; + + if (unlikely(!leds->brightness_knode)) { + /* + * Note that sysfs_get_dirent() may sleep. This is okay, because we are in threaded + * context. + */ + leds->brightness_knode = sysfs_get_dirent(leds->client->dev.kobj.sd, "brightness"); + if (!leds->brightness_knode) + return IRQ_NONE; + } + + sysfs_notify_dirent(leds->brightness_knode); + + return IRQ_HANDLED; +} + +static void omnia_brightness_knode_put(void *data) +{ + struct omnia_leds *leds = data; + + if (leds->brightness_knode) + sysfs_put(leds->brightness_knode); +} + +static int omnia_request_brightness_irq(struct omnia_leds *leds) +{ + struct device *dev = &leds->client->dev; + int ret; + + if (!leds->client->irq) { + dev_info(dev, + "Brightness change interrupt supported by MCU firmware but not described in device-tree\n"); + + return 0; + } + + /* + * Registering the brightness_knode destructor before requesting the IRQ ensures that on + * removal the brightness_knode sysfs node is put only after the IRQ is freed. + * This is needed because the interrupt handler uses the knode. + */ + ret = devm_add_action(dev, omnia_brightness_knode_put, leds); + if (ret < 0) + return ret; + + return devm_request_threaded_irq(dev, leds->client->irq, NULL, + omnia_brightness_changed_threaded_fn, IRQF_ONESHOT, + "leds-turris-omnia", leds); +} + static int omnia_mcu_get_features(const struct i2c_client *mcu_client) { u16 reply; @@ -458,6 +514,12 @@ static int omnia_leds_probe(struct i2c_client *client) "Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); } + if (ret & OMNIA_FEAT_BRIGHTNESS_INT) { + ret = omnia_request_brightness_irq(leds); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot request brightness IRQ\n"); + } + mutex_init(&leds->lock); ret = devm_led_trigger_register(dev, &omnia_hw_trigger); -- cgit From 1783b766940f37aeaf7c841f777be8cf68326908 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:52 +0100 Subject: platform: cznic: turris-omnia-mcu: Inform about missing LED panel brightness change interrupt feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When reading MCU firmware features, check also for the LED panel brightness change interrupt feature, and suggest upgrading the firmware if it is missing. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-9-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/platform/cznic/turris-omnia-mcu-base.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index bb871226e357..3bb4a3cfdb29 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -258,6 +258,7 @@ static int omnia_mcu_read_features(struct omnia_mcu *mcu) _DEF_FEAT(NEW_INT_API, "new interrupt API"), _DEF_FEAT(POWEROFF_WAKEUP, "poweroff and wakeup"), _DEF_FEAT(TRNG, "true random number generator"), + _DEF_FEAT(BRIGHTNESS_INT, "LED panel brightness change interrupt"), #undef _DEF_FEAT }; struct i2c_client *client = mcu->client; -- cgit From 2de889e9cf25558bf12505b6827dcd435cbc1b11 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:53 +0100 Subject: leds: turris-omnia: Inform about missing LED gamma correction feature in the MCU driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the LED gamma correction feature is missing in the MCU firmware, inform about this in the MCU firmware probe function instead of LED driver probe function, so that all the feature checks are in one place. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-10-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-turris-omnia.c | 6 ------ drivers/platform/cznic/turris-omnia-mcu-base.c | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 4f7b529781ce..cfbcbdd7921b 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -507,12 +507,6 @@ static int omnia_leds_probe(struct i2c_client *client) } leds->has_gamma_correction = ret & OMNIA_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"); - } if (ret & OMNIA_FEAT_BRIGHTNESS_INT) { ret = omnia_request_brightness_irq(leds); diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index 3bb4a3cfdb29..770e680b96f9 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -259,6 +259,7 @@ static int omnia_mcu_read_features(struct omnia_mcu *mcu) _DEF_FEAT(POWEROFF_WAKEUP, "poweroff and wakeup"), _DEF_FEAT(TRNG, "true random number generator"), _DEF_FEAT(BRIGHTNESS_INT, "LED panel brightness change interrupt"), + _DEF_FEAT(LED_GAMMA_CORRECTION, "LED gamma correction"), #undef _DEF_FEAT }; struct i2c_client *client = mcu->client; -- cgit From eda057c7c7c2c99c15b86bb5e3c1858b8650c074 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:54 +0100 Subject: leds: turris-omnia: Use dev_err_probe() where appropriate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use dev_err_probe() instead of dev_err() + separate return where appropriate. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-11-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-turris-omnia.c | 50 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index cfbcbdd7921b..bb40cc3c874a 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -255,33 +255,23 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, /* put the LED into software mode */ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) | OMNIA_CMD_LED_MODE_USER); - if (ret) { - dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, - ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Cannot set LED %pOF to software mode\n", np); /* disable the LED */ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_STATE, OMNIA_CMD_LED_STATE_LED(led->reg)); - if (ret) { - dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Cannot set LED %pOF brightness\n", np); /* 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; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot set LED %pOF initial color\n", np); ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); - if (ret < 0) { - dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot register LED %pOF\n", np); return 1; } @@ -484,13 +474,10 @@ static int omnia_leds_probe(struct i2c_client *client) int ret, count; count = of_get_available_child_count(np); - if (!count) { - dev_err(dev, "LEDs are not defined in device tree!\n"); - return -ENODEV; - } else if (count > OMNIA_BOARD_LEDS) { - dev_err(dev, "Too many LEDs defined in device tree!\n"); - return -EINVAL; - } + if (count == 0) + return dev_err_probe(dev, -ENODEV, "LEDs are not defined in device tree!\n"); + if (count > OMNIA_BOARD_LEDS) + return dev_err_probe(dev, -EINVAL, "Too many LEDs defined in device tree!\n"); leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL); if (!leds) @@ -500,11 +487,8 @@ static int omnia_leds_probe(struct i2c_client *client) i2c_set_clientdata(client, leds); ret = omnia_find_mcu_and_get_features(dev); - if (ret < 0) { - dev_err(dev, "Cannot determine MCU supported features: %d\n", - ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot determine MCU supported features\n"); leds->has_gamma_correction = ret & OMNIA_FEAT_LED_GAMMA_CORRECTION; @@ -517,10 +501,8 @@ static int omnia_leds_probe(struct i2c_client *client) 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; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot register private LED trigger\n"); led = &leds->leds[0]; for_each_available_child_of_node_scoped(np, child) { -- cgit From 4cc40bf45b6f6b6ae350e1f75ee0af49b81f9b1f Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Mon, 11 Nov 2024 11:03:55 +0100 Subject: leds: turris-omnia: Use uppercase first letter in all comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change first letter of 4 more comments to uppercase to make the driver comments uniform. Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241111100355.6978-12-kabel@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-turris-omnia.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index bb40cc3c874a..7d3b24c8ecae 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -252,13 +252,13 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, */ cdev->default_trigger = omnia_hw_trigger.name; - /* put the LED into software mode */ + /* Put the LED into software mode */ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) | OMNIA_CMD_LED_MODE_USER); if (ret) return dev_err_probe(dev, ret, "Cannot set LED %pOF to software mode\n", np); - /* disable the LED */ + /* Disable the LED */ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_STATE, OMNIA_CMD_LED_STATE_LED(led->reg)); if (ret) return dev_err_probe(dev, ret, "Cannot set LED %pOF brightness\n", np); @@ -518,10 +518,10 @@ static int omnia_leds_probe(struct i2c_client *client) static void omnia_leds_remove(struct i2c_client *client) { - /* put all LEDs into default (HW triggered) mode */ + /* Put all LEDs into default (HW triggered) mode */ omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); - /* set all LEDs color to [255, 255, 255] */ + /* Set all LEDs color to [255, 255, 255] */ omnia_cmd_set_color(client, OMNIA_BOARD_LEDS, 255, 255, 255); } -- cgit From 0dfda50988c6805e8ab432e99866a021ea6ec46d Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 16 Dec 2024 11:48:22 +0100 Subject: leds: trigger: netdev: Check offload ability on interface up The trigger_data->hw_control indicates whether the LED is controlled by HW offload, i.e. the PHY. The trigger_data->hw_control = can_hw_control() is currently called only from netdev_led_attr_store(), i.e. when writing any sysfs attribute of the netdev trigger instance associated with a PHY LED. The can_hw_control() calls validate_net_dev() which internally calls led_cdev->hw_control_get_device(), which is phy_led_hw_control_get_device() for PHY LEDs. The phy_led_hw_control_get_device() returns NULL if the PHY is not attached. At least in case of DWMAC (STM32MP, iMX8M, ...), the PHY device is attached only when the interface is brought up and is detached again when the interface is brought down. In case e.g. udev rules configure the netdev LED trigger sysfs attributes before the interface is brought up, then when the interface is brought up, the LEDs are not blinking. This is because trigger_data->hw_control = can_hw_control() was called when udev wrote the sysfs attribute files, before the interface was up, so can_hw_control() resp. validate_net_dev() returned false, and the trigger_data->hw_control = can_hw_control() was never called again to update the trigger_data->hw_control content and let the offload take over the LED blinking. Call data->hw_control = can_hw_control() from netdev_trig_notify() to update the offload capability of the LED when the UP notification arrives. This makes the LEDs blink after the interface is brought up. On STM32MP13xx with RTL8211F, it is enough to have the following udev rule in place, boot the machine with cable plugged in, and the LEDs won't work without this patch once the interface is brought up, even if they should: " ACTION=="add", SUBSYSTEM=="leds", KERNEL=="stmmac-0:01:green:wan", ATTR{trigger}="netdev", ATTR{link_10}="1", ATTR{link_100}="1", ATTR{link_1000}="1", ATTR{device_name}="end0" ACTION=="add", SUBSYSTEM=="leds", KERNEL=="stmmac-0:01:yellow:wan", ATTR{trigger}="netdev", ATTR{rx}="1", ATTR{tx}="1", ATTR{device_name}="end0" " Signed-off-by: Marek Vasut Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/20241216104826.6946-1-marex@denx.de Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-netdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 4b0863db901a..c15efe3e5078 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -605,6 +605,8 @@ static int netdev_trig_notify(struct notifier_block *nb, trigger_data->net_dev = NULL; break; case NETDEV_UP: + trigger_data->hw_control = can_hw_control(trigger_data); + fallthrough; case NETDEV_CHANGE: get_device_state(trigger_data); /* Refresh link_speed visibility */ -- cgit From 0ef2929a018123154283b14dd11744ff32a2a12c Mon Sep 17 00:00:00 2001 From: Thomas Richard Date: Wed, 11 Dec 2024 17:27:17 +0100 Subject: leds: Add AAEON UP board LED driver Add support for LEDs on AAEON UP boards. These leds are managed by the onboard FPGA: - UP boards: yellow, green, red - UP Squared boards: blue, yellow, green, red Based on the work done by Gary Wang , largely rewritten. Signed-off-by: Thomas Richard Link: https://lore.kernel.org/r/20241211-aaeon-up-board-pinctrl-support-v1-2-24719be27631@bootlin.com Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 9 ++++ drivers/leds/Makefile | 1 + drivers/leds/leds-upboard.c | 126 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 drivers/leds/leds-upboard.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9ab6b56898c6..3bf51a4e01d7 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -817,6 +817,15 @@ config LEDS_SC27XX_BLTC This driver can also be built as a module. If so the module will be called leds-sc27xx-bltc. +config LEDS_UPBOARD + tristate "LED support for the UP board" + depends on LEDS_CLASS && MFD_UPBOARD_FPGA + help + This option enables support for the UP board LEDs. + + This driver can also be built as a module. If so the module will be + called leds-upboard. + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" config LEDS_BLINKM diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 18afbb5a23ee..e7982938ddc1 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o +obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o diff --git a/drivers/leds/leds-upboard.c b/drivers/leds/leds-upboard.c new file mode 100644 index 000000000000..b350eb294280 --- /dev/null +++ b/drivers/leds/leds-upboard.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UP board LED driver. + * + * Copyright (c) AAEON. All rights reserved. + * Copyright (C) 2024 Bootlin + * + * Author: Gary Wang + * Author: Thomas Richard + */ + +#include +#include +#include +#include +#include +#include +#include + +#define led_cdev_to_led_upboard(c) container_of(c, struct upboard_led, cdev) + +struct upboard_led { + struct regmap_field *field; + struct led_classdev cdev; +}; + +struct upboard_led_profile { + const char *name; + unsigned int bit; +}; + +static struct upboard_led_profile upboard_up_led_profile[] = { + { "upboard:yellow:" LED_FUNCTION_STATUS, 0 }, + { "upboard:green:" LED_FUNCTION_STATUS, 1 }, + { "upboard:red:" LED_FUNCTION_STATUS, 2 }, +}; + +static struct upboard_led_profile upboard_up2_led_profile[] = { + { "upboard:blue:" LED_FUNCTION_STATUS, 0 }, + { "upboard:yellow:" LED_FUNCTION_STATUS, 1 }, + { "upboard:green:" LED_FUNCTION_STATUS, 2 }, + { "upboard:red:" LED_FUNCTION_STATUS, 3 }, +}; + +static enum led_brightness upboard_led_brightness_get(struct led_classdev *cdev) +{ + struct upboard_led *led = led_cdev_to_led_upboard(cdev); + int brightness, ret; + + ret = regmap_field_read(led->field, &brightness); + + return ret ? LED_OFF : brightness; +}; + +static int upboard_led_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct upboard_led *led = led_cdev_to_led_upboard(cdev); + + return regmap_field_write(led->field, brightness != LED_OFF); +}; + +static int upboard_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct upboard_fpga *fpga = dev_get_drvdata(dev->parent); + struct upboard_led_profile *led_profile; + struct upboard_led *led; + int led_instances, ret, i; + + switch (fpga->fpga_data->type) { + case UPBOARD_UP_FPGA: + led_profile = upboard_up_led_profile; + led_instances = ARRAY_SIZE(upboard_up_led_profile); + break; + case UPBOARD_UP2_FPGA: + led_profile = upboard_up2_led_profile; + led_instances = ARRAY_SIZE(upboard_up2_led_profile); + break; + default: + return dev_err_probe(dev, -EINVAL, "Unknown device type %d\n", + fpga->fpga_data->type); + } + + for (i = 0; i < led_instances; i++) { + const struct reg_field fldconf = { + .reg = UPBOARD_REG_FUNC_EN0, + .lsb = led_profile[i].bit, + .msb = led_profile[i].bit, + }; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->field = devm_regmap_field_alloc(&pdev->dev, fpga->regmap, fldconf); + if (IS_ERR(led->field)) + return PTR_ERR(led->field); + + led->cdev.brightness_get = upboard_led_brightness_get; + led->cdev.brightness_set_blocking = upboard_led_brightness_set; + led->cdev.max_brightness = LED_ON; + + led->cdev.name = led_profile[i].name; + + ret = devm_led_classdev_register(dev, &led->cdev); + if (ret) + return ret; + } + + return 0; +} + +static struct platform_driver upboard_led_driver = { + .driver = { + .name = "upboard-leds", + }, + .probe = upboard_led_probe, +}; + +module_platform_driver(upboard_led_driver); + +MODULE_AUTHOR("Gary Wang "); +MODULE_AUTHOR("Thomas Richard "); +MODULE_DESCRIPTION("UP Board LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:upboard-led"); -- cgit From 3d6976047922374347fb77b509d755da153f549d Mon Sep 17 00:00:00 2001 From: Thomas Richard Date: Wed, 11 Dec 2024 17:27:20 +0100 Subject: MAINTAINERS: Add entry for AAEON UP board FPGA drivers Add the AAEON UP board FPGA drivers and header as Maintained by myself. Signed-off-by: Thomas Richard Link: https://lore.kernel.org/r/20241211-aaeon-up-board-pinctrl-support-v1-5-24719be27631@bootlin.com Signed-off-by: Lee Jones --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1e930c7a58b1..cbe4cfbe110d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -185,6 +185,14 @@ W: http://www.adaptec.com/ F: Documentation/scsi/aacraid.rst F: drivers/scsi/aacraid/ +AAEON UPBOARD FPGA MFD DRIVER +M: Thomas Richard +S: Maintained +F: drivers/leds/leds-upboard.c +F: drivers/mfd/upboard-fpga.c +F: drivers/pinctrl/pinctrl-upboard.c +F: include/linux/mfd/upboard-fpga.h + AB8500 BATTERY AND CHARGER DRIVERS M: Linus Walleij F: Documentation/devicetree/bindings/power/supply/*ab8500* -- cgit From 1c896113f04e34d0036ef506532d2e6cf77dd1e5 Mon Sep 17 00:00:00 2001 From: Marek Behún Date: Sun, 15 Dec 2024 22:13:23 +0100 Subject: turris-omnia-mcu-interface.h: Move macro definitions outside of enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the definitions of enumerator related macros outside of the enumerator definitions. Suggested-by: Lee Jones Link: https://lore.kernel.org/linux-leds/20241212183357.GK7139@google.com/ Signed-off-by: Marek Behún Link: https://lore.kernel.org/r/20241215211323.23364-1-kabel@kernel.org Signed-off-by: Lee Jones --- include/linux/turris-omnia-mcu-interface.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/linux/turris-omnia-mcu-interface.h b/include/linux/turris-omnia-mcu-interface.h index 06c94e032c6f..38b45ab00053 100644 --- a/include/linux/turris-omnia-mcu-interface.h +++ b/include/linux/turris-omnia-mcu-interface.h @@ -241,16 +241,18 @@ enum omnia_int_e { enum omnia_cmd_led_mode_e { OMNIA_CMD_LED_MODE_LED_MASK = GENMASK(3, 0), -#define OMNIA_CMD_LED_MODE_LED(_l) FIELD_PREP(OMNIA_CMD_LED_MODE_LED_MASK, _l) OMNIA_CMD_LED_MODE_USER = BIT(4), }; +#define OMNIA_CMD_LED_MODE_LED(_l) FIELD_PREP(OMNIA_CMD_LED_MODE_LED_MASK, _l) + enum omnia_cmd_led_state_e { OMNIA_CMD_LED_STATE_LED_MASK = GENMASK(3, 0), -#define OMNIA_CMD_LED_STATE_LED(_l) FIELD_PREP(OMNIA_CMD_LED_STATE_LED_MASK, _l) OMNIA_CMD_LED_STATE_ON = BIT(4), }; +#define OMNIA_CMD_LED_STATE_LED(_l) FIELD_PREP(OMNIA_CMD_LED_STATE_LED_MASK, _l) + enum omnia_cmd_poweroff_e { OMNIA_CMD_POWER_OFF_POWERON_BUTTON = BIT(0), OMNIA_CMD_POWER_OFF_MAGIC = 0xdead, -- cgit From 0508316be63bb735f59bdc8fe4527cadb62210ca Mon Sep 17 00:00:00 2001 From: Joe Hattori Date: Mon, 16 Dec 2024 16:49:23 +0900 Subject: leds: netxbig: Fix an OF node reference leak in netxbig_leds_get_of_pdata() netxbig_leds_get_of_pdata() does not release the OF node obtained by of_parse_phandle() when of_find_device_by_node() fails. Add an of_node_put() call to fix the leak. This bug was found by an experimental static analysis tool that I am developing. Fixes: 9af512e81964 ("leds: netxbig: Convert to use GPIO descriptors") Signed-off-by: Joe Hattori Link: https://lore.kernel.org/r/20241216074923.628509-1-joe@pf.is.s.u-tokyo.ac.jp Signed-off-by: Lee Jones --- drivers/leds/leds-netxbig.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index af5a908b8d9e..e95287416ef8 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -439,6 +439,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev, } gpio_ext_pdev = of_find_device_by_node(gpio_ext_np); if (!gpio_ext_pdev) { + of_node_put(gpio_ext_np); dev_err(dev, "Failed to find platform device for gpio-ext\n"); return -ENODEV; } -- cgit From 29df7025cff00dd9fa7cacbec979ede97ee775eb Mon Sep 17 00:00:00 2001 From: Jakob Riepler Date: Mon, 16 Dec 2024 22:37:55 +0100 Subject: leds: pwm-multicolor: Disable PWM when going to suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes suspend on platforms like stm32mp1xx, where the PWM consumer has to be disabled for the PWM to enter suspend. Another positive side effect is that active-low LEDs now properly turn off instead of going back to full brightness when they are set to 0. Link: https://lore.kernel.org/all/20240417153846.271751-2-u.kleine-koenig@pengutronix.de/ Signed-off-by: Jakob Riepler Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20241216213754.18374-2-jakob+lkml@paranoidlabs.org Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-pwm-multicolor.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index e1a81e0109e8..f80a06cc31f8 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -50,7 +50,13 @@ static int led_pwm_mc_set(struct led_classdev *cdev, duty = priv->leds[i].state.period - duty; priv->leds[i].state.duty_cycle = duty; - priv->leds[i].state.enabled = duty > 0; + /* + * Disabling a PWM doesn't guarantee that it emits the inactive level. + * So keep it on. Only for suspending the PWM should be disabled because + * otherwise it refuses to suspend. The possible downside is that the + * LED might stay (or even go) on. + */ + priv->leds[i].state.enabled = !(cdev->flags & LED_SUSPENDED); ret = pwm_apply_might_sleep(priv->leds[i].pwm, &priv->leds[i].state); if (ret) -- cgit From 132e6687a118cedda0f59ada4af5ce42d0d05043 Mon Sep 17 00:00:00 2001 From: Vicentiu Galanopulo Date: Wed, 18 Dec 2024 18:33:57 +0000 Subject: Documentation:leds: Add leds-st1202.rst Add usage for sysfs hw_pattern entry for leds-st1202 Signed-off-by: Vicentiu Galanopulo Link: https://lore.kernel.org/r/20241218183401.41687-2-vicentiu.galanopulo@remote-tech.co.uk Signed-off-by: Lee Jones --- Documentation/leds/index.rst | 1 + Documentation/leds/leds-st1202.rst | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 Documentation/leds/leds-st1202.rst diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst index 3ade16c18328..0ab0a2128a11 100644 --- a/Documentation/leds/index.rst +++ b/Documentation/leds/index.rst @@ -28,4 +28,5 @@ LEDs leds-mlxcpld leds-mt6370-rgb leds-sc27xx + leds-st1202.rst leds-qcom-lpg diff --git a/Documentation/leds/leds-st1202.rst b/Documentation/leds/leds-st1202.rst new file mode 100644 index 000000000000..1a09fbfcedcf --- /dev/null +++ b/Documentation/leds/leds-st1202.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================ +Kernel driver for STMicroelectronics LED1202 +============================================ + +/sys/class/leds//hw_pattern +-------------------------------- + +Specify a hardware pattern for the ST1202 LED. The LED controller +implements 12 low-side current generators with independent dimming +control. Internal volatile memory allows the user to store up to 8 +different patterns. Each pattern is a particular output configuration +in terms of PWM duty-cycle and duration (ms). + +To be compatible with the hardware pattern format, maximum 8 tuples of +brightness (PWM) and duration must be written to hw_pattern. + +- Min pattern duration: 22 ms +- Max pattern duration: 5660 ms + +The format of the hardware pattern values should be: +"brightness duration brightness duration ..." + +/sys/class/leds//repeat +---------------------------- + +Specify a pattern repeat number, which is common for all channels. +Default is 1; negative numbers and 0 are invalid. + +This file will always return the originally written repeat number. + +When the 255 value is written to it, all patterns will repeat +indefinitely. -- cgit From 599b92fd0efa8b7c43e7f58c9dd0f7951f7cbf09 Mon Sep 17 00:00:00 2001 From: Vicentiu Galanopulo Date: Wed, 18 Dec 2024 18:33:58 +0000 Subject: dt-bindings: leds: Add LED1202 LED Controller The LED1202 is a 12-channel low quiescent current LED driver with: * Supply range from 2.6 V to 5 V * 20 mA current capability per channel * 1.8 V compatible I2C control interface * 8-bit analog dimming individual control * 12-bit local PWM resolution * 8 programmable patterns If the led node is present in the controller then the channel is set to active. Signed-off-by: Vicentiu Galanopulo Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20241218183401.41687-3-vicentiu.galanopulo@remote-tech.co.uk Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/st,led1202.yaml | 132 +++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/st,led1202.yaml diff --git a/Documentation/devicetree/bindings/leds/st,led1202.yaml b/Documentation/devicetree/bindings/leds/st,led1202.yaml new file mode 100644 index 000000000000..f1e5e4efaa3a --- /dev/null +++ b/Documentation/devicetree/bindings/leds/st,led1202.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/st,led1202.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ST LED1202 LED controllers + +maintainers: + - Vicentiu Galanopulo + +description: | + The LED1202 is a 12-channel low quiescent current LED controller + programmable via I2C; The output current can be adjusted separately + for each channel by 8-bit analog and 12-bit digital dimming control. + Datasheet available at + https://www.st.com/en/power-management/led1202.html + +properties: + compatible: + const: st,led1202 + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-9a-f]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 11 + + required: + - reg + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@58 { + compatible = "st,led1202"; + reg = <0x58>; + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0x0>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <1>; + }; + + led@1 { + reg = <0x1>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <2>; + }; + + led@2 { + reg = <0x2>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <3>; + }; + + led@3 { + reg = <0x3>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <4>; + }; + + led@4 { + reg = <0x4>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <5>; + }; + + led@5 { + reg = <0x5>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <6>; + }; + + led@6 { + reg = <0x6>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <7>; + }; + + led@7 { + reg = <0x7>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <8>; + }; + + led@8 { + reg = <0x8>; + function = LED_FUNCTION_STATUS; + color = ; + function-enumerator = <9>; + }; + }; + }; +... -- cgit From 939757aafeb9c266dda37657ee5f7a73ffd35ae2 Mon Sep 17 00:00:00 2001 From: Vicentiu Galanopulo Date: Wed, 18 Dec 2024 18:33:59 +0000 Subject: leds: Add LED1202 I2C driver The output current can be adjusted separately for each channel by 8-bit analog (current sink input) and 12-bit digital (PWM) dimming control. The LED1202 implements 12 low-side current generators with independent dimming control. Internal volatile memory allows the user to store up to 8 different patterns, each pattern is a particular output configuration in terms of PWM duty-cycle (on 4096 steps). Analog dimming (on 256 steps) is per channel but common to all patterns. Each device tree LED node will have a corresponding entry in /sys/class/leds with the label name. The brightness property corresponds to the per channel analog dimming, while the patterns[1-8] to the PWM dimming control. Signed-off-by: Vicentiu Galanopulo Link: https://lore.kernel.org/r/20241218183401.41687-4-vicentiu.galanopulo@remote-tech.co.uk Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 10 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-st1202.c | 416 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 427 insertions(+) create mode 100644 drivers/leds/leds-st1202.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 3bf51a4e01d7..83bce94e8917 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -942,6 +942,16 @@ config LEDS_LM36274 Say Y to enable the LM36274 LED driver for TI LMU devices. This supports the LED device LM36274. +config LEDS_ST1202 + tristate "LED Support for STMicroelectronics LED1202 I2C chips" + depends on LEDS_CLASS + depends on I2C + depends on OF + select LEDS_TRIGGERS + help + Say Y to enable support for LEDs connected to LED1202 + LED driver chips accessed via the I2C bus. + config LEDS_TPS6105X tristate "LED support for TI TPS6105X" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index e7982938ddc1..cb3c3e10a6a3 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -81,6 +81,7 @@ obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o +obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c new file mode 100644 index 000000000000..b691c4886993 --- /dev/null +++ b/drivers/leds/leds-st1202.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED driver for STMicroelectronics LED1202 chip + * + * Copyright (C) 2024 Remote-Tech Ltd. UK + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ST1202_CHAN_DISABLE_ALL 0x00 +#define ST1202_CHAN_ENABLE_HIGH 0x03 +#define ST1202_CHAN_ENABLE_LOW 0x02 +#define ST1202_CONFIG_REG 0x04 +/* PATS: Pattern sequence feature enable */ +#define ST1202_CONFIG_REG_PATS BIT(7) +/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */ +#define ST1202_CONFIG_REG_PATSR BIT(6) +#define ST1202_CONFIG_REG_SHFT BIT(3) +#define ST1202_DEV_ENABLE 0x01 +#define ST1202_DEV_ENABLE_ON BIT(0) +#define ST1202_DEV_ENABLE_RESET BIT(7) +#define ST1202_DEVICE_ID 0x00 +#define ST1202_ILED_REG0 0x09 +#define ST1202_MAX_LEDS 12 +#define ST1202_MAX_PATTERNS 8 +#define ST1202_MILLIS_PATTERN_DUR_MAX 5660 +#define ST1202_MILLIS_PATTERN_DUR_MIN 22 +#define ST1202_PATTERN_DUR 0x16 +#define ST1202_PATTERN_PWM 0x1E +#define ST1202_PATTERN_REP 0x15 + +struct st1202_led { + struct fwnode_handle *fwnode; + struct led_classdev led_cdev; + struct st1202_chip *chip; + bool is_active; + int led_num; +}; + +struct st1202_chip { + struct i2c_client *client; + struct mutex lock; + struct st1202_led leds[ST1202_MAX_LEDS]; +}; + +static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct st1202_led, led_cdev); +} + +static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val) +{ + struct device *dev = &chip->client->dev; + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) +{ + struct device *dev = &chip->client->dev; + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret != 0) + dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret); + + return ret; +} + +static uint8_t st1202_prescalar_to_miliseconds(unsigned int value) +{ + return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1; +} + +static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num, + int pattern, unsigned int value) +{ + u8 value_l, value_h; + int ret; + + value_l = (u8)value; + value_h = (u8)(value >> 8); + + /* + * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh), + * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh) + * and y is the pattern number in hexadecimal (y = 00h .. 07h) + */ + ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern), + value_l); + if (ret != 0) + return ret; + + /* + * Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh), + * where x is the channel number in hexadecimal (x = 00h .. 0Bh) + * and y is the pattern number in hexadecimal (y = 00h .. 07h) + */ + ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern), + value_h); + if (ret != 0) + return ret; + + return 0; +} + +static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern, + unsigned int value) +{ + return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern), + st1202_prescalar_to_miliseconds(value)); +} + +static void st1202_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + + guard(mutex)(&chip->lock); + + st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); +} + +static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + u8 value = 0; + + guard(mutex)(&chip->lock); + + st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); + + return value; +} + +static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) +{ + u8 chan_low, chan_high; + int ret; + + guard(mutex)(&chip->lock); + + if (led_num <= 7) { + ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low); + if (ret < 0) + return ret; + + chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num); + + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low); + if (ret < 0) + return ret; + + } else { + ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high); + if (ret < 0) + return ret; + + chan_high = active ? chan_high | (BIT(led_num) >> 8) : + chan_high & ~(BIT(led_num) >> 8); + + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high); + if (ret < 0) + return ret; + } + + return 0; +} + +static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value) +{ + struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + + return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true); +} + +static int st1202_led_pattern_clear(struct led_classdev *ldev) +{ + struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + int ret; + + guard(mutex)(&chip->lock); + + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF); + if (ret != 0) + return ret; + + ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN); + if (ret != 0) + return ret; + } + + return 0; +} + +static int st1202_led_pattern_set(struct led_classdev *ldev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + int ret; + + if (len > ST1202_MAX_PATTERNS) + return -EINVAL; + + guard(mutex)(&chip->lock); + + for (int patt = 0; patt < len; patt++) { + if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || + pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) + return -EINVAL; + + ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness); + if (ret != 0) + return ret; + + ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t); + if (ret != 0) + return ret; + } + + ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat); + if (ret != 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR | + ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT)); + if (ret != 0) + return ret; + + return 0; +} + +static int st1202_dt_init(struct st1202_chip *chip) +{ + struct device *dev = &chip->client->dev; + struct st1202_led *led; + int err, reg; + + for_each_available_child_of_node_scoped(dev_of_node(dev), child) { + struct led_init_data init_data = {}; + + err = of_property_read_u32(child, "reg", ®); + if (err) + return dev_err_probe(dev, err, "Invalid register\n"); + + led = &chip->leds[reg]; + led->is_active = true; + led->fwnode = of_fwnode_handle(child); + + led->led_cdev.max_brightness = U8_MAX; + led->led_cdev.brightness_set_blocking = st1202_led_set; + led->led_cdev.pattern_set = st1202_led_pattern_set; + led->led_cdev.pattern_clear = st1202_led_pattern_clear; + led->led_cdev.default_trigger = "pattern"; + + init_data.fwnode = led->fwnode; + init_data.devicename = "st1202"; + init_data.default_label = ":"; + + err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data); + if (err < 0) + return dev_err_probe(dev, err, "Failed to register LED class device\n"); + + led->led_cdev.brightness_set = st1202_brightness_set; + led->led_cdev.brightness_get = st1202_brightness_get; + } + + return 0; +} + +static int st1202_setup(struct st1202_chip *chip) +{ + int ret; + + guard(mutex)(&chip->lock); + + /* + * Once the supply voltage is applied, the LED1202 executes some internal checks, + * afterwords it stops the oscillator and puts the internal LDO in quiescent mode. + * To start the device, EN bit must be set inside the “Device Enable” register at + * address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters + * from the internal non-volatile memory and performs an auto-calibration procedure + * in order to increase the output current precision. + * Such initialization lasts about 6.5 ms. + */ + + /* Reset the chip during setup */ + ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET); + if (ret < 0) + return ret; + + /* Enable phase-shift delay feature */ + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + if (ret < 0) + return ret; + + /* Enable the device */ + ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON); + if (ret < 0) + return ret; + + /* Duration of initialization */ + usleep_range(6500, 10000); + + /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */ + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL); + if (ret < 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL); + if (ret < 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, + ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR); + if (ret < 0) + return ret; + + return 0; +} + +static int st1202_probe(struct i2c_client *client) +{ + struct st1202_chip *chip; + struct st1202_led *led; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n"); + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + devm_mutex_init(&client->dev, &chip->lock); + chip->client = client; + + ret = st1202_dt_init(chip); + if (ret < 0) + return ret; + + ret = st1202_setup(chip); + if (ret < 0) + return ret; + + for (int i = 0; i < ST1202_MAX_LEDS; i++) { + led = &chip->leds[i]; + led->chip = chip; + led->led_num = i; + + if (!led->is_active) + continue; + + ret = st1202_channel_set(led->chip, led->led_num, true); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to activate LED channel\n"); + + ret = st1202_led_pattern_clear(&led->led_cdev); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to clear LED pattern\n"); + } + + return 0; +} + +static const struct i2c_device_id st1202_id[] = { + { "st1202-i2c" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, st1202_id); + +static const struct of_device_id st1202_dt_ids[] = { + { .compatible = "st,led1202" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st1202_dt_ids); + +static struct i2c_driver st1202_driver = { + .driver = { + .name = "leds-st1202", + .of_match_table = of_match_ptr(st1202_dt_ids), + }, + .probe = st1202_probe, + .id_table = st1202_id, +}; +module_i2c_driver(st1202_driver); + +MODULE_AUTHOR("Remote Tech LTD"); +MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver"); +MODULE_LICENSE("GPL"); -- cgit From efd435a808d9f7c4d71822119e0ebce1b843d1a7 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Wed, 18 Dec 2024 22:06:30 +0100 Subject: dt-bindings: leds: Convert LP8860 into YAML format Convert Texas Instruments' LP8860 LED driver bindings into YAML format. Signed-off-by: Alexander Sverdlin Acked-by: Andrew Davis Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20241218210631.72997-1-alexander.sverdlin@siemens.com Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/leds-lp8860.txt | 50 ------------ .../devicetree/bindings/leds/ti,lp8860.yaml | 90 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 50 deletions(-) delete mode 100644 Documentation/devicetree/bindings/leds/leds-lp8860.txt create mode 100644 Documentation/devicetree/bindings/leds/ti,lp8860.yaml diff --git a/Documentation/devicetree/bindings/leds/leds-lp8860.txt b/Documentation/devicetree/bindings/leds/leds-lp8860.txt deleted file mode 100644 index 8bb25749a3da..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-lp8860.txt +++ /dev/null @@ -1,50 +0,0 @@ -* Texas Instruments - lp8860 4-Channel LED Driver - -The LP8860-Q1 is an high-efficiency LED -driver with boost controller. It has 4 high-precision -current sinks that can be controlled by a PWM input -signal, a SPI/I2C master, or both. - -Required properties: - - compatible : - "ti,lp8860" - - reg : I2C slave address - - #address-cells : 1 - - #size-cells : 0 - -Optional properties: - - enable-gpios : gpio pin to enable (active high)/disable the device. - - vled-supply : LED supply - -Required child properties: - - reg : 0 - -Optional child properties: - - function : see Documentation/devicetree/bindings/leds/common.txt - - color : see Documentation/devicetree/bindings/leds/common.txt - - label : see Documentation/devicetree/bindings/leds/common.txt (deprecated) - - linux,default-trigger : - see Documentation/devicetree/bindings/leds/common.txt - -Example: - -#include - -led-controller@2d { - compatible = "ti,lp8860"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0x2d>; - enable-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>; - vled-supply = <&vbatt>; - - led@0 { - reg = <0>; - function = LED_FUNCTION_BACKLIGHT; - color = ; - linux,default-trigger = "backlight"; - }; -} - -For more product information please see the link below: -https://www.ti.com/product/lp8860-q1 diff --git a/Documentation/devicetree/bindings/leds/ti,lp8860.yaml b/Documentation/devicetree/bindings/leds/ti,lp8860.yaml new file mode 100644 index 000000000000..0ee357b02661 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/ti,lp8860.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/ti,lp8860.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments - lp8860 4-Channel LED Driver + +maintainers: + - Andrew Davis + +description: | + The LP8860-Q1 is an high-efficiency LED driver with boost controller. + It has 4 high-precision current sinks that can be controlled by a PWM input + signal, a SPI/I2C master, or both. + + For more product information please see the link below: + https://www.ti.com/product/lp8860-q1 + +properties: + compatible: + const: ti,lp8860 + + reg: + maxItems: 1 + description: I2C slave address + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + enable-gpios: + maxItems: 1 + description: GPIO pin to enable (active high) / disable the device + + vled-supply: + description: LED supply + +patternProperties: + "^led(@[0-3])?$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + Index of the LED. + maxItems: 1 + + function: true + color: true + label: true + linux,default-trigger: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@2d { + compatible = "ti,lp8860"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x2d>; + enable-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>; + vled-supply = <&vbatt>; + + led@0 { + reg = <0>; + function = LED_FUNCTION_BACKLIGHT; + color = ; + linux,default-trigger = "backlight"; + }; + }; + }; + +... -- cgit From e14d879292b0f7755c0d51b82a19b30859bb080a Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Wed, 18 Dec 2024 22:08:27 +0100 Subject: leds: lp8864: Add support for Texas Instruments LP8864, LP8864S, LP8866 LED-backlights Add driver for TI LP8864, LP8864S, LP8866 4/6 channel LED-backlight drivers with I2C interface. Link: https://www.ti.com/lit/gpn/lp8864-q1 Link: https://www.ti.com/lit/gpn/lp8864s-q1 Link: https://www.ti.com/lit/gpn/lp8866-q1 Link: https://www.ti.com/lit/gpn/lp8866s-q1 Signed-off-by: Alexander Sverdlin Link: https://lore.kernel.org/r/20241218210829.73191-3-alexander.sverdlin@siemens.com Signed-off-by: Lee Jones --- MAINTAINERS | 7 ++ drivers/leds/Kconfig | 12 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-lp8864.c | 296 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 drivers/leds/leds-lp8864.c diff --git a/MAINTAINERS b/MAINTAINERS index cbe4cfbe110d..8e0acf3ea018 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23240,6 +23240,13 @@ S: Supported F: Documentation/devicetree/bindings/iio/dac/ti,dac7612.yaml F: drivers/iio/dac/ti-dac7612.c +TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER +M: Alexander Sverdlin +L: linux-leds@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/leds/backlight/ti,lp8864.yaml +F: drivers/leds/leds-lp8864.c + TEXAS INSTRUMENTS' SYSTEM CONTROL INTERFACE (TISCI) PROTOCOL DRIVER M: Nishanth Menon M: Tero Kristo diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 83bce94e8917..6efd514bfb48 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -513,6 +513,18 @@ config LEDS_LP8860 on the LP8860 4 channel LED driver using the I2C communication bus. +config LEDS_LP8864 + tristate "LED support for the TI LP8864/LP8866 4/6 channel LED drivers" + depends on LEDS_CLASS && I2C && OF + select REGMAP_I2C + help + If you say yes here you get support for the TI LP8864-Q1, + LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight + drivers with I2C interface. + + To compile this driver as a module, choose M here: the + module will be called leds-lp8864. + config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" depends on LEDS_CLASS && BROKEN diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index cb3c3e10a6a3..1cedd61ddb28 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o +obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o diff --git a/drivers/leds/leds-lp8864.c b/drivers/leds/leds-lp8864.c new file mode 100644 index 000000000000..3afd729d2f8a --- /dev/null +++ b/drivers/leds/leds-lp8864.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI LP8864/LP8866 4/6 Channel LED Driver + * + * Copyright (C) 2024 Siemens AG + * + * Based on LP8860 driver by Dan Murphy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LP8864_BRT_CONTROL 0x00 +#define LP8864_USER_CONFIG1 0x04 +#define LP8864_BRT_MODE_MASK GENMASK(9, 8) +#define LP8864_BRT_MODE_REG BIT(9) /* Brightness control by DISPLAY_BRT reg */ +#define LP8864_SUPPLY_STATUS 0x0e +#define LP8864_BOOST_STATUS 0x10 +#define LP8864_LED_STATUS 0x12 +#define LP8864_LED_STATUS_WR_MASK GENMASK(14, 9) /* Writeable bits in the LED_STATUS reg */ + +/* Textual meaning for status bits, starting from bit 1 */ +static const char *const lp8864_supply_status_msg[] = { + "Vin under-voltage fault", + "Vin over-voltage fault", + "Vdd under-voltage fault", + "Vin over-current fault", + "Missing charge pump fault", + "Charge pump fault", + "Missing boost sync fault", + "CRC error fault ", +}; + +/* Textual meaning for status bits, starting from bit 1 */ +static const char *const lp8864_boost_status_msg[] = { + "Boost OVP low fault", + "Boost OVP high fault", + "Boost over-current fault", + "Missing boost FSET resistor fault", + "Missing MODE SEL resistor fault", + "Missing LED resistor fault", + "ISET resistor short to ground fault", + "Thermal shutdown fault", +}; + +/* Textual meaning for every register bit */ +static const char *const lp8864_led_status_msg[] = { + "LED 1 fault", + "LED 2 fault", + "LED 3 fault", + "LED 4 fault", + "LED 5 fault", + "LED 6 fault", + "LED open fault", + "LED internal short fault", + "LED short to GND fault", + NULL, NULL, NULL, + "Invalid string configuration fault", + NULL, + "I2C time out fault", +}; + +/** + * struct lp8864_led + * @client: Pointer to the I2C client + * @led_dev: led class device pointer + * @regmap: Devices register map + * @led_status_mask: Helps to report LED fault only once + */ +struct lp8864_led { + struct i2c_client *client; + struct led_classdev led_dev; + struct regmap *regmap; + u16 led_status_mask; +}; + +static int lp8864_fault_check(struct lp8864_led *led) +{ + int ret, i; + unsigned int val; + + ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val); + if (ret) + goto err; + + /* Odd bits are status bits, even bits are clear bits */ + for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++) + if (val & BIT(i * 2 + 1)) + dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]); + + /* + * Clear bits have an index preceding the corresponding Status bits; + * both have to be written "1" simultaneously to clear the corresponding + * Status bit. + */ + if (val) + ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val); + if (ret) + goto err; + + ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val); + if (ret) + goto err; + + /* Odd bits are status bits, even bits are clear bits */ + for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++) + if (val & BIT(i * 2 + 1)) + dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]); + + if (val) + ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val); + if (ret) + goto err; + + ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val); + if (ret) + goto err; + + /* + * Clear already reported faults that maintain their value until device + * power-down + */ + val &= ~led->led_status_mask; + + for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++) + if (lp8864_led_status_msg[i] && val & BIT(i)) + dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]); + + /* + * Mark those which maintain their value until device power-down as + * "already reported" + */ + led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK; + + /* + * Only bits 14, 12, 10 have to be cleared here, but others are RO, + * we don't care what we write to them. + */ + if (val & LP8864_LED_STATUS_WR_MASK) + ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val); + if (ret) + goto err; + + return 0; + +err: + dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static int lp8864_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev); + /* Scale 0..LED_FULL into 16-bit HW brightness */ + unsigned int val = brt_val * 0xffff / LED_FULL; + int ret; + + ret = lp8864_fault_check(led); + if (ret) + return ret; + + ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val); + if (ret) + dev_err(&led->client->dev, "Failed to write brightness value\n"); + + return ret; +} + +static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev) +{ + struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev); + unsigned int val; + int ret; + + ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val); + if (ret) { + dev_err(&led->client->dev, "Failed to read brightness value\n"); + return ret; + } + + /* Scale 16-bit HW brightness into 0..LED_FULL */ + return val * LED_FULL / 0xffff; +} + +static const struct regmap_config lp8864_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static void lp8864_disable_gpio(void *data) +{ + struct gpio_desc *gpio = data; + + gpiod_set_value(gpio, 0); +} + +static int lp8864_probe(struct i2c_client *client) +{ + int ret; + struct lp8864_led *led; + struct device_node *np = dev_of_node(&client->dev); + struct device_node *child_node; + struct led_init_data init_data = {}; + struct gpio_desc *enable_gpio; + + led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + child_node = of_get_next_available_child(np, NULL); + if (!child_node) { + dev_err(&client->dev, "No LED function defined\n"); + return -EINVAL; + } + + ret = devm_regulator_get_enable_optional(&client->dev, "vled"); + if (ret && ret != -ENODEV) + return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n"); + + enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(enable_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(enable_gpio), + "Failed to get enable GPIO\n"); + + ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio); + if (ret) + return ret; + + led->client = client; + led->led_dev.brightness_set_blocking = lp8864_brightness_set; + led->led_dev.brightness_get = lp8864_brightness_get; + + led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config); + if (IS_ERR(led->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(led->regmap), + "Failed to allocate regmap\n"); + + /* Control brightness by DISPLAY_BRT register */ + ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK, + LP8864_BRT_MODE_REG); + if (ret) { + dev_err(&led->client->dev, "Failed to set brightness control mode\n"); + return ret; + } + + ret = lp8864_fault_check(led); + if (ret) + return ret; + + init_data.fwnode = of_fwnode_handle(child_node); + init_data.devicename = "lp8864"; + init_data.default_label = ":display_cluster"; + + ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data); + if (ret) + dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static const struct i2c_device_id lp8864_id[] = { + { "lp8864" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, lp8864_id); + +static const struct of_device_id of_lp8864_leds_match[] = { + { .compatible = "ti,lp8864" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_lp8864_leds_match); + +static struct i2c_driver lp8864_driver = { + .driver = { + .name = "lp8864", + .of_match_table = of_lp8864_leds_match, + }, + .probe = lp8864_probe, + .id_table = lp8864_id, +}; +module_i2c_driver(lp8864_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver"); +MODULE_AUTHOR("Alexander Sverdlin "); +MODULE_LICENSE("GPL"); -- cgit From 417cad5dc782096350e6a967ee5dd3417a19a24e Mon Sep 17 00:00:00 2001 From: Joe Hattori Date: Fri, 20 Dec 2024 17:53:46 +0900 Subject: leds: cht-wcove: Use devm_led_classdev_register() to avoid memory leak cht_wc_leds_probe() leaks memory when the second led_classdev_register() call in the for-loop fails as it does not call the cleanup function led_classdev_unregister() on the first device. Avoid this leak by calling devm_led_classdev_register(). Fixes: 047da762b9a9 ("leds: Add Intel Cherry Trail Whiskey Cove PMIC LED driver") Signed-off-by: Joe Hattori Link: https://lore.kernel.org/r/20241220085346.533675-1-joe@pf.is.s.u-tokyo.ac.jp Signed-off-by: Lee Jones --- drivers/leds/leds-cht-wcove.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/leds/leds-cht-wcove.c b/drivers/leds/leds-cht-wcove.c index 8246f048edcb..9a609dd5acdc 100644 --- a/drivers/leds/leds-cht-wcove.c +++ b/drivers/leds/leds-cht-wcove.c @@ -394,7 +394,7 @@ static int cht_wc_leds_probe(struct platform_device *pdev) led->cdev.pattern_clear = cht_wc_leds_pattern_clear; led->cdev.max_brightness = 255; - ret = led_classdev_register(&pdev->dev, &led->cdev); + ret = devm_led_classdev_register(&pdev->dev, &led->cdev); if (ret < 0) return ret; } @@ -406,10 +406,6 @@ static int cht_wc_leds_probe(struct platform_device *pdev) static void cht_wc_leds_remove(struct platform_device *pdev) { struct cht_wc_leds *leds = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < CHT_WC_LED_COUNT; i++) - led_classdev_unregister(&leds->leds[i].cdev); /* Restore LED1 regs if hw-control was active else leave LED1 off */ if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL)) -- cgit From daefd7fbd544671ad0b9c2a815d7f5e3d2f0365c Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Sun, 22 Dec 2024 21:04:50 +0100 Subject: leds: triggers: Constify 'struct bin_attribute' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sysfs core now allows instances of 'struct bin_attribute' to be moved into read-only memory. Make use of that to protect them against accidental or malicious modifications. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241222-sysfs-const-bin_attr-led-v1-1-ecc5212a31fa@weissschuh.net Signed-off-by: Lee Jones --- drivers/leds/led-class.c | 6 +++--- drivers/leds/led-triggers.c | 4 ++-- drivers/leds/leds.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 2a04ac61574d..c20ac8ccf52b 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -85,13 +85,13 @@ static ssize_t max_brightness_show(struct device *dev, static DEVICE_ATTR_RO(max_brightness); #ifdef CONFIG_LEDS_TRIGGERS -static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); -static struct bin_attribute *led_trigger_bin_attrs[] = { +static const BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); +static const struct bin_attribute *const led_trigger_bin_attrs[] = { &bin_attr_trigger, NULL, }; static const struct attribute_group led_trigger_group = { - .bin_attrs = led_trigger_bin_attrs, + .bin_attrs_new = led_trigger_bin_attrs, }; #endif diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 78eb20093b2c..b2d40f87a5ff 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -34,7 +34,7 @@ trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig) } ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -123,7 +123,7 @@ static int led_trigger_format(char *buf, size_t size, * copy it. */ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index d7999e7372a4..bee46651e068 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -22,10 +22,10 @@ void led_stop_software_blink(struct led_classdev *led_cdev); void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value); void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value); ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count); ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count); extern struct rw_semaphore leds_list_lock; -- cgit