From 5ed87cc71c1bc1c3a3449ebd2de83e2b9c32670b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 27 Mar 2026 13:58:41 +0100 Subject: [PATCH 01/30] leds: st1202: Drop unused include The driver includes the legacy GPIO header but does not use any symbols from it so drop the include. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260327-leds-st1202-v1-1-15c107cc9fb9@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 4e5dd76d714d..2b68cd3c45f8 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include From ce1464f193a8299166acc26d4f8e13cdb56c060d Mon Sep 17 00:00:00 2001 From: Biswapriyo Nath Date: Wed, 25 Mar 2026 18:07:27 +0000 Subject: [PATCH 02/30] dt-bindings: leds: irled: ir-spi-led: Add new duty-cycle value 30 duty cycle for IR transmitter is used in Xiaomi Redmi Note 8 (ginkgo). Signed-off-by: Biswapriyo Nath Reviewed-by: Sean Young Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260325-ginkgo-add-usb-ir-vib-v1-4-446c6e865ad6@gmail.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml b/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml index 72cadebf6e3e..0297bfbb2750 100644 --- a/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml +++ b/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml @@ -25,7 +25,7 @@ properties: duty-cycle: $ref: /schemas/types.yaml#/definitions/uint8 - enum: [50, 60, 70, 75, 80, 90] + enum: [30, 50, 60, 70, 75, 80, 90] description: Percentage of one period in which the signal is active. From 0261683a4d31783d680e74b3ae5f22f6a62128cc Mon Sep 17 00:00:00 2001 From: Tobias Deiminger Date: Tue, 31 Mar 2026 22:28:48 +0200 Subject: [PATCH 03/30] leds: pca9532: Don't stop blinking for non-zero brightness pca9532 unexpectedly stopped blinking when changing brightness to a non-zero value. To reproduce: echo timer > /sys/class/leds/led-1/trigger # blinks echo 255 > /sys/class/leds/led-1/brightness # blinking stops, light on cat /sys/class/leds/led-1/trigger # still claims [timer] According to Documentation/leds/leds-class.rst, only brightness = 0 shall be a stop condition: > You can change the brightness value of a LED independently of the > timer trigger. However, if you set the brightness value to LED_OFF it > will also disable the timer trigger. Therefore add a guard to continue blinking when brightness != LED_OFF, similar to how pca955x does it since 575f10dc64a2 ("leds: pca955x: Add HW blink support"). Signed-off-by: Tobias Deiminger Link: https://patch.msgid.link/20260331202848.658676-1-tobias.deiminger@linutronix.de Signed-off-by: Lee Jones --- drivers/leds/leds-pca9532.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index 0344189bb991..dae7c6760508 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -182,11 +182,13 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev, int err = 0; struct pca9532_led *led = ldev_to_led(led_cdev); - if (value == LED_OFF) + if (value == LED_OFF) { led->state = PCA9532_OFF; - else if (value == LED_FULL) + } else if (led->state == PCA9532_PWM1) { + return 0; /* Non-zero brightness shall not stop HW blinking */ + } else if (value == LED_FULL) { led->state = PCA9532_ON; - else { + } else { led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */ err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value); if (err) From 7c150c17c01d4d64942b9872d66dd7670e8ba80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corvin=20K=C3=B6hne?= Date: Wed, 8 Apr 2026 08:29:42 +0200 Subject: [PATCH 04/30] dt-binding: leds: Publish common bindings under dual license MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes leds/common.h DT binding header file to be published under GPLv2 or BSD-2-Clause license terms. This change allows this common LED bindings header file to be used in software components as bootloaders and OSes that are not published under GPLv2 terms. All contributors to leds/common.h file in copy. Signed-off-by: Corvin Köhne Acked-by: Krzysztof Kozlowski Acked-by: Gergo Koteles Acked-by: Jacek Anaszewski Acked-by: Rafał Miłecki Link: https://patch.msgid.link/20260408062942.7128-1-corvin.koehne@gmail.com Signed-off-by: Lee Jones --- include/dt-bindings/leds/common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dt-bindings/leds/common.h b/include/dt-bindings/leds/common.h index 4f017bea0123..b7bafbaf7df3 100644 --- a/include/dt-bindings/leds/common.h +++ b/include/dt-bindings/leds/common.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ /* * This header provides macros for the common LEDs device tree bindings. * From 26e15f2558f66b6747ef981df8054d0d92d01b60 Mon Sep 17 00:00:00 2001 From: Piotr Kubik Date: Wed, 8 Apr 2026 13:51:06 +0200 Subject: [PATCH 05/30] leds: trigger: gpio: Use GPIOD_FLAGS_BIT_NONEXCLUSIVE When a GPIO is shared between the LED trigger driver and another driver, the LED trigger driver needs to request the GPIO with GPIOD_FLAGS_BIT_NONEXCLUSIVE to allow both drivers to monitor the same GPIO pin. Without this flag, if another driver has already claimed the GPIO, the LED trigger driver's gpiod_get_optional() call fails silently, and the LED trigger doesn't work. This is needed for scenarios like: - SFP module presence/status LED triggered by SFP Mod_ABS/Rx_LOS Both GPIOs are also monitored by the SFP driver for module state management, so they need to be shared. Signed-off-by: Piotr Kubik Link: https://patch.msgid.link/20260408115106.379834-1-piotr@kubik.pl Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-gpio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index fc911dfec0ef..3b3e6869e856 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -86,7 +86,8 @@ static int gpio_trig_activate(struct led_classdev *led) * The generic property "trigger-sources" is followed, * and we hope that this is a GPIO. */ - gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN); + gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", + GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE); if (IS_ERR(gpio_data->gpiod)) { ret = PTR_ERR(gpio_data->gpiod); kfree(gpio_data); From e7fc971825608d6b3861e0bfef9965c3ae7c0d86 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 9 Apr 2026 10:15:55 -0700 Subject: [PATCH 06/30] leds: qcom-lpg: Allocate channels with main struct Use a flexible array member to combine kzalloc and kcalloc. This required moving the struct lpg_channel definition up as flexible array members require a full definition. Add __counted_by for extra runtime analysis. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260409171555.14580-1-rosenp@gmail.com Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-qcom-lpg.c | 120 +++++++++++++++---------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index f6061c47f863..d7d6518de30f 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -80,58 +80,8 @@ #define SDAM_PAUSE_HI_MULTIPLIER_OFFSET 0x8 #define SDAM_PAUSE_LO_MULTIPLIER_OFFSET 0x9 -struct lpg_channel; struct lpg_data; -/** - * struct lpg - LPG device context - * @dev: pointer to LPG device - * @map: regmap for register access - * @lock: used to synchronize LED and pwm callback requests - * @pwm: PWM-chip object, if operating in PWM mode - * @data: reference to version specific data - * @lut_base: base address of the LUT block (optional) - * @lut_size: number of entries in the LUT block - * @lut_bitmap: allocation bitmap for LUT entries - * @pbs_dev: PBS device - * @lpg_chan_sdam: LPG SDAM peripheral device - * @lut_sdam: LUT SDAM peripheral device - * @pbs_en_bitmap: bitmap for tracking PBS triggers - * @triled_base: base address of the TRILED block (optional) - * @triled_src: power-source for the TRILED - * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register - * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register - * @channels: list of PWM channels - * @num_channels: number of @channels - */ -struct lpg { - struct device *dev; - struct regmap *map; - - struct mutex lock; - - struct pwm_chip *pwm; - - const struct lpg_data *data; - - u32 lut_base; - u32 lut_size; - unsigned long *lut_bitmap; - - struct pbs_dev *pbs_dev; - struct nvmem_device *lpg_chan_sdam; - struct nvmem_device *lut_sdam; - unsigned long pbs_en_bitmap; - - u32 triled_base; - u32 triled_src; - bool triled_has_atc_ctl; - bool triled_has_src_sel; - - struct lpg_channel *channels; - unsigned int num_channels; -}; - /** * struct lpg_channel - per channel data * @lpg: reference to parent lpg @@ -203,8 +153,8 @@ struct lpg_channel { * @lpg: lpg context reference * @cdev: LED class device * @mcdev: Multicolor LED class device - * @num_channels: number of @channels * @channels: list of channels associated with the LED + * @num_channels: number of @channels */ struct lpg_led { struct lpg *lpg; @@ -216,6 +166,55 @@ struct lpg_led { struct lpg_channel *channels[] __counted_by(num_channels); }; +/** + * struct lpg - LPG device context + * @dev: pointer to LPG device + * @map: regmap for register access + * @lock: used to synchronize LED and pwm callback requests + * @pwm: PWM-chip object, if operating in PWM mode + * @data: reference to version specific data + * @lut_base: base address of the LUT block (optional) + * @lut_size: number of entries in the LUT block + * @lut_bitmap: allocation bitmap for LUT entries + * @pbs_dev: PBS device + * @lpg_chan_sdam: LPG SDAM peripheral device + * @lut_sdam: LUT SDAM peripheral device + * @pbs_en_bitmap: bitmap for tracking PBS triggers + * @triled_base: base address of the TRILED block (optional) + * @triled_src: power-source for the TRILED + * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register + * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register + * @num_channels: number of @channels + * @channels: list of PWM channels + */ +struct lpg { + struct device *dev; + struct regmap *map; + + struct mutex lock; + + struct pwm_chip *pwm; + + const struct lpg_data *data; + + u32 lut_base; + u32 lut_size; + unsigned long *lut_bitmap; + + struct pbs_dev *pbs_dev; + struct nvmem_device *lpg_chan_sdam; + struct nvmem_device *lut_sdam; + unsigned long pbs_en_bitmap; + + u32 triled_base; + u32 triled_src; + bool triled_has_atc_ctl; + bool triled_has_src_sel; + + unsigned int num_channels; + struct lpg_channel channels[] __counted_by(num_channels); +}; + /** * struct lpg_channel_data - per channel initialization data * @sdam_offset: Channel offset in LPG SDAM @@ -1475,12 +1474,6 @@ static int lpg_init_channels(struct lpg *lpg) struct lpg_channel *chan; int i; - lpg->num_channels = data->num_channels; - lpg->channels = devm_kcalloc(lpg->dev, data->num_channels, - sizeof(struct lpg_channel), GFP_KERNEL); - if (!lpg->channels) - return -ENOMEM; - for (i = 0; i < data->num_channels; i++) { chan = &lpg->channels[i]; @@ -1603,18 +1596,21 @@ static int lpg_init_sdam(struct lpg *lpg) static int lpg_probe(struct platform_device *pdev) { + const struct lpg_data *data; struct lpg *lpg; int ret; int i; - lpg = devm_kzalloc(&pdev->dev, sizeof(*lpg), GFP_KERNEL); + data = of_device_get_match_data(&pdev->dev); + if (!data) + return -EINVAL; + + lpg = devm_kzalloc(&pdev->dev, struct_size(lpg, channels, data->num_channels), GFP_KERNEL); if (!lpg) return -ENOMEM; - lpg->data = of_device_get_match_data(&pdev->dev); - if (!lpg->data) - return -EINVAL; - + lpg->num_channels = data->num_channels; + lpg->data = data; lpg->dev = &pdev->dev; mutex_init(&lpg->lock); From 7bf097d90e0439c3b601ed72758706a6978c7a41 Mon Sep 17 00:00:00 2001 From: Xinhong Hu Date: Thu, 23 Apr 2026 18:39:10 +0800 Subject: [PATCH 07/30] docs: leds: Fix sysfs ABI reference in lp5812.rst Documentation/ABI/testing/sysfs-class-led-multicolor is a plain ABI description without a .rst suffix. The lp5812 documentation incorrectly referred to sysfs-class-led-multicolor.rst, which does not exist. This was reported by documentation-file-ref-check (make refcheckdocs). Signed-off-by: Xinhong Hu Link: https://patch.msgid.link/tencent_26F6155ED10CA20CC65F62FD659218853809@qq.com Signed-off-by: Lee Jones --- Documentation/leds/leds-lp5812.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/leds/leds-lp5812.rst b/Documentation/leds/leds-lp5812.rst index c2a6368d5149..12e757d45c3a 100644 --- a/Documentation/leds/leds-lp5812.rst +++ b/Documentation/leds/leds-lp5812.rst @@ -20,7 +20,7 @@ Sysfs Interface =============== This driver uses the standard multicolor LED class interfaces defined -in Documentation/ABI/testing/sysfs-class-led-multicolor.rst. +in Documentation/ABI/testing/sysfs-class-led-multicolor. Each LP5812 LED output appears under ``/sys/class/leds/`` with its assigned label (for example ``LED_A``). From d92e7ca6931910e72ebbed7ff96f4f74c24cf0d8 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sat, 25 Apr 2026 20:03:21 -0400 Subject: [PATCH 08/30] leds: as3668: Fix Kconfig symbol name mismatch in Makefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kconfiglint reports: X001: CONFIG_LEDS_AS3668 referenced in Makefile but not defined in any Kconfig The AS3668 LED driver was introduced in commit c7dd343a3756 ("leds: as3668: Driver for the ams Osram 4-channel i2c LED driver"). That commit defined the Kconfig symbol as LEDS_OSRAM_AMS_AS3668 in drivers/leds/Kconfig but used the shorter name LEDS_AS3668 in the Makefile's obj-* line. Because the Makefile references CONFIG_LEDS_AS3668 which does not exist, the driver can never be built — the obj-* line always evaluates to obj- += leds-as3668.o (empty config), so the object file is never compiled regardless of what the user selects in menuconfig. Fix the Makefile to reference the correct Kconfig symbol CONFIG_LEDS_OSRAM_AMS_AS3668, matching what is defined in drivers/leds/Kconfig. Assisted-by: Claude:claude-opus-4-6 kconfiglint Signed-off-by: Sasha Levin Acked-by: Lukas Timmermann Link: https://patch.msgid.link/20260426000322.55999-1-sashal@kernel.org Signed-off-by: Lee Jones --- drivers/leds/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 8fdb45d5b439..7db3768912ca 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -15,7 +15,7 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o obj-$(CONFIG_LEDS_APU) += leds-apu.o obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o -obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o +obj-$(CONFIG_LEDS_OSRAM_AMS_AS3668) += leds-as3668.o obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o From 647a6b59d138ff11761fa97dbf36524d726437c7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Apr 2026 09:01:18 +0200 Subject: [PATCH 09/30] leds: qcom: Unify user-visible "Qualcomm" name Various names for Qualcomm as a company are used in user-visible config options: QCOM, Qualcomm and Qualcomm Technologies. Switch to unified "Qualcomm" so it will be easier for users to identify the options when for example running menuconfig. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260427070117.18363-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Lee Jones --- drivers/leds/flash/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig index 5e08102a6784..31c996651f2a 100644 --- a/drivers/leds/flash/Kconfig +++ b/drivers/leds/flash/Kconfig @@ -76,7 +76,7 @@ config LEDS_MT6370_FLASH will be called "leds-mt6370-flash". config LEDS_QCOM_FLASH - tristate "LED support for flash module inside Qualcomm Technologies, Inc. PMIC" + tristate "LED support for flash module inside Qualcomm PMIC" depends on MFD_SPMI_PMIC || COMPILE_TEST depends on LEDS_CLASS && OF depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS From a03484e1e42893e2b219d9d780ce6933d319ef46 Mon Sep 17 00:00:00 2001 From: Stepan Ionichev Date: Mon, 4 May 2026 17:37:30 +0500 Subject: [PATCH 10/30] leds: blinkm: Fix spelling and comment style above lock acquire Fix two issues in the comment above mutex_lock_interruptible() in blinkm_transfer_hw(): - Spelling mistake (Aquire -> Acquire). - Trailing "*/" was on the same line as text; move it to its own line to match kernel coding style. No functional change. Signed-off-by: Stepan Ionichev Link: https://patch.msgid.link/20260504123730.1094-1-sozdayvek@gmail.com Signed-off-by: Lee Jones --- drivers/leds/leds-blinkm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c index 577497b9d426..1978bbda47db 100644 --- a/drivers/leds/leds-blinkm.c +++ b/drivers/leds/leds-blinkm.c @@ -356,7 +356,8 @@ static int blinkm_transfer_hw(struct i2c_client *client, int cmd) struct blinkm_data *data = i2c_get_clientdata(client); /* We start hardware transfers which are not to be - * mixed with other commands. Aquire a lock now. */ + * mixed with other commands. Acquire a lock now. + */ if (mutex_lock_interruptible(&data->update_lock) < 0) return -EAGAIN; From 79ac28f175e02c8ca6216d5df1c83885f30b4c3b Mon Sep 17 00:00:00 2001 From: Carlos Ferreira Date: Mon, 4 May 2026 16:54:34 +0200 Subject: [PATCH 11/30] Documentation: leds: leds-class: Document keyboard backlight LED class naming Document the existing practice of always using 'kbd_backlight' for the function part of LED class device names for LED class devices which control single-zone keyboard backlights. Also extend this existing practice with a new naming scheme for keyboards with zoned backlight control. There are several drivers in the works (see the Link:tags below) which offer backlight control for keyboards where the keyboard backlight is divided in a limited number of zones, e.g. "main", "cursor" and "numpad" zones. It is important to agree on a consistent naming scheme for these now, so that userspace can support multiple different models / vendors through a single unified naming scheme. Link: https://lore.kernel.org/platform-driver-x86/20230131235027.36304-1-rishitbansal0@gmail.com/ Link: https://lore.kernel.org/platform-driver-x86/20240719100011.16656-1-carlosmiguelferreira.2003@gmail.com/ Link: https://lore.kernel.org/platform-driver-x86/20260304105831.119349-3-edip@medip.dev/ Link: https://lore.kernel.org/platform-driver-x86/20240806205001.191551-2-mustafa.eskieksi@gmail.com/ Link: https://lore.kernel.org/linux-input/20260402075239.3829699-1-xav@bes.tel/ Signed-off-by: Carlos Ferreira Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Acked-by: Kate Hsuan Link: https://patch.msgid.link/20260504145434.12746-1-johannes.goede@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/leds/leds-class.rst | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst index 5db620ed27aa..3913966cfdac 100644 --- a/Documentation/leds/leds-class.rst +++ b/Documentation/leds/leds-class.rst @@ -116,6 +116,69 @@ above leaves scope for further attributes should they be needed. If sections of the name don't apply, just leave that section blank. +Keyboard backlight control LED Device Naming +============================================ + +For backlit keyboards with a single brightness / color settings a single +(multicolor) LED class device should be used to allow userspace to change +the backlight brightness (and if possible the color). This LED class device +must use "kbd_backlight" for the function part of the LED class device name. +IOW the name must end with ":kbd_backlight". + +For backlit keyboards with multiple control zones, one (multicolor) LED class +device should be used per zone. These LED class devices' name must follow: + + "::kbd_zoned_backlight-" + +and must be the same for all zones of the same keyboard. + + should be descriptive of which part of the keyboard backlight +the zone covers and should be suitable for userspace to show to an end user +in an UI for controlling the zones. + +Where possible should be a value already used by other +zoned keyboards with a similar or identical zone layout, e.g.: + +::kbd_zoned_backlight-right +::kbd_zoned_backlight-middle +::kbd_zoned_backlight-left +::kbd_zoned_backlight-corners +::kbd_zoned_backlight-wasd + +or: + +::kbd_zoned_backlight-main +::kbd_zoned_backlight-cursor +::kbd_zoned_backlight-numpad +::kbd_zoned_backlight-corners +::kbd_zoned_backlight-wasd + +Note that this is intended for keyboards with a limited number of zones, +keyboards with per key addressable backlighting must not use LED class devices +since the sysfs API is not suitable for rapidly change multiple LEDs in one +"commit" as is necessary to do animations / special effects on such keyboards. + +An exception to the rule that all zones must follow: + + "::kbd_zoned_backlight-" + +is made for the special case where there is a single big zone which controls +the backlighting of almost all of the keyboard and there are some small areas +with separate control, like just the 4 cursor keys, or the WASD keys. In this +case the main zone should use 'kbd_backlight' for the function part of the name +for compatibility with (older) userspace code which is not aware of +the "kbd_zoned_backlight-" function naming scheme. + +While the smaller zones should use the new zoned naming scheme. Such a setup +would result in e.g.: + +::kbd_backlight +::kbd_zoned_backlight-wasd + +"kbd_zoned_backlight-" aware userspace should be aware of this +exception and check for a main zone with a "kbd_backlight" function-name. + + Brightness setting API ====================== From f0a66563aa2d0bb5673329ae6c4acb5d583cfdef Mon Sep 17 00:00:00 2001 From: Steffen Trumtrar Date: Tue, 5 May 2026 08:16:10 +0200 Subject: [PATCH 12/30] leds: Add support for TI LP5860 LED driver chip Add support for the Texas Instruments LP5860 LED driver chip via SPI interfaces. The LP5860 is an LED matrix driver for up to 196 LEDs, which supports short and open detection of the individual channel select lines. It can be connected to SPI or I2C bus. For now add support for SPI only. Signed-off-by: Steffen Trumtrar Link: https://patch.msgid.link/20260505-v6-14-topic-ti-lp5860-v10-1-ee29341a75e4@pengutronix.de Signed-off-by: Lee Jones --- drivers/leds/rgb/Kconfig | 25 +++ drivers/leds/rgb/Makefile | 2 + drivers/leds/rgb/leds-lp5860-core.c | 234 ++++++++++++++++++++++++ drivers/leds/rgb/leds-lp5860-spi.c | 98 ++++++++++ drivers/leds/rgb/leds-lp5860.h | 268 ++++++++++++++++++++++++++++ 5 files changed, 627 insertions(+) create mode 100644 drivers/leds/rgb/leds-lp5860-core.c create mode 100644 drivers/leds/rgb/leds-lp5860-spi.c create mode 100644 drivers/leds/rgb/leds-lp5860.h diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 28ef4c487367..9a4ba6531cf8 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -39,6 +39,31 @@ config LEDS_LP5812 If unsure, say N. +config LEDS_LP5860_CORE + tristate "Core Driver for TI LP5860" + depends on LEDS_CLASS + depends on OF + select REGMAP + help + This option supports common operations for LP5860 devices. + The LP5860 is a LED matrix driver with 18 constant current + sinks and 11 scan switches for 198 LED dots. Each dot can be + controlled individually and supports 8/16-bit PWM dimming. + The chip supports individual LED open and short detection. + + The device can be used with SPI or I2C bus. + +config LEDS_LP5860_SPI + tristate "LED Support for TI LP5860 SPI LED driver chip" + depends on SPI + select LEDS_LP5860_CORE + help + If you say yes here you get support for the Texas Instruments + LP5860 LED driver for SPI bus connections. + + To compile this driver as a module, choose M here: the + module will be called leds-lp5860-spi. + config LEDS_NCP5623 tristate "LED support for NCP5623" depends on I2C diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index be45991f63f5..f3b365ea082d 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -3,6 +3,8 @@ obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o +obj-$(CONFIG_LEDS_LP5860_CORE) += leds-lp5860-core.o +obj-$(CONFIG_LEDS_LP5860_SPI) += leds-lp5860-spi.o obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o diff --git a/drivers/leds/rgb/leds-lp5860-core.c b/drivers/leds/rgb/leds-lp5860-core.c new file mode 100644 index 000000000000..fd0e2f6e6e0f --- /dev/null +++ b/drivers/leds/rgb/leds-lp5860-core.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Pengutronix + * + * Author: Steffen Trumtrar + */ + +#include +#include +#include +#include +#include +#include + +#include "leds-lp5860.h" + +static struct lp5860_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev) +{ + return container_of(mc_cdev, struct lp5860_led, mc_cdev); +} + +static int lp5860_set_dot_onoff(struct lp5860_led *led, unsigned int dot, bool enable) +{ + unsigned int offset = dot / LP5860_MAX_DOT_ONOFF_GROUP_NUM; + unsigned int mask = BIT(dot % LP5860_MAX_DOT_ONOFF_GROUP_NUM); + + if (dot > LP5860_MAX_LED) + return -EINVAL; + + return regmap_update_bits(led->chip->regmap, + LP5860_REG_DOT_ONOFF_START + offset, mask, + enable ? LP5860_DOT_ALL_ON : LP5860_DOT_ALL_OFF); +} + +static int lp5860_set_mc_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct lp5860_led *led = mcled_cdev_to_led(mc_cdev); + + led_mc_calc_color_components(mc_cdev, brightness); + + guard(mutex)(&led->chip->lock); + for (int i = 0; i < led->mc_cdev.num_colors; i++) { + unsigned int channel = mc_cdev->subled_info[i].channel; + unsigned int led_brightness = mc_cdev->subled_info[i].brightness; + int ret; + + ret = lp5860_set_dot_onoff(led, channel, !!led_brightness); + if (ret) + return ret; + + ret = regmap_write(led->chip->regmap, + LP5860_REG_PWM_BRI_START + channel, led_brightness); + if (ret) + return ret; + } + + return 0; +} + +static int lp5860_chip_enable(struct lp5860 *lp, bool enable) +{ + guard(mutex)(&lp->lock); + return regmap_write(lp->regmap, LP5860_REG_CHIP_EN, enable); +} + +static int lp5860_led_init(struct lp5860_led *led, struct fwnode_handle *fwnode, + unsigned int channel) +{ + enum led_default_state default_state; + unsigned int brightness; + int ret; + + guard(mutex)(&led->chip->lock); + ret = regmap_read(led->chip->regmap, LP5860_REG_PWM_BRI_START + channel, &brightness); + if (ret) + return ret; + + default_state = led_init_default_state_get(fwnode); + + switch (default_state) { + case LEDS_DEFSTATE_ON: + led->brightness = LP5860_MAX_BRIGHTNESS; + break; + case LEDS_DEFSTATE_KEEP: + led->brightness = min(brightness, LP5860_MAX_BRIGHTNESS); + break; + default: + led->brightness = 0; + break; + } + + return 0; +} + +static int lp5860_iterate_subleds(struct lp5860_led *led, struct led_init_data *init_data) +{ + struct fwnode_handle *led_node = NULL; + struct fwnode_handle *multi_led = init_data->fwnode; + int subled = 0; + + fwnode_for_each_child_node(multi_led, led_node) { + u32 channel; + u32 color_index; + int ret; + + ret = fwnode_property_read_u32(led_node, "color", &color_index); + if (ret) { + dev_err_probe(led->chip->dev, ret, + "%pfwP: Cannot read 'color' property. Skipping.\n", led_node); + fwnode_handle_put(led_node); + return ret; + } + + ret = fwnode_property_read_u32(led_node, "reg", &channel); + if (ret < 0 || channel > LP5860_MAX_LED) { + dev_err_probe(led->chip->dev, ret, + "%pfwP: 'reg' property is missing. Skipping.\n", led_node); + fwnode_handle_put(led_node); + return ret; + } + + led->mc_cdev.subled_info[subled].color_index = color_index; + led->mc_cdev.subled_info[subled].channel = channel; + ret = lp5860_led_init(led, init_data->fwnode, channel); + if (ret) { + dev_err_probe(led->chip->dev, ret, + "%pfwP: Failed to init LED\n", led_node); + fwnode_handle_put(led_node); + return ret; + } + + subled++; + } + + return 0; +} + +static int lp5860_init_dt(struct lp5860 *lp) +{ + struct led_init_data init_data = {}; + struct led_classdev *led_cdev; + struct mc_subled *mc_led_info; + struct lp5860_led *led; + int led_index = 0; + int chan; + int ret; + + device_for_each_child_node_scoped(lp->dev, multi_led) { + led = &lp->leds[led_index]; + + init_data.fwnode = multi_led; + + /* Count the number of channels in this multi_led */ + chan = fwnode_get_child_node_count(multi_led); + if (!chan || chan > LP5860_MAX_LED_CHANNELS) + return -EINVAL; + + led->mc_cdev.num_colors = chan; + + mc_led_info = devm_kcalloc(lp->dev, chan, sizeof(*mc_led_info), GFP_KERNEL); + if (!mc_led_info) + return -ENOMEM; + + led->chip = lp; + led->mc_cdev.subled_info = mc_led_info; + led_cdev = &led->mc_cdev.led_cdev; + led_cdev->max_brightness = LP5860_MAX_BRIGHTNESS; + led_cdev->brightness_set_blocking = lp5860_set_mc_brightness; + + ret = lp5860_iterate_subleds(led, &init_data); + if (ret) + continue; + + ret = lp5860_set_mc_brightness(&led->mc_cdev.led_cdev, led->brightness); + if (ret) + return dev_err_probe(lp->dev, ret, "%pfwP: Failed to set Multi-Color brightness\n", + multi_led); + + ret = devm_led_classdev_multicolor_register_ext(lp->dev, &led->mc_cdev, &init_data); + if (ret) + return dev_err_probe(lp->dev, ret, "%pfwP: Failed to register Multi-Color LEDs\n", + multi_led); + led_index++; + } + + return 0; +} + +int lp5860_device_init(struct device *dev) +{ + struct lp5860 *lp = dev_get_drvdata(dev); + int ret; + + ret = lp5860_chip_enable(lp, LP5860_CHIP_ENABLE); + if (ret) + return ret; + + /* + * Set to 8-bit PWM data without VSYNC. + * Data is sent out for display instantly after received. + */ + mutex_lock(&lp->lock); + ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK, + LP5860_MODE_1 << LP5860_MODE_SHIFT); + if (ret) + goto err_disable; + mutex_unlock(&lp->lock); + + ret = lp5860_init_dt(lp); + if (ret) + goto err_disable; + + return 0; + +err_disable: + mutex_unlock(&lp->lock); + lp5860_chip_enable(lp, LP5860_CHIP_DISABLE); + return ret; +} +EXPORT_SYMBOL_GPL(lp5860_device_init); + +void lp5860_device_remove(struct device *dev) +{ + struct lp5860 *lp = dev_get_drvdata(dev); + + lp5860_chip_enable(lp, LP5860_CHIP_DISABLE); +} +EXPORT_SYMBOL_GPL(lp5860_device_remove); + +MODULE_AUTHOR("Steffen Trumtrar "); +MODULE_DESCRIPTION("TI LP5860 RGB LED core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/leds-lp5860-spi.c b/drivers/leds/rgb/leds-lp5860-spi.c new file mode 100644 index 000000000000..5e0c44854a68 --- /dev/null +++ b/drivers/leds/rgb/leds-lp5860-spi.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Pengutronix + * + * Author: Steffen Trumtrar + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "leds-lp5860.h" + +#define LP5860_SPI_WRITE_FLAG BIT(13) + +/* + * The lp5860 uses a rather uncommon SPI data format: The R/W flag is on BIT(5) in the two address + * bytes; BIT(4) to BIT(0) are don't care. Therefore it has 10 bits for the address and 6 bits for + * padding the address. The address bytes are sent MSB first. Matching the cores registers to regmap + * results in write_flag_mask being BIT(13). + */ +static const struct regmap_config lp5860_regmap_config = { + .name = "lp5860", + .reg_bits = 10, + .pad_bits = 6, + .val_bits = 8, + .write_flag_mask = LP5860_SPI_WRITE_FLAG, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .max_register = LP5860_MAX_REG, +}; + +static int lp5860_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct lp5860 *lp5860; + unsigned int multi_leds; + + multi_leds = device_get_child_node_count(dev); + if (!multi_leds) { + dev_err(dev, "LEDs are not defined in Device Tree!"); + return -ENODEV; + } + + if (multi_leds > LP5860_MAX_LED) { + dev_err(dev, "Too many LEDs specified.\n"); + return -EINVAL; + } + + lp5860 = devm_kzalloc(dev, struct_size(lp5860, leds, multi_leds), + GFP_KERNEL); + if (!lp5860) + return -ENOMEM; + + lp5860->regmap = devm_regmap_init_spi(spi, &lp5860_regmap_config); + if (IS_ERR(lp5860->regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(lp5860->regmap), + "Failed to initialise Regmap.\n"); + + lp5860->dev = dev; + mutex_init(&lp5860->lock); + + spi_set_drvdata(spi, lp5860); + + return lp5860_device_init(dev); +} + +static void lp5860_remove(struct spi_device *spi) +{ + struct lp5860 *lp5860 = spi_get_drvdata(spi); + + mutex_destroy(&lp5860->lock); + + lp5860_device_remove(&spi->dev); +} + +static const struct of_device_id lp5860_of_match[] = { + { .compatible = "ti,lp5860" }, + {} +}; +MODULE_DEVICE_TABLE(of, lp5860_of_match); + +static struct spi_driver lp5860_driver = { + .driver = { + .name = "lp5860-spi", + .of_match_table = lp5860_of_match, + }, + .probe = lp5860_probe, + .remove = lp5860_remove, +}; +module_spi_driver(lp5860_driver); + +MODULE_AUTHOR("Steffen Trumtrar "); +MODULE_DESCRIPTION("TI LP5860 RGB LED SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/leds-lp5860.h b/drivers/leds/rgb/leds-lp5860.h new file mode 100644 index 000000000000..940be0c6e8da --- /dev/null +++ b/drivers/leds/rgb/leds-lp5860.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2025 Pengutronix + * + * Author: Steffen Trumtrar + */ + +#ifndef _DRIVERS_LEDS_RGB_LP5860_H +#define _DRIVERS_LEDS_RGB_LP5860_H + +#include +#include + +#define LP5860_REG_CHIP_EN 0x00 +#define LP5860_REG_DEV_INITIAL 0x01 +#define LP5860_REG_DEV_CONFIG1 0x02 +#define LP5860_REG_DEV_CONFIG2 0x03 +#define LP5860_REG_DEV_CONFIG3 0x04 +#define LP5860_REG_GLOBAL_BRI 0x05 +#define LP5860_REG_GROUP0_BRI 0x06 +#define LP5860_REG_GROUP1_BRI 0x07 +#define LP5860_REG_GROUP2_BRI 0x08 +#define LP5860_REG_R_CURRENT_SET 0x09 +#define LP5860_REG_G_CURRENT_SET 0x0A +#define LP5860_REG_B_CURRENT_SET 0x0B +#define LP5860_REG_GRP_SEL_START 0x0C +#define LP5860_REG_DOT_ONOFF_START 0x43 +#define LP5860_REG_DOT_ONOFF_MAX 0x63 +#define LP5860_REG_FAULT_STATE 0x64 +#define LP5860_REG_DOT_LOD_START 0x65 +#define LP5860_REG_DOT_LSD_START 0x86 +#define LP5860_REG_LOD_CLEAR 0xA7 +#define LP5860_REG_LSD_CLEAR 0xA8 +#define LP5860_REG_RESET 0xA9 +#define LP5860_REG_DC_START 0x0100 +#define LP5860_REG_PWM_BRI_START 0x0200 +#define LP5860_MAX_REG 0x038B + +/* Register chip_enable value */ +#define LP5860_CHIP_SHIFT 0 +#define LP5860_CHIP_MASK BIT(0) +#define LP5860_CHIP_DISABLE false +#define LP5860_CHIP_ENABLE true + +/* Register dev_initial value */ +#define LP5860_MAX_LINE_SHIFT 3 +#define LP5860_MAX_LINE_MASK GENMASK(6, 3) +#define LP5860_MAX_LINE_11 0x0B +#define LP5860_MAX_LINE_10 0x0A +#define LP5860_MAX_LINE_9 0x09 +#define LP5860_MAX_LINE_8 0x08 +#define LP5860_MAX_LINE_7 0x07 +#define LP5860_MAX_LINE_6 0x06 +#define LP5860_MAX_LINE_5 0x05 +#define LP5860_MAX_LINE_4 0x04 +#define LP5860_MAX_LINE_3 0x03 +#define LP5860_MAX_LINE_2 0x02 +#define LP5860_MAX_LINE_1 0x01 + +#define LP5860_MODE_SHIFT 1 +#define LP5860_MODE_MASK GENMASK(2, 1) +#define LP5860_MODE_3_1 0x03 +#define LP5860_MODE_3 0x02 +#define LP5860_MODE_2 0x01 +#define LP5860_MODE_1 0x00 + +#define LP5860_PWM_FREQUENCY_SHIFT 0 +#define LP5860_PWM_FREQUENCY_MASK BIT(0) +#define LP5860_PWM_FREQUENCY_62_5K 0x01 +#define LP5860_PWM_FREQUENCY_125K 0x00 + +/* Register dev_config1 value */ +#define LP5860_SW_BLK_SHIFT 3 +#define LP5860_SW_BLK_MASK BIT(3) +#define LP5860_SW_BLK_05US 0x01 +#define LP5860_SW_BLK_1US 0x00 + +#define LP5860_PWM_SCALE_MODE_SHIFT 2 +#define LP5860_PWM_SCALE_MODE_MASK BIT(2) +#define LP5860_PWM_SCALE_EXPONENTIAL 0x01 +#define LP5860_PWM_SCALE_LINEAR 0x00 + +#define LP5860_PWM_PHASESHIFT_SHIFT 1 +#define LP5860_PWM_PHASESHIFT_MASK BIT(1) +#define LP5860_PWM_PHASESHIFT_ON 0x01 +#define LP5860_PWM_PHASESHIFT_OFF 0x00 + +#define LP5860_CS_ON_SHIFT_SHIFT 0 +#define LP5860_CS_ON_SHIFT_MASK BIT(0) +#define LP5860_CS_DELAY_ON 0x01 +#define LP5860_CS_DELAY_OFF 0x00 + +/* Register dev_config2 value */ +#define LP5860_COMP_GROUP3_SHIFT 6 +#define LP5860_COMP_GROUP3_MASK GENMASK(7, 6) +#define LP5860_COMP_GROUP3_3CLOCK 0x03 +#define LP5860_COMP_GROUP3_2CLOCK 0x02 +#define LP5860_COMP_GROUP3_1CLOCK 0x01 +#define LP5860_COMP_GROUP3_OFF 0x00 + +#define LP5860_COMP_GROUP2_SHIFT 4 +#define LP5860_COMP_GROUP2_MASK GENMASK(5, 4) +#define LP5860_COMP_GROUP2_3CLOCK 0x03 +#define LP5860_COMP_GROUP2_2CLOCK 0x02 +#define LP5860_COMP_GROUP2_1CLOCK 0x01 +#define LP5860_COMP_GROUP2_OFF 0x00 + +#define LP5860_COMP_GROUP1_SHIFT 2 +#define LP5860_COMP_GROUP1_MASK GENMASK(3, 2) +#define LP5860_COMP_GROUP1_3CLOCK 0x03 +#define LP5860_COMP_GROUP1_2CLOCK 0x02 +#define LP5860_COMP_GROUP1_1CLOCK 0x01 +#define LP5860_COMP_GROUP1_OFF 0x00 + +#define LP5860_LOD_REMOVAL_SHIFT 1 +#define LP5860_LOD_REMOVAL_MASK BIT(1) +#define LP5860_LOD_REMOVAL_EN 0x01 +#define LP5860_LOD_REMOVAL_OFF 0x00 + +#define LP5860_LSD_REMOVAL_SHIFT 0 +#define LP5860_LSD_REMOVAL_MASK BIT(0) +#define LP5860_LSD_REMOVAL_EN 0x01 +#define LP5860_LSD_REMOVAL_OFF 0x00 + +/* Register dev_config3 value */ +#define LP5860_DOWN_DEGHOST_SHIFT 6 +#define LP5860_DOWN_DEGHOST_MASK GENMASK(7, 6) +#define LP5860_DOWN_DEGHOST_STRONG 0x03 +#define LP5860_DOWN_DEGHOST_MEDIUM 0x02 +#define LP5860_DOWN_DEGHOST_WEAK 0x01 +#define LP5860_DOWN_DEGHOST_OFF 0x00 + +#define LP5860_UP_DEGHOST_SHIFT 4 +#define LP5860_UP_DEGHOST_MASK GENMASK(5, 4) +#define LP5860_UP_DEGHOST_GND 0x03 +#define LP5860_UP_DEGHOST_3 0x02 +#define LP5860_UP_DEGHOST_2_5 0x01 +#define LP5860_UP_DEGHOST_2 0x00 + +#define LP5860_MAXIMUM_CURRENT_SHIFT 1 +#define LP5860_MAXIMUM_CURRENT_MASK GENMASK(3, 1) +#define LP5860_MAXIMUM_CURRENT_50 0x07 +#define LP5860_MAXIMUM_CURRENT_40 0x06 +#define LP5860_MAXIMUM_CURRENT_30 0x05 +#define LP5860_MAXIMUM_CURRENT_20 0x04 +#define LP5860_MAXIMUM_CURRENT_15 0x03 +#define LP5860_MAXIMUM_CURRENT_10 0x02 +#define LP5860_MAXIMUM_CURRENT_5 0x01 +#define LP5860_MAXIMUM_CURRENT_3 0x00 + +#define LP5860_UP_DEGHOST_ENABLE_SHIFT 0 +#define LP5860_UP_DEGHOST_ENABLE_MASK BIT(0) +#define LP5860_UP_DEGHOST_ENABLE_EN 0x01 +#define LP5860_UP_DEGHOST_ENABLE_OFF 0x00 + +/* Register PWM */ +#define LP5860_PWM_GLOBAL_MAX 0xff +#define LP5860_PWM_GROUP_MAX 0xff + +/* Register CC group select */ +#define LP5860_CC_GROUP_MASK GENMASK(7, 0) +#define LP5860_CC_GROUP_MAX 0x7F + +/* Register dot group select */ +#define LP5860_DOT_0_SHIFT 0 +#define LP5860_DOT_1_SHIFT 2 +#define LP5860_DOT_2_SHIFT 4 +#define LP5860_DOT_3_SHIFT 6 + +#define LP5860_DOT_GROUP3 0x03 +#define LP5860_DOT_GROUP2 0x02 +#define LP5860_DOT_GROUP1 0x01 +#define LP5860_DOT_GROUP_NONE 0x00 + +#define LP5860_DOT_ALL_ON 0xff +#define LP5860_DOT_ALL_OFF 0x0 +#define LP5860_PWM_DOT_MAX 0xff +/* Dot onoff value */ +#define LP5860_DOT_CS0_SHIFT 0 +#define LP5860_DOT_CS1_SHIFT 1 +#define LP5860_DOT_CS2_SHIFT 2 +#define LP5860_DOT_CS3_SHIFT 3 +#define LP5860_DOT_CS4_SHIFT 4 +#define LP5860_DOT_CS5_SHIFT 5 +#define LP5860_DOT_CS6_SHIFT 6 +#define LP5860_DOT_CS7_SHIFT 7 + +#define LP5860_DOT_CS_ON 0x01 +#define LP5860_DOT_CS_OFF 0x00 + +/* Dot lod value */ +#define LP5860_DOT_LOD0_SHIFT 0 +#define LP5860_DOT_LOD1_SHIFT 1 +#define LP5860_DOT_LOD2_SHIFT 2 +#define LP5860_DOT_LOD3_SHIFT 3 +#define LP5860_DOT_LOD4_SHIFT 4 +#define LP5860_DOT_LOD5_SHIFT 5 +#define LP5860_DOT_LOD6_SHIFT 6 +#define LP5860_DOT_LOD7_SHIFT 7 + +#define LP5860_DOT_LOD_ON 0x01 +#define LP5860_DOT_LOD_OFF 0x00 + +/* dot lsd value */ +#define LP5860_DOT_LSD0_SHIFT 0 +#define LP5860_DOT_LSD1_SHIFT 1 +#define LP5860_DOT_LSD2_SHIFT 2 +#define LP5860_DOT_LSD3_SHIFT 3 +#define LP5860_DOT_LSD4_SHIFT 4 +#define LP5860_DOT_LSD5_SHIFT 5 +#define LP5860_DOT_LSD6_SHIFT 6 +#define LP5860_DOT_LSD7_SHIFT 7 + +#define LP5860_DOT_LSD_ON 0x01 +#define LP5860_DOT_LSD_OFF 0x00 + +/* Register lod state */ +#define LP5860_GLOBAL_LOD_SHIFT 1 +#define LP5860_GLOBAL_LOD_STATE BIT(1) +#define LP5860_GLOBAL_LSD_SHIFT 0 +#define LP5860_GLOBAL_LSD_STATE BIT(0) + +#define LP5860_FAULT_STATE_ON 0x01 +#define LP5860_FAULT_STATE_OFF 0x00 + +#define LP5860_GLOBAL_LOD_CLEAR 0x00 +#define LP5860_GLOBAL_LSD_CLEAR 0x00 + + +#define LP5860_LOD_CLEAR_EN 0xff +#define LP5860_LSD_CLEAR_EN 0xff +#define LP5860_RESET_EN 0xff + +#define LP5860_MAX_BRIGHTNESS 255 +#define LP5860_REG_R_PWM 0x0 +#define LP5860_REG_G_PWM 0x1 +#define LP5860_REG_B_PWM 0x2 + +#define LP5860_MAX_LED_CONSTANT 18 +#define LP5860_MAX_LED_SCAN 11 +#define LP5860_MAX_LED (LP5860_MAX_LED_CONSTANT * LP5860_MAX_LED_SCAN) + +#define LP5860_MAX_DOT_ONOFF_GROUP_NUM 8 + +/* + * Theoretically, there is no max channel per LED, + * limit this to a reasonable value for RGBW LEDs + */ +#define LP5860_MAX_LED_CHANNELS 4 + +struct lp5860_led { + struct lp5860 *chip; + struct led_classdev_mc mc_cdev; + u8 brightness; +}; + +struct lp5860 { + struct device *dev; + struct regmap *regmap; + struct mutex lock; + + DECLARE_FLEX_ARRAY(struct lp5860_led, leds); +}; + +int lp5860_device_init(struct device *dev); +void lp5860_device_remove(struct device *dev); + +#endif /* _DRIVERS_LEDS_RGB_LP5860_H */ From a6ec318b16f8e6b91867d0cfce22cb8b8b665eeb Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 5 May 2026 12:28:45 +0200 Subject: [PATCH 13/30] leds: bcm63138/cros_ec: Move MODULE_DEVICE_TABLE next to the table itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By convention MODULE_DEVICE_TABLE() immediately follows the ID table it exports, because this is easier to read and verify. It also makes more sense since #ifdef for ACPI or OF could hide both of them. Most of the privers already have this correctly placed, so adjust the missing ones. No functional impact. Signed-off-by: Krzysztof Kozlowski Acked-by: Thomas Weißschuh # leds-cros_ec.c Reviewed-by: Tzung-Bi Shih Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260505102846.186219-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Lee Jones --- drivers/leds/blink/leds-bcm63138.c | 2 +- drivers/leds/leds-cros_ec.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c index ef2e511438cc..45c0662df933 100644 --- a/drivers/leds/blink/leds-bcm63138.c +++ b/drivers/leds/blink/leds-bcm63138.c @@ -296,6 +296,7 @@ static const struct of_device_id bcm63138_leds_of_match_table[] = { { .compatible = "brcm,bcm63138-leds", }, { }, }; +MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); static struct platform_driver bcm63138_leds_driver = { .probe = bcm63138_leds_probe, @@ -310,4 +311,3 @@ module_platform_driver(bcm63138_leds_driver); MODULE_AUTHOR("Rafał Miłecki"); MODULE_DESCRIPTION("Broadcom BCM63138 SoC LED driver"); MODULE_LICENSE("GPL"); -MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c index bea3cc3fbfd2..6592ceee866a 100644 --- a/drivers/leds/leds-cros_ec.c +++ b/drivers/leds/leds-cros_ec.c @@ -249,6 +249,7 @@ static const struct platform_device_id cros_ec_led_id[] = { { "cros-ec-led", 0 }, {} }; +MODULE_DEVICE_TABLE(platform, cros_ec_led_id); static struct platform_driver cros_ec_led_driver = { .driver.name = "cros-ec-led", @@ -257,7 +258,6 @@ static struct platform_driver cros_ec_led_driver = { }; module_platform_driver(cros_ec_led_driver); -MODULE_DEVICE_TABLE(platform, cros_ec_led_id); MODULE_DESCRIPTION("ChromeOS EC LED Driver"); MODULE_AUTHOR("Thomas Weißschuh Date: Wed, 6 May 2026 09:48:42 +0300 Subject: [PATCH 14/30] dt-bindings: leds: Document TI LM3560 Synchronous Boost Flash Driver Document TI LM3559 and LM3560 Synchronous Boost Flash Driver used for camera flash LEDs. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260506064847.37795-2-clamor95@gmail.com Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/ti,lm3560.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/ti,lm3560.yaml diff --git a/Documentation/devicetree/bindings/leds/ti,lm3560.yaml b/Documentation/devicetree/bindings/leds/ti,lm3560.yaml new file mode 100644 index 000000000000..6cf8cf91ab2e --- /dev/null +++ b/Documentation/devicetree/bindings/leds/ti,lm3560.yaml @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/ti,lm3560.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI LM3560 Synchronous Boost Flash Driver + +maintainers: + - Svyatoslav Ryhel + +description: + The LM3560 is a 2-MHz fixed frequency synchronous boost converter with two + 1000-mA constant current drivers for high-current white LEDs. The dual high- + side current sources allow for grounded cathode LED operation and can be + tied together for providing flash currents at up to 2 A through a single LED. + An adaptive regulation method ensures the current for each LED remains in + regulation and maximizes efficiency. + +properties: + compatible: + enum: + - ti,lm3559 + - ti,lm3560 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + enable-gpios: + description: GPIO connected to the HWEN pin. + maxItems: 1 + + vin-supply: + description: Supply connected to the IN line. + + flash-max-timeout-us: + minimum: 32000 + maximum: 1024000 + default: 32000 + + ti,peak-current-microamp: + description: + The LM3560 features 4 selectable current limits 1.6A, 2.3A, 3A, and 3.6A + (in case of LM3559 - 1.4A, 2.1A, 2.7A, and 3.2A). When the current limit + is reached, the LM3559/LM3560 stops switching for the remainder of the + switching cycle. + +patternProperties: + '^led@[01]$': + type: object + $ref: /schemas/leds/common.yaml# + description: LED control bank nodes. + unevaluatedProperties: false + + properties: + reg: + description: Control bank selection (0 = bank A, 1 = bank B). + maximum: 1 + + required: + - reg + - flash-max-microamp + - led-max-microamp + +allOf: + - $ref: /schemas/leds/common.yaml# + - if: + properties: + compatible: + contains: + const: ti,lm3559 + then: + properties: + ti,peak-current-microamp: + enum: [1400000, 2100000, 2700000, 3200000] + default: 1400000 + patternProperties: + '^led@[01]$': + properties: + flash-max-microamp: + minimum: 56250 + maximum: 900000 + led-max-microamp: + minimum: 28125 + maximum: 225000 + + - if: + properties: + compatible: + contains: + const: ti,lm3560 + then: + properties: + ti,peak-current-microamp: + enum: [1600000, 2300000, 3000000, 3600000] + default: 1600000 + patternProperties: + '^led@[01]$': + properties: + flash-max-microamp: + minimum: 62500 + maximum: 1000000 + led-max-microamp: + minimum: 31250 + maximum: 250000 + +required: + - compatible + - reg + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@53 { + compatible = "ti,lm3560"; + reg = <0x53>; + + enable-gpios = <&gpio 28 GPIO_ACTIVE_HIGH>; + vin-supply = <&vdd_3v3_sys>; + + flash-max-timeout-us = <1024000>; + ti,peak-current-microamp = <1600000>; + + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + + function = LED_FUNCTION_FLASH; + color = ; + + flash-max-microamp = <562500>; + led-max-microamp = <156250>; + }; + + led@1 { + reg = <1>; + + function = LED_FUNCTION_FLASH; + color = ; + + flash-max-microamp = <562500>; + led-max-microamp = <156250>; + }; + }; + }; From 2682d6308654850ee4d4f9b3bbe3a31417890594 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 9 May 2026 23:46:03 +0200 Subject: [PATCH 15/30] leds: Introduce the multi_max_intensity sysfs attribute Some multicolor LEDs support global brightness control in hardware, meaning that the maximum intensity of the color components is not connected to the maximum global brightness. Such LEDs cannot be described properly by the current multicolor LED class interface, because it assumes that the maximum intensity of each color component is described by the maximum global brightness of the LED. Fix this by introducing a new sysfs attribute called "multi_max_intensity" holding the maximum intensity values for the color components of a multicolor LED class device. Drivers can use the new max_intensity field inside struct mc_subled to tell the multicolor LED class code about those values. Intensity values written by userspace applications will be limited to this maximum value. Drivers for multicolor LEDs that do not support global brightness control in hardware might still want to use the maximum global LED brightness supplied via devicetree as the maximum intensity of each individual color component. Such drivers should set max_intensity to 0 so that the multicolor LED core can act accordingly. The lp50xx and ncp5623 LED drivers already use hardware-based control for the global LED brightness. Modify those drivers to correctly initalize .max_intensity to avoid being limited to the maximum global brightness supplied via devicetree. Reviewed-by: Werner Sembach Reviewed-by: Jacek Anaszewski Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260509214603.262368-2-W_Armin@gmx.de Signed-off-by: Lee Jones --- .../ABI/testing/sysfs-class-led-multicolor | 19 ++++++-- Documentation/leds/leds-class-multicolor.rst | 21 ++++++++- drivers/leds/led-class-multicolor.c | 47 ++++++++++++++++++- drivers/leds/leds-lp50xx.c | 1 + drivers/leds/rgb/leds-ncp5623.c | 4 +- include/linux/led-class-multicolor.h | 30 +++++++++++- 6 files changed, 113 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor b/Documentation/ABI/testing/sysfs-class-led-multicolor index 16fc827b10cb..197da3e775b4 100644 --- a/Documentation/ABI/testing/sysfs-class-led-multicolor +++ b/Documentation/ABI/testing/sysfs-class-led-multicolor @@ -16,9 +16,22 @@ Date: March 2020 KernelVersion: 5.9 Contact: Dan Murphy Description: read/write - This file contains array of integers. Order of components is - described by the multi_index array. The maximum intensity should - not exceed /sys/class/leds//max_brightness. + This file contains an array of integers. The order of components + is described by the multi_index array. The maximum intensity value + supported by each color component is described by the multi_max_intensity + file. Writing intensity values larger than the maximum value of a + given color component will result in those values being clamped. + + For additional details please refer to + Documentation/leds/leds-class-multicolor.rst. + +What: /sys/class/leds//multi_max_intensity +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf +Description: read + This file contains an array of integers describing the maximum + intensity value for each intensity component. For additional details please refer to Documentation/leds/leds-class-multicolor.rst. diff --git a/Documentation/leds/leds-class-multicolor.rst b/Documentation/leds/leds-class-multicolor.rst index c6b47b4093c4..68340644f80b 100644 --- a/Documentation/leds/leds-class-multicolor.rst +++ b/Documentation/leds/leds-class-multicolor.rst @@ -25,10 +25,14 @@ color name to indexed value. The ``multi_index`` file is an array that contains the string list of the colors as they are defined in each ``multi_*`` array file. -The ``multi_intensity`` is an array that can be read or written to for the +The ``multi_intensity`` file is an array that can be read or written to for the individual color intensities. All elements within this array must be written in order for the color LED intensities to be updated. +The ``multi_max_intensity`` file is an array that contains the maximum intensity +value supported by each color intensity. Intensity values above this will be +automatically clamped into the supported range. + Directory Layout Example ======================== .. code-block:: console @@ -38,6 +42,7 @@ Directory Layout Example -r--r--r-- 1 root root 4096 Oct 19 16:16 max_brightness -r--r--r-- 1 root root 4096 Oct 19 16:16 multi_index -rw-r--r-- 1 root root 4096 Oct 19 16:16 multi_intensity + -r--r--r-- 1 root root 4096 Oct 19 16:16 multi_max_intensity .. @@ -104,3 +109,17 @@ the color LED group. 128 .. + +Writing intensity values larger than the maximum specified in ``multi_max_intensity`` +will result in those values being clamped into the supported range. + +.. code-block:: console + + # cat /sys/class/leds/multicolor:status/multi_max_intensity + 255 255 255 + + # echo 512 512 512 > /sys/class/leds/multicolor:status/multi_intensity + # cat /sys/class/leds/multicolor:status/multi_intensity + 255 255 255 + +.. diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c index 6b671f3f9c61..59d46f8a90d5 100644 --- a/drivers/leds/led-class-multicolor.c +++ b/drivers/leds/led-class-multicolor.c @@ -7,10 +7,29 @@ #include #include #include +#include #include #include #include +static unsigned int led_mc_get_max_intensity(struct led_classdev_mc *mcled_cdev, size_t index) +{ + unsigned int max_intensity; + + /* + * The maximum global brightness value might still be changed by + * led_classdev_register_ext() using devicetree properties. This + * prevents us from changing subled_info[X].max_intensity when + * registering a multicolor LED class device, so we have to do + * this during runtime. + */ + max_intensity = mcled_cdev->subled_info[index].max_intensity; + if (max_intensity) + return max_intensity; + + return mcled_cdev->led_cdev.max_brightness; +} + int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, enum led_brightness brightness) { @@ -27,6 +46,26 @@ int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, } EXPORT_SYMBOL_GPL(led_mc_calc_color_components); +static ssize_t multi_max_intensity_show(struct device *dev, + struct device_attribute *intensity_attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); + unsigned int max_intensity; + int len = 0; + + for (int i = 0; i < mcled_cdev->num_colors; i++) { + max_intensity = led_mc_get_max_intensity(mcled_cdev, i); + len += sysfs_emit_at(buf, len, "%u", max_intensity); + if (i < mcled_cdev->num_colors - 1) + len += sprintf(buf + len, " "); + } + + buf[len++] = '\n'; + return len; +} +static DEVICE_ATTR_RO(multi_max_intensity); + static ssize_t multi_intensity_store(struct device *dev, struct device_attribute *intensity_attr, const char *buf, size_t size) @@ -35,6 +74,7 @@ static ssize_t multi_intensity_store(struct device *dev, struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); int nrchars, offset = 0; unsigned int intensity_value[LED_COLOR_ID_MAX]; + unsigned int max_intensity; int i; ssize_t ret; @@ -56,8 +96,10 @@ static ssize_t multi_intensity_store(struct device *dev, goto err_out; } - for (i = 0; i < mcled_cdev->num_colors; i++) - mcled_cdev->subled_info[i].intensity = intensity_value[i]; + for (int i = 0; i < mcled_cdev->num_colors; i++) { + max_intensity = led_mc_get_max_intensity(mcled_cdev, i); + mcled_cdev->subled_info[i].intensity = min(intensity_value[i], max_intensity); + } if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags)) led_set_brightness(led_cdev, led_cdev->brightness); @@ -111,6 +153,7 @@ static ssize_t multi_index_show(struct device *dev, static DEVICE_ATTR_RO(multi_index); static struct attribute *led_multicolor_attrs[] = { + &dev_attr_multi_max_intensity.attr, &dev_attr_multi_intensity.attr, &dev_attr_multi_index.attr, NULL, diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index e2a9c8592953..69c3550f1a31 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -525,6 +525,7 @@ static int lp50xx_probe_dt(struct lp50xx *priv) } mc_led_info[multi_index].color_index = color_id; + mc_led_info[multi_index].max_intensity = 255; num_colors++; } diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c index 85d6be6fff2b..f2528f06507d 100644 --- a/drivers/leds/rgb/leds-ncp5623.c +++ b/drivers/leds/rgb/leds-ncp5623.c @@ -56,8 +56,7 @@ static int ncp5623_brightness_set(struct led_classdev *cdev, for (int i = 0; i < mc_cdev->num_colors; i++) { ret = ncp5623_write(ncp->client, NCP5623_PWM_REG(mc_cdev->subled_info[i].channel), - min(mc_cdev->subled_info[i].intensity, - NCP5623_MAX_BRIGHTNESS)); + mc_cdev->subled_info[i].intensity); if (ret) return ret; } @@ -190,6 +189,7 @@ static int ncp5623_probe(struct i2c_client *client) goto release_led_node; subled_info[ncp->mc_dev.num_colors].channel = reg; + subled_info[ncp->mc_dev.num_colors].max_intensity = NCP5623_MAX_BRIGHTNESS; subled_info[ncp->mc_dev.num_colors++].color_index = color_index; } diff --git a/include/linux/led-class-multicolor.h b/include/linux/led-class-multicolor.h index db9f34c6736e..8a05836cae25 100644 --- a/include/linux/led-class-multicolor.h +++ b/include/linux/led-class-multicolor.h @@ -9,10 +9,31 @@ #include #include +/** + * struct mc_subled - Color component description. + * @color_index: Color ID. + * @brightness: Scaled intensity. + * @intensity: Current intensity. + * @max_intensity: Maximum supported intensity value. + * @channel: Channel index. + * + * Describes a color component of a multicolor LED. Many multicolor LEDs + * do not support global brightness control in hardware, so they use + * the brightness field in connection with led_mc_calc_color_components() + * to perform the intensity scaling in software. + * Such drivers should set max_intensity to 0 to signal the multicolor LED core + * that the maximum global brightness of the LED class device should be used for + * limiting incoming intensity values. + * + * Multicolor LEDs that do support global brightness control in hardware + * should instead set max_intensity to the maximum intensity value supported + * by the hardware for a given color component. + */ struct mc_subled { unsigned int color_index; unsigned int brightness; unsigned int intensity; + unsigned int max_intensity; unsigned int channel; }; @@ -53,7 +74,14 @@ int led_classdev_multicolor_register_ext(struct device *parent, */ void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev); -/* Calculate brightness for the monochrome LED cluster */ +/** + * led_mc_calc_color_components() - Calculates component brightness values of a LED cluster. + * @mcled_cdev - Multicolor LED class device of the LED cluster. + * @brightness - Global brightness of the LED cluster. + * + * Calculates the brightness values for each color component of a monochrome LED cluster, + * see Documentation/leds/leds-class-multicolor.rst for details. + */ int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, enum led_brightness brightness); From d0316c89636d72a8a6766e82453570f7bd003b21 Mon Sep 17 00:00:00 2001 From: Stepan Ionichev Date: Sun, 10 May 2026 05:36:32 +0500 Subject: [PATCH 16/30] leds: dac124s085: Declare SPI command word as __le16 dac124s085_set_brightness() builds a 16-bit SPI command word: u16 word; ... word = cpu_to_le16(((led->id) << 14) | REG_WRITE_UPDATE | (brightness & 0xfff)); ret = spi_write(led->spi, (const u8 *)&word, sizeof(word)); cpu_to_le16() returns __le16, but the local 'word' is declared as plain u16, which sparse flags: drivers/leds/leds-dac124s085.c:42:14: warning: incorrect type in assignment (different base types) The bytes that hit the wire are correct because cpu_to_le16() does the right thing on either endianness, but mixing the annotated and unannotated types defeats sparse's __bitwise checking and would let a future reader treat the buffer as a host-endian u16 by mistake. Declare 'word' as __le16 to match how it is built and consumed. No functional change. Signed-off-by: Stepan Ionichev Link: https://patch.msgid.link/20260510003632.35942-1-sozdayvek@gmail.com Signed-off-by: Lee Jones --- drivers/leds/leds-dac124s085.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c index cf5fb1195f87..192b43333aee 100644 --- a/drivers/leds/leds-dac124s085.c +++ b/drivers/leds/leds-dac124s085.c @@ -35,7 +35,7 @@ static int dac124s085_set_brightness(struct led_classdev *ldev, { struct dac124s085_led *led = container_of(ldev, struct dac124s085_led, ldev); - u16 word; + __le16 word; int ret; mutex_lock(&led->mutex); From 6ebff754c46e2277c831670fa1f0b5043250eb77 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 14 May 2026 00:05:24 +0200 Subject: [PATCH 17/30] leds: Adjust documentation of brightness sysfs node Adjust documentation of brightness sysfs node about accepted value range. The code accepts only decimal values. We may not relax that due to different readings for, e.g., octal 0100, which becomes 64 instead of currently parsed 100. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260513220620.369825-1-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- Documentation/ABI/testing/sysfs-class-led | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led index 0313b82644f2..d4c918cc11a1 100644 --- a/Documentation/ABI/testing/sysfs-class-led +++ b/Documentation/ABI/testing/sysfs-class-led @@ -22,8 +22,8 @@ Description: For additional details please refer to Documentation/leds/leds-class-multicolor.rst. - The value is between 0 and - /sys/class/leds//max_brightness. + The value is between 0 and /sys/class/leds//max_brightness + and is represented by as a decimal. Writing 0 to this file clears active trigger. From f92135f100669b508dd62b424ab20bcb33494c79 Mon Sep 17 00:00:00 2001 From: Craig McQueen Date: Thu, 23 Apr 2026 21:36:38 +1000 Subject: [PATCH 18/30] leds: core: Fix race condition for software blink led_set_brightness() function: Change handling of software blink to avoid race conditions when stopping blink and setting brightness. Triggers may call led_set_brightness(LED_OFF), led_set_brightness(LED_FULL) in quick succession to disable blinking and turn the LED on. If the delayed work task has not yet disabled blinking by the time the second call occurs, then the brightness also needs to be changed in the delayed work task. Signed-off-by: Craig McQueen Link: https://patch.msgid.link/20260423113638.2079302-1-craig@mcqueen.au Signed-off-by: Lee Jones --- drivers/leds/led-core.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 385e78af1dac..073c547068cc 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -303,24 +303,31 @@ EXPORT_SYMBOL_GPL(led_stop_software_blink); void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness) { - /* - * If software blink is active, delay brightness setting - * until the next timer tick. - */ - if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { + if (brightness) { + /* + * If software blink disable is pending, also queue brightness setting. + * If software blink is active, delay brightness setting + * until the next timer tick. + */ + if (test_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags) || + test_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { + led_cdev->delayed_set_value = brightness; + set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); + return; + } else if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { + led_cdev->new_blink_brightness = brightness; + set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags); + return; + } + } else if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { /* * If we need to disable soft blinking delegate this to the * work queue task to avoid problems in case we are called * from hard irq context. */ - if (!brightness) { - set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); - queue_work(led_cdev->wq, &led_cdev->set_brightness_work); - } else { - set_bit(LED_BLINK_BRIGHTNESS_CHANGE, - &led_cdev->work_flags); - led_cdev->new_blink_brightness = brightness; - } + set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); return; } From 38569c751ccfa8a37ce4724b12548ae5fa9e7f57 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Sat, 16 May 2026 03:08:33 +0530 Subject: [PATCH 19/30] dt-bindings: leds: Document Samsung S2M series PMIC flash LED device Certain Samsung S2M series PMICs have a flash LED controller with two LED channels, and with torch and flash control modes. Document the devicetree schema for the device. Signed-off-by: Kaustabh Chakraborty Acked-by: Conor Dooley Link: https://patch.msgid.link/20260516-s2mu005-pmic-v7-1-73f9702fb461@disroot.org Signed-off-by: Lee Jones --- .../bindings/leds/samsung,s2mu005-flash.yaml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml diff --git a/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml b/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml new file mode 100644 index 000000000000..36051ab20509 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/samsung,s2mu005-flash.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Flash and Torch LED Controller for Samsung S2M series PMICs + +maintainers: + - Kaustabh Chakraborty + +description: | + The Samsung S2M series PMIC flash LED has two led channels (typically + as back and front camera flashes), with support for both torch and + flash modes. + + This is a part of device tree bindings for S2M and S5M family of Power + Management IC (PMIC). + + See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for + additional information and example. + +properties: + compatible: + enum: + - samsung,s2mu005-flash + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-1]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + enum: [0, 1] + + required: + - reg + +required: + - compatible + - "#address-cells" + - "#size-cells" + +additionalProperties: false From 02149db273a936703764c4b5173e533817180d25 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Sat, 16 May 2026 03:08:38 +0530 Subject: [PATCH 20/30] leds: flash: Add support for Samsung S2M series PMIC flash LED device Add support for flash LEDs found in certain Samsung S2M series PMICs. The device has two channels for LEDs, typically for the back and front cameras in mobile devices. Both channels can be independently controlled, and can be operated in torch or flash modes. The driver includes initial support for the S2MU005 PMIC flash LEDs. Signed-off-by: Kaustabh Chakraborty Link: https://patch.msgid.link/20260516-s2mu005-pmic-v7-6-73f9702fb461@disroot.org Signed-off-by: Lee Jones --- drivers/leds/flash/Kconfig | 11 + drivers/leds/flash/Makefile | 1 + drivers/leds/flash/leds-s2m-flash.c | 350 ++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 drivers/leds/flash/leds-s2m-flash.c diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig index 31c996651f2a..9b6dd3f1ffe8 100644 --- a/drivers/leds/flash/Kconfig +++ b/drivers/leds/flash/Kconfig @@ -114,6 +114,17 @@ config LEDS_RT8515 To compile this driver as a module, choose M here: the module will be called leds-rt8515. +config LEDS_S2M_FLASH + tristate "Samsung S2M series PMICs flash/torch LED support" + depends on LEDS_CLASS + depends on MFD_SEC_CORE + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS + help + This option enables support for the flash/torch LEDs found in certain + Samsung S2M series PMICs, such as the S2MU005. It has a LED channel + dedicated for every physical LED. The LEDs can be controlled in flash + and torch modes. + config LEDS_SGM3140 tristate "LED support for the SGM3140" depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile index 712fb737a428..44e6c1b4beb3 100644 --- a/drivers/leds/flash/Makefile +++ b/drivers/leds/flash/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o +obj-$(CONFIG_LEDS_S2M_FLASH) += leds-s2m-flash.o obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o diff --git a/drivers/leds/flash/leds-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c new file mode 100644 index 000000000000..6ee8db094611 --- /dev/null +++ b/drivers/leds/flash/leds-s2m-flash.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flash and Torch LED Driver for Samsung S2M series PMICs. + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * Copyright (c) 2026 Kaustabh Chakraborty + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CHANNELS 2 + +struct s2m_led { + struct regmap *regmap; + struct led_classdev_flash fled; + struct v4l2_flash *v4l2_flash; + /* + * The mutex object prevents the concurrent access of flash control + * registers by the LED and V4L2 subsystems. + */ + struct mutex lock; + unsigned int reg_enable; + u8 channel; + u8 flash_brightness; + u8 flash_timeout; +}; + +static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled) +{ + return container_of(fled, struct s2m_led, fled); +} + +static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev) +{ + return container_of(cdev, struct led_classdev_flash, led_cdev); +} + +static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness) +{ + struct s2m_led *led = to_s2m_led(fled); + struct led_flash_setting *setting = &fled->brightness; + + mutex_lock(&led->lock); + led->flash_brightness = (brightness - setting->min) / setting->step; + mutex_unlock(&led->lock); + + return 0; +} + +static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout) +{ + struct s2m_led *led = to_s2m_led(fled); + struct led_flash_setting *setting = &fled->timeout; + + mutex_lock(&led->lock); + led->flash_timeout = (timeout - setting->min) / setting->step; + mutex_unlock(&led->lock); + + return 0; +} + +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) +static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable) +{ + struct s2m_led *led = to_s2m_led(v4l2_flash->fled_cdev); + + return led->fled.ops->strobe_set(&led->fled, enable); +} + +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = { + .external_strobe_set = s2m_fled_flash_external_strobe_set, +}; +#else +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops; +#endif + +static void s2m_fled_v4l2_flash_release(void *v4l2_flash) +{ + v4l2_flash_release(v4l2_flash); +} + +static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value) +{ + struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev)); + int ret; + + mutex_lock(&led->lock); + + if (!value) { + ret = regmap_clear_bits(led->regmap, led->reg_enable, + S2MU005_FLED_TORCH_EN(led->channel)); + if (ret) + dev_err(cdev->dev, "failed to disable torch LED\n"); + goto unlock; + } + + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel), + S2MU005_FLED_TORCH_IOUT, + FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1)); + if (ret) { + dev_err(cdev->dev, "failed to set torch current\n"); + goto unlock; + } + + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel)); + if (ret) { + dev_err(cdev->dev, "failed to enable torch LED\n"); + goto unlock; + } + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state) +{ + struct s2m_led *led = to_s2m_led(fled); + int ret; + + mutex_lock(&led->lock); + + ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to disable flash LED\n"); + goto unlock; + } + + if (!state) + goto unlock; + + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel), + S2MU005_FLED_FLASH_IOUT, + FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to set flash brightness\n"); + goto unlock; + } + + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel), + S2MU005_FLED_FLASH_TIMEOUT, + FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to set flash timeout\n"); + goto unlock; + } + + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to enable flash LED\n"); + goto unlock; + } + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state) +{ + struct s2m_led *led = to_s2m_led(fled); + u32 val; + int ret; + + mutex_lock(&led->lock); + + ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to fetch LED status\n"); + goto unlock; + } + + *state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel)); + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +static const struct led_flash_ops s2mu005_fled_flash_ops = { + .flash_brightness_set = s2m_fled_flash_brightness_set, + .timeout_set = s2m_fled_flash_timeout_set, + .strobe_set = s2mu005_fled_flash_strobe_set, + .strobe_get = s2mu005_fled_flash_strobe_get, +}; + +static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap, + unsigned int nr_channels) +{ + unsigned int val; + int ret; + + ret = regmap_read(regmap, S2MU005_REG_ID, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read revision\n"); + + for (int i = 0; i < nr_channels; i++) { + /* + * Read the revision register. Revision EVT0 has the register + * at CTRL4, while EVT1 and higher have it at CTRL6. + */ + if (FIELD_GET(S2MU005_ID_MASK, val) == 0) + led[i].reg_enable = S2MU005_REG_FLED_CTRL4; + else + led[i].reg_enable = S2MU005_REG_FLED_CTRL6; + } + + /* Enable the LED channels. */ + ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN); + if (ret) + return dev_err_probe(dev, ret, "failed to enable LED channels\n"); + + return 0; +} + +static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev, + struct fwnode_handle *fwnp) +{ + struct led_classdev *cdev = &led->fled.led_cdev; + struct led_init_data init_data = {}; + struct v4l2_flash_config v4l2_cfg = {}; + int ret; + + cdev->max_brightness = 16; + cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set; + cdev->flags |= LED_DEV_CAP_FLASH; + + led->fled.timeout.min = 62000; + led->fled.timeout.step = 62000; + led->fled.timeout.max = 992000; + led->fled.timeout.val = 992000; + + led->fled.brightness.min = 25000; + led->fled.brightness.step = 25000; + led->fled.brightness.max = 375000; /* 400000 causes flickering */ + led->fled.brightness.val = 375000; + + s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val); + s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val); + + led->fled.ops = &s2mu005_fled_flash_ops; + + init_data.fwnode = fwnp; + ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data); + if (ret) + return dev_err_probe(dev, ret, "failed to create LED flash device\n"); + + v4l2_cfg.intensity.min = led->fled.brightness.min; + v4l2_cfg.intensity.step = led->fled.brightness.step; + v4l2_cfg.intensity.max = led->fled.brightness.max; + v4l2_cfg.intensity.val = led->fled.brightness.val; + + v4l2_cfg.has_external_strobe = true; + + led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops, + &v4l2_cfg); + if (IS_ERR(led->v4l2_flash)) + return dev_err_probe(dev, PTR_ERR(led->v4l2_flash), + "failed to create V4L2 flash device\n"); + + ret = devm_add_action_or_reset(dev, s2m_fled_v4l2_flash_release, led->v4l2_flash); + if (ret) + return dev_err_probe(dev, ret, "failed to add cleanup action\n"); + + return 0; +} + +static int s2m_fled_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent); + struct s2m_led *led; + int ret; + + led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL); + if (!led) + return -ENOMEM; + + /* Initialize LED controller with variant-specific implementation. */ + ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS); + if (ret) + return ret; + + device_for_each_child_node_scoped(dev, child) { + u32 reg; + + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + + if (reg >= MAX_CHANNELS) { + dev_warn(dev, "channel %d is non-existent\n", reg); + continue; + } + + if (led[reg].regmap) { + dev_warn(dev, "duplicate node for channel %d\n", reg); + continue; + } + + led[reg].regmap = ddata->regmap_pmic; + led[reg].channel = (u8)reg; + + ret = devm_mutex_init(dev, &led[reg].lock); + if (ret) + return dev_err_probe(dev, ret, "failed to create mutex\n"); + + /* Initialize LED channel with variant-specific implementation. */ + ret = s2mu005_fled_init_channel(led + reg, dev, child); + if (ret) + return ret; + } + + return 0; +} + +static const struct platform_device_id s2m_fled_id_table[] = { + { "s2mu005-flash", S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, s2m_fled_id_table); + +static const struct of_device_id s2m_fled_of_match_table[] = { + { .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table); + +static struct platform_driver s2m_fled_driver = { + .driver = { + .name = "s2m-flash", + }, + .probe = s2m_fled_probe, + .id_table = s2m_fled_id_table, +}; +module_platform_driver(s2m_fled_driver); + +MODULE_DESCRIPTION("Flash/Torch LED Driver for Samsung S2M Series PMICs"); +MODULE_AUTHOR("Kaustabh Chakraborty "); +MODULE_LICENSE("GPL"); From 63ccd117f4257c66d66bc4a49f2d7199adaefa66 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Sat, 16 May 2026 03:08:39 +0530 Subject: [PATCH 21/30] leds: rgb: Add support for Samsung S2M series PMIC RGB LED device Add support for the RGB LEDs found in certain Samsung S2M series PMICs. The device has three LED channels, controlled as a single device. These LEDs are typically used as status indicators in mobile phones. The driver includes initial support for the S2MU005 PMIC RGB LEDs. Signed-off-by: Kaustabh Chakraborty Link: https://patch.msgid.link/20260516-s2mu005-pmic-v7-7-73f9702fb461@disroot.org Signed-off-by: Lee Jones --- drivers/leds/rgb/Kconfig | 10 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-s2m-rgb.c | 426 ++++++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 drivers/leds/rgb/leds-s2m-rgb.c diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 9a4ba6531cf8..5599b71be54d 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -100,6 +100,16 @@ config LEDS_QCOM_LPG If compiled as a module, the module will be named leds-qcom-lpg. +config LEDS_S2M_RGB + tristate "Samsung S2M series PMICs RGB LED support" + depends on LEDS_CLASS + depends on MFD_SEC_CORE + help + This option enables support for the S2MU005 RGB LEDs. These devices + have three LED channels, with 8-bit brightness control for each + channel. The S2MU005 is usually found in mobile phones as status + indicators. + config LEDS_MT6370_RGB tristate "LED Support for MediaTek MT6370 PMIC" depends on MFD_MT6370 diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index f3b365ea082d..cc0f2df66286 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -8,4 +8,5 @@ obj-$(CONFIG_LEDS_LP5860_SPI) += leds-lp5860-spi.o obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o +obj-$(CONFIG_LEDS_S2M_RGB) += leds-s2m-rgb.o obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c new file mode 100644 index 000000000000..d239f54eee90 --- /dev/null +++ b/drivers/leds/rgb/leds-s2m-rgb.c @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RGB LED Driver for Samsung S2M series PMICs. + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * Copyright (c) 2026 Kaustabh Chakraborty + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct s2m_rgb { + struct device *dev; + struct regmap *regmap; + struct led_classdev_mc mc; + /* + * The mutex object prevents race conditions when evaluation and + * application of LED pattern state. + */ + struct mutex lock; + /* + * State variables representing the current LED pattern, these only to + * be accessed when lock is held. + */ + u8 ramp_up; + u8 ramp_dn; + u8 stay_hi; + u8 stay_lo; +}; + +static struct led_classdev_mc *to_s2m_mc(struct led_classdev *cdev) +{ + return container_of(cdev, struct led_classdev_mc, led_cdev); +} + +static struct s2m_rgb *to_s2m_rgb(struct led_classdev_mc *mc) +{ + return container_of(mc, struct s2m_rgb, mc); +} + +static const u32 s2mu005_rgb_lut_ramp[] = { + 0, 100, 200, 300, 400, 500, 600, 700, + 800, 1000, 1200, 1400, 1600, 1800, 2000, 2200, +}; + +static const u32 s2mu005_rgb_lut_stay_hi[] = { + 100, 200, 300, 400, 500, 750, 1000, 1250, + 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3250, +}; + +static const u32 s2mu005_rgb_lut_stay_lo[] = { + 0, 500, 1000, 1500, 2000, 2500, 3000, 3500, + 4000, 4500, 5000, 6000, 7000, 8000, 10000, 12000, +}; + +static int s2mu005_rgb_apply_params(struct s2m_rgb *rgb) +{ + struct regmap *regmap = rgb->regmap; + unsigned int ramp_val = 0; + unsigned int stay_val = 0; + int ret; + + ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_UP, rgb->ramp_up); + ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_DN, rgb->ramp_dn); + + stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_HI, rgb->stay_hi); + stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_LO, rgb->stay_lo); + + ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET); + if (ret) { + dev_err(rgb->dev, "failed to reset RGB LEDs\n"); + return ret; + } + + for (int i = 0; i < rgb->mc.num_colors; i++) { + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_CTRL(i), + rgb->mc.subled_info[i].brightness); + if (ret) { + dev_err(rgb->dev, "failed to set LED brightness\n"); + return ret; + } + + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_RAMP(i), ramp_val); + if (ret) { + dev_err(rgb->dev, "failed to set ramp timings\n"); + return ret; + } + + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_STAY(i), stay_val); + if (ret) { + dev_err(rgb->dev, "failed to set stay timings\n"); + return ret; + } + } + + ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_SLOPE_SMOOTH); + if (ret) { + dev_err(rgb->dev, "failed to set ramp slope\n"); + return ret; + } + + return 0; +} + +static int s2mu005_rgb_reset_params(struct s2m_rgb *rgb) +{ + struct regmap *regmap = rgb->regmap; + int ret; + + ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET); + if (ret) { + dev_err(rgb->dev, "failed to reset RGB LEDs\n"); + return ret; + } + + rgb->ramp_up = 0; + rgb->ramp_dn = 0; + rgb->stay_hi = 0; + rgb->stay_lo = 0; + + return 0; +} + +/* + * s2m_rgb_lut_get_closest_duration - find closest duration in look-up table + * @lut: the look-up table to search for the closest timing + * @len: number of elements in the look-up table array + * @duration: the timing duration requested + * + * This function does a binary search on the given array, and finds the closest + * value to the requested timing. It is expected that the look-up table to be + * provided, is already sorted. + * + * This function returns a negative error code, or a non-negative index of the + * value in the look-up table closest to the one requested. + */ +static int s2m_rgb_lut_get_closest_duration(const u32 *lut, const size_t len, const u32 duration) +{ + u32 closest_distance = abs(duration - lut[0]); + int closest_index = 0; + int lo = 0; + int hi = len - 1; + + /* + * Allow a small amount of extrapolation beyond the highest timing value. + * + * Consider x and y to be the two last values in the table, and x < y. + * Since (y - x) / 2 integers, in the range [x + (y - x) / 2, y) + * returns y as the closest, allow extrapolation for the succeeding + * (y - x) / 2 integers as well, viz, up to (y, y + (y - x) / 2]. + * Anything beyond that is invalid. + */ + if (len >= 2 && duration > lut[len - 1] + (lut[len - 1] - lut[len - 2]) / 2) + return -EINVAL; + + while (lo <= hi) { + int mid = lo + (hi - lo) / 2; + + /* Narrow down search window as per binary-search algorithm. */ + if (duration < lut[mid]) + hi = mid - 1; + else + lo = mid + 1; + + if (abs(duration - lut[mid]) < closest_distance) { + closest_distance = abs(duration - lut[mid]); + closest_index = mid; + } + } + + return closest_index; +} + +static int s2m_rgb_pattern_set(struct led_classdev *cdev, struct led_pattern *pattern, u32 len, + int repeat) +{ + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev)); + const u32 *lut_ramp_up, *lut_ramp_dn, *lut_stay_hi, *lut_stay_lo; + size_t lut_ramp_up_len, lut_ramp_dn_len, lut_stay_hi_len, lut_stay_lo_len; + int brightness_peak = 0; + u32 time_hi = 0, time_lo = 0; + bool ramp_up_en = false, ramp_dn_en = false; + int ret; + + /* + * The typical pattern supported by this device can be represented with + * the following graph: + * + * 255 T ''''''-. .-'''''''-. + * | '. .' '. + * | \ / \ + * | '. .' '. + * | '-...........-' '- + * 0 +----------------------------------------------------> time (s) + * + * <---- HIGH ----><-- LOW --><-------- HIGH ---------> + * <-----><-------><---------><-------><-----><-------> + * stay_hi ramp_dn stay_lo ramp_up stay_hi ramp_dn + * + * There are two states, named HIGH and LOW. HIGH has a non-zero + * brightness level, while LOW is of zero brightness. The pattern + * provided should mention only one zero and non-zero brightness level. + * The hardware always starts the pattern from the HIGH state, as shown + * in the graph. + * + * The HIGH state can be divided in three somewhat equal timings: + * ramp_up, stay_hi, and ramp_dn. The LOW state has only one timing: + * stay_lo. + */ + + /* Only indefinitely looping patterns are supported. */ + if (repeat != -1) + return -EINVAL; + + /* Pattern should consist of at least two tuples. */ + if (len < 2) + return -EINVAL; + + for (int i = 0; i < len; i++) { + int brightness = pattern[i].brightness; + u32 delta_t = pattern[i].delta_t; + + if (brightness) { + /* + * The pattern should define only one non-zero + * brightness in the HIGH state. The device doesn't + * have any provisions to handle multiple peak + * brightness levels. + */ + if (brightness_peak && brightness_peak != brightness) + return -EINVAL; + + brightness_peak = brightness; + time_hi += delta_t; + ramp_dn_en = !!delta_t; + } else { + time_lo += delta_t; + ramp_up_en = !!delta_t; + } + } + + /* LUTs are specific to device variant. */ + lut_ramp_up = s2mu005_rgb_lut_ramp; + lut_ramp_up_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp); + lut_ramp_dn = s2mu005_rgb_lut_ramp; + lut_ramp_dn_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp); + lut_stay_hi = s2mu005_rgb_lut_stay_hi; + lut_stay_hi_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_hi); + lut_stay_lo = s2mu005_rgb_lut_stay_lo; + lut_stay_lo_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_lo); + + mutex_lock(&rgb->lock); + + /* + * The timings ramp_up, stay_hi, and ramp_dn of the HIGH state are + * roughly equal. Firstly, calculate and set timings for ramp_up and + * ramp_dn (making sure they're exactly equal). + */ + rgb->ramp_up = 0; + rgb->ramp_dn = 0; + + if (ramp_up_en) { + ret = s2m_rgb_lut_get_closest_duration(lut_ramp_up, lut_ramp_up_len, time_hi / 3); + if (ret < 0) + goto param_fail; + rgb->ramp_up = (u8)ret; + } + + if (ramp_dn_en) { + ret = s2m_rgb_lut_get_closest_duration(lut_ramp_dn, lut_ramp_dn_len, time_hi / 3); + if (ret < 0) + goto param_fail; + rgb->ramp_dn = (u8)ret; + } + + /* + * Subtract the allocated ramp timings from time_hi (and also making + * sure it doesn't underflow!). The remaining time is allocated to + * stay_hi. + */ + time_hi -= min(time_hi, lut_ramp_up[rgb->ramp_up]); + time_hi -= min(time_hi, lut_ramp_dn[rgb->ramp_dn]); + + ret = s2m_rgb_lut_get_closest_duration(lut_stay_hi, lut_stay_hi_len, time_hi); + if (ret < 0) + goto param_fail; + rgb->stay_hi = (u8)ret; + + ret = s2m_rgb_lut_get_closest_duration(lut_stay_lo, lut_stay_lo_len, time_lo); + if (ret < 0) + goto param_fail; + rgb->stay_lo = (u8)ret; + + led_mc_calc_color_components(&rgb->mc, brightness_peak); + /* Apply params with variant-specific implementation. */ + ret = s2mu005_rgb_apply_params(rgb); + if (ret) + goto param_fail; + + mutex_unlock(&rgb->lock); + + return 0; + +param_fail: + rgb->ramp_up = 0; + rgb->ramp_dn = 0; + rgb->stay_hi = 0; + rgb->stay_lo = 0; + + mutex_unlock(&rgb->lock); + + return ret; +} + +static int s2m_rgb_pattern_clear(struct led_classdev *cdev) +{ + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev)); + int ret = 0; + + mutex_lock(&rgb->lock); + + /* Reset params with variant-specific implementation. */ + ret = s2mu005_rgb_reset_params(rgb); + + mutex_unlock(&rgb->lock); + + return ret; +} + +static int s2m_rgb_brightness_set(struct led_classdev *cdev, enum led_brightness value) +{ + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev)); + int ret = 0; + + if (!value) + return s2m_rgb_pattern_clear(cdev); + + mutex_lock(&rgb->lock); + + led_mc_calc_color_components(&rgb->mc, value); + /* Apply params with variant-specific implementation. */ + ret = s2mu005_rgb_apply_params(rgb); + + mutex_unlock(&rgb->lock); + + return ret; +} + +static const struct mc_subled s2mu005_rgb_subled_info[] = { + { .channel = 0, .color_index = LED_COLOR_ID_BLUE }, + { .channel = 1, .color_index = LED_COLOR_ID_GREEN }, + { .channel = 2, .color_index = LED_COLOR_ID_RED }, +}; + +static int s2m_rgb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent); + struct s2m_rgb *rgb; + struct led_init_data init_data = {}; + int ret; + + rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL); + if (!rgb) + return -ENOMEM; + + platform_set_drvdata(pdev, rgb); + rgb->dev = dev; + rgb->regmap = pmic_drvdata->regmap_pmic; + + /* Configure variant-specific details. */ + rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info); + rgb->mc.subled_info = devm_kmemdup(dev, s2mu005_rgb_subled_info, + sizeof(s2mu005_rgb_subled_info), GFP_KERNEL); + if (!rgb->mc.subled_info) + return -ENOMEM; + + rgb->mc.led_cdev.max_brightness = 255; + rgb->mc.led_cdev.brightness_set_blocking = s2m_rgb_brightness_set; + rgb->mc.led_cdev.pattern_set = s2m_rgb_pattern_set; + rgb->mc.led_cdev.pattern_clear = s2m_rgb_pattern_clear; + + ret = devm_mutex_init(dev, &rgb->lock); + if (ret) + return dev_err_probe(dev, ret, "failed to create mutex lock\n"); + + init_data.fwnode = of_fwnode_handle(dev->of_node); + ret = devm_led_classdev_multicolor_register_ext(dev, &rgb->mc, &init_data); + if (ret) + return dev_err_probe(dev, ret, "failed to create LED device\n"); + + return 0; +} + +static const struct platform_device_id s2m_rgb_id_table[] = { + { "s2mu005-rgb", S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, s2m_rgb_id_table); + +static const struct of_device_id s2m_rgb_of_match_table[] = { + { .compatible = "samsung,s2mu005-rgb", .data = (void *)S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s2m_rgb_of_match_table); + +static struct platform_driver s2m_rgb_driver = { + .driver = { + .name = "s2m-rgb", + }, + .probe = s2m_rgb_probe, + .id_table = s2m_rgb_id_table, +}; +module_platform_driver(s2m_rgb_driver); + +MODULE_DESCRIPTION("RGB LED Driver for Samsung S2M Series PMICs"); +MODULE_AUTHOR("Kaustabh Chakraborty "); +MODULE_LICENSE("GPL"); From 373777fb56ab4db8a2a45cb14985550e888043eb Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Sat, 16 May 2026 03:08:40 +0530 Subject: [PATCH 22/30] Documentation: leds: Document pattern behavior of Samsung S2M series PMIC RGB LEDs Add documentation to describe how hardware patterns (as defined by the documentation of leds-trigger-pattern) are parsed and implemented by the Samsung S2M series PMIC RGB LED driver. Signed-off-by: Kaustabh Chakraborty Link: https://patch.msgid.link/20260516-s2mu005-pmic-v7-8-73f9702fb461@disroot.org Signed-off-by: Lee Jones --- Documentation/leds/index.rst | 1 + Documentation/leds/leds-s2m-rgb.rst | 60 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 Documentation/leds/leds-s2m-rgb.rst diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst index bebf44004278..23fa9ff7aaf4 100644 --- a/Documentation/leds/index.rst +++ b/Documentation/leds/index.rst @@ -28,6 +28,7 @@ LEDs leds-lp5812 leds-mlxcpld leds-mt6370-rgb + leds-s2m-rgb leds-sc27xx leds-st1202 leds-qcom-lpg diff --git a/Documentation/leds/leds-s2m-rgb.rst b/Documentation/leds/leds-s2m-rgb.rst new file mode 100644 index 000000000000..4f89a8c89ea8 --- /dev/null +++ b/Documentation/leds/leds-s2m-rgb.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Samsung S2M Series PMIC RGB LED Driver +====================================== + +Description +----------- + +The RGB LED on the S2M series PMIC hardware features a three-channel LED that +is grouped together as a single device. Furthermore, it supports 8-bit +brightness control for each channel. This LED is typically used as a status +indicator in mobile devices. It also supports various parameters for hardware +patterns. + +The hardware pattern can be programmed using the "pattern" trigger, using the +hw_pattern attribute. + +/sys/class/leds//repeat +---------------------------- + +The hardware supports only indefinitely repeating patterns. The repeat +attribute must be set to -1 for hardware patterns to function. + +/sys/class/leds//hw_pattern +-------------------------------- + +Specify a hardware pattern for the RGB LEDs. + +The pattern is a series of brightness levels and durations in milliseconds. +There should be only one non-zero brightness level. Unlike the results +described in leds-trigger-pattern, the transitions between on and off states +are smoothed out by the hardware. + +Simple pattern:: + + "255 3000 0 1000" + + 255 -+ ''''''-. .-'''''''-. + | '. .' '. + | \ / \ + | '. .' '. + | '-.......-' '- + 0 -+-------+-------+-------+-------+-------+-------+--> time (s) + 0 1 2 3 4 5 6 + +As described in leds-trigger-pattern, it is also possible to use zero-length +entries to disable the ramping mechanism. + +On-Off pattern:: + + "255 1000 255 0 0 1000 0 0" + + 255 -+ ------+ +-------+ +-------+ + | | | | | | + | | | | | | + | | | | | | + | +-------+ +-------+ +------- + 0 -+-------+-------+-------+-------+-------+-------+--> time (s) + 0 1 2 3 4 5 6 From 8f040b5c5e3a4fa6e14ec44219d4717f66923e16 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 13 May 2026 13:58:53 +0200 Subject: [PATCH 23/30] leds: class: Use firmware nodes for device lookup Replace the OF based lookup with the fwnode equivalent to get support for ACPI and software nodes. Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20260513115853.1584230-1-alban.bedel@lht.dlh.de Signed-off-by: Lee Jones --- drivers/leds/led-class.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 9e14ae588f78..a17db3d6644f 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -249,32 +249,34 @@ static const struct class leds_class = { }; /** - * of_led_get() - request a LED device via the LED framework - * @np: device node to get the LED device from + * fwnode_led_get() - request a LED device via the LED framework + * @fwnode: firmware node to get the LED device from * @index: the index of the LED * @name: the name of the LED used to map it to its function, if present * * Returns the LED device parsed from the phandle specified in the "leds" * property of a device tree node or a negative error-code on failure. */ -static struct led_classdev *of_led_get(struct device_node *np, int index, - const char *name) +static struct led_classdev *fwnode_led_get(struct fwnode_handle *fwnode, + int index, const char *name) { + struct fwnode_handle *led_node; struct device *led_dev; - struct device_node *led_node; /* * For named LEDs, first look up the name in the "led-names" property. - * If it cannot be found, then of_parse_phandle() will propagate the error. + * If it cannot be found, then fwnode_find_reference() will propagate + * the error. */ if (name) - index = of_property_match_string(np, "led-names", name); - led_node = of_parse_phandle(np, "leds", index); - if (!led_node) - return ERR_PTR(-ENOENT); + index = fwnode_property_match_string(fwnode, "led-names", + name); + led_node = fwnode_find_reference(fwnode, "leds", index); + if (IS_ERR(led_node)) + return ERR_CAST(led_node); - led_dev = class_find_device_by_fwnode(&leds_class, of_fwnode_handle(led_node)); - of_node_put(led_node); + led_dev = class_find_device_by_fwnode(&leds_class, led_node); + fwnode_handle_put(led_node); return led_module_get(led_dev); } @@ -332,7 +334,7 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, if (!dev) return ERR_PTR(-EINVAL); - led = of_led_get(dev->of_node, index, NULL); + led = fwnode_led_get(dev_fwnode(dev), index, NULL); if (IS_ERR(led)) return led; @@ -354,7 +356,7 @@ struct led_classdev *led_get(struct device *dev, char *con_id) const char *provider = NULL; struct device *led_dev; - led_cdev = of_led_get(dev->of_node, -1, con_id); + led_cdev = fwnode_led_get(dev_fwnode(dev), -1, con_id); if (!IS_ERR(led_cdev) || PTR_ERR(led_cdev) != -ENOENT) return led_cdev; From b819dc7d8fb2896b07e51eba0381d61daf35edd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 18:42:41 +0200 Subject: [PATCH 24/30] leds: core: Report ENODATA for brightness of hardware controlled LED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the LED is controlled fully by the hardware, the value cached by the LED driver core is incorrect. Return ENODATA to userspace in this case. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260521-cros_ec-leds-hw-trigger-brightness-v1-1-6cd9d7c9671e@weissschuh.net Signed-off-by: Lee Jones --- drivers/leds/led-class.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index a17db3d6644f..a51b0ed53886 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -27,12 +27,25 @@ static LIST_HEAD(leds_lookup_list); static struct workqueue_struct *leds_wq; +static bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev) +{ +#ifdef CONFIG_LEDS_TRIGGERS + guard(rwsem_read)(&led_cdev->trigger_lock); + return led_cdev->trigger && led_cdev->trigger->trigger_type; +#else + return false; +#endif +} + static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned int brightness; + if (led_trigger_is_hw_controlled(led_cdev)) + return -ENODATA; + mutex_lock(&led_cdev->led_access); led_update_brightness(led_cdev); brightness = led_cdev->brightness; From 61ed78f55a46e12afd4b464c4ba736f55ff33c5e Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Thu, 21 May 2026 20:12:05 +0200 Subject: [PATCH 25/30] leds: uleds: Return -EFAULT on copy_to_user() failure uleds_read() copies the current brightness value to userspace but ignores copy_to_user() failures. It then clears the pending update and reports a successful full read even when no data was copied. Return -EFAULT when the copy fails and leave the update pending so a later read can retry. Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260521181205.15130-1-alhouseenyousef@gmail.com Signed-off-by: Lee Jones --- drivers/leds/uleds.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c index ace71ffc0591..470015e3f802 100644 --- a/drivers/leds/uleds.c +++ b/drivers/leds/uleds.c @@ -147,10 +147,13 @@ static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count, } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) { retval = -EAGAIN; } else if (udev->new_data) { - retval = copy_to_user(buffer, &udev->brightness, - sizeof(udev->brightness)); - udev->new_data = false; - retval = sizeof(udev->brightness); + if (copy_to_user(buffer, &udev->brightness, + sizeof(udev->brightness))) { + retval = -EFAULT; + } else { + udev->new_data = false; + retval = sizeof(udev->brightness); + } } mutex_unlock(&udev->mutex); From a031b5fce5265938912d66047ec12b2208dd868f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 22 May 2026 12:42:22 +0200 Subject: [PATCH 26/30] leds: Use named initializers for arrays of i2c_device_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct i2c_device_id that replaces .driver_data by an anonymous union. While touching all these arrays, unify usage of whitespace and commas. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Alexander Sverdlin Link: https://patch.msgid.link/20260522104222.4081017-2-u.kleine-koenig@baylibre.com Signed-off-by: Lee Jones --- drivers/leds/flash/leds-as3645a.c | 2 +- drivers/leds/flash/leds-lm3601x.c | 4 ++-- drivers/leds/leds-an30259a.c | 2 +- drivers/leds/leds-as3668.c | 2 +- drivers/leds/leds-aw200xx.c | 10 +++++----- drivers/leds/leds-bd2802.c | 2 +- drivers/leds/leds-blinkm.c | 4 ++-- drivers/leds/leds-is31fl319x.c | 22 +++++++++++----------- drivers/leds/leds-is31fl32xx.c | 18 +++++++++--------- drivers/leds/leds-lm3530.c | 4 ++-- drivers/leds/leds-lm3532.c | 4 ++-- drivers/leds/leds-lm355x.c | 6 +++--- drivers/leds/leds-lm3642.c | 4 ++-- drivers/leds/leds-lm3692x.c | 4 ++-- drivers/leds/leds-lm3697.c | 2 +- drivers/leds/leds-lp3944.c | 4 ++-- drivers/leds/leds-lp3952.c | 4 ++-- drivers/leds/leds-lp50xx.c | 12 ++++++------ drivers/leds/leds-lp5521.c | 2 +- drivers/leds/leds-lp5523.c | 4 ++-- drivers/leds/leds-lp5562.c | 2 +- drivers/leds/leds-lp5569.c | 2 +- drivers/leds/leds-lp8501.c | 2 +- drivers/leds/leds-lp8860.c | 2 +- drivers/leds/leds-lp8864.c | 4 ++-- drivers/leds/leds-pca9532.c | 8 ++++---- drivers/leds/leds-pca955x.c | 12 ++++++------ drivers/leds/leds-pca963x.c | 8 ++++---- drivers/leds/leds-pca995x.c | 8 ++++---- drivers/leds/leds-st1202.c | 2 +- drivers/leds/leds-tca6507.c | 2 +- drivers/leds/leds-tlc591xx.c | 6 +++--- drivers/leds/leds-turris-omnia.c | 2 +- drivers/leds/rgb/leds-ktd202x.c | 6 +++--- 34 files changed, 91 insertions(+), 91 deletions(-) diff --git a/drivers/leds/flash/leds-as3645a.c b/drivers/leds/flash/leds-as3645a.c index 2f2d783c62c3..5fefcaef3714 100644 --- a/drivers/leds/flash/leds-as3645a.c +++ b/drivers/leds/flash/leds-as3645a.c @@ -741,7 +741,7 @@ static void as3645a_remove(struct i2c_client *client) } static const struct i2c_device_id as3645a_id_table[] = { - { AS_NAME }, + { .name = AS_NAME }, { } }; MODULE_DEVICE_TABLE(i2c, as3645a_id_table); diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c index abf6b96ade3d..8d00510c8967 100644 --- a/drivers/leds/flash/leds-lm3601x.c +++ b/drivers/leds/flash/leds-lm3601x.c @@ -465,8 +465,8 @@ static void lm3601x_remove(struct i2c_client *client) } static const struct i2c_device_id lm3601x_id[] = { - { "LM36010", CHIP_LM36010 }, - { "LM36011", CHIP_LM36011 }, + { .name = "LM36010", .driver_data = CHIP_LM36010 }, + { .name = "LM36011", .driver_data = CHIP_LM36011 }, { } }; MODULE_DEVICE_TABLE(i2c, lm3601x_id); diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c index a42cc4bc6917..4654a732d1b1 100644 --- a/drivers/leds/leds-an30259a.c +++ b/drivers/leds/leds-an30259a.c @@ -331,7 +331,7 @@ static const struct of_device_id an30259a_match_table[] = { MODULE_DEVICE_TABLE(of, an30259a_match_table); static const struct i2c_device_id an30259a_id[] = { - { "an30259a" }, + { .name = "an30259a" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, an30259a_id); diff --git a/drivers/leds/leds-as3668.c b/drivers/leds/leds-as3668.c index b2794492370e..46427fe752a8 100644 --- a/drivers/leds/leds-as3668.c +++ b/drivers/leds/leds-as3668.c @@ -175,7 +175,7 @@ static void as3668_remove(struct i2c_client *client) } static const struct i2c_device_id as3668_idtable[] = { - { "as3668" }, + { .name = "as3668" }, { } }; MODULE_DEVICE_TABLE(i2c, as3668_idtable); diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index fe223d363a5d..0d90eeb6448f 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -637,11 +637,11 @@ static const struct aw200xx_chipdef aw20108_cdef = { }; static const struct i2c_device_id aw200xx_id[] = { - { "aw20036" }, - { "aw20054" }, - { "aw20072" }, - { "aw20108" }, - {} + { .name = "aw20036" }, + { .name = "aw20054" }, + { .name = "aw20072" }, + { .name = "aw20108" }, + { } }; MODULE_DEVICE_TABLE(i2c, aw200xx_id); diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 2a08c5f27608..9732f41a1143 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -776,7 +776,7 @@ static int bd2802_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume); static const struct i2c_device_id bd2802_id[] = { - { "BD2802" }, + { .name = "BD2802" }, { } }; MODULE_DEVICE_TABLE(i2c, bd2802_id); diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c index 1978bbda47db..ee1589015826 100644 --- a/drivers/leds/leds-blinkm.c +++ b/drivers/leds/leds-blinkm.c @@ -806,8 +806,8 @@ static void blinkm_remove(struct i2c_client *client) } static const struct i2c_device_id blinkm_id[] = { - { "blinkm" }, - {} + { .name = "blinkm" }, + { } }; MODULE_DEVICE_TABLE(i2c, blinkm_id); diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index e411cee06dab..80f38dba0fba 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -565,17 +565,17 @@ static int is31fl319x_probe(struct i2c_client *client) * even though it is not used for DeviceTree based instantiation. */ static const struct i2c_device_id is31fl319x_id[] = { - { "is31fl3190" }, - { "is31fl3191" }, - { "is31fl3193" }, - { "is31fl3196" }, - { "is31fl3199" }, - { "sn3190" }, - { "sn3191" }, - { "sn3193" }, - { "sn3196" }, - { "sn3199" }, - {}, + { .name = "is31fl3190" }, + { .name = "is31fl3191" }, + { .name = "is31fl3193" }, + { .name = "is31fl3196" }, + { .name = "is31fl3199" }, + { .name = "sn3190" }, + { .name = "sn3191" }, + { .name = "sn3193" }, + { .name = "sn3196" }, + { .name = "sn3199" }, + { } }; MODULE_DEVICE_TABLE(i2c, is31fl319x_id); diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index fe07acbb103a..6c8d6b833260 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -616,15 +616,15 @@ static void is31fl32xx_remove(struct i2c_client *client) * even though it is not used for DeviceTree based instantiation. */ static const struct i2c_device_id is31fl32xx_id[] = { - { "is31fl3293" }, - { "is31fl3236" }, - { "is31fl3236a" }, - { "is31fl3235" }, - { "is31fl3218" }, - { "sn3218" }, - { "is31fl3216" }, - { "sn3216" }, - {}, + { .name = "is31fl3293" }, + { .name = "is31fl3236" }, + { .name = "is31fl3236a" }, + { .name = "is31fl3235" }, + { .name = "is31fl3218" }, + { .name = "sn3218" }, + { .name = "is31fl3216" }, + { .name = "sn3216" }, + { } }; MODULE_DEVICE_TABLE(i2c, is31fl32xx_id); diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index e44a3db106c3..481e9c0a41e7 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -478,8 +478,8 @@ static void lm3530_remove(struct i2c_client *client) } static const struct i2c_device_id lm3530_id[] = { - { LM3530_NAME }, - {} + { .name = LM3530_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lm3530_id); diff --git a/drivers/leds/leds-lm3532.c b/drivers/leds/leds-lm3532.c index 24dc8ad27bb3..b51496910b08 100644 --- a/drivers/leds/leds-lm3532.c +++ b/drivers/leds/leds-lm3532.c @@ -722,8 +722,8 @@ static const struct of_device_id of_lm3532_leds_match[] = { MODULE_DEVICE_TABLE(of, of_lm3532_leds_match); static const struct i2c_device_id lm3532_id[] = { - { LM3532_NAME }, - {} + { .name = LM3532_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lm3532_id); diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c index f68771b9eac6..2b7cf42141e4 100644 --- a/drivers/leds/leds-lm355x.c +++ b/drivers/leds/leds-lm355x.c @@ -504,9 +504,9 @@ static void lm355x_remove(struct i2c_client *client) } static const struct i2c_device_id lm355x_id[] = { - {LM3554_NAME, CHIP_LM3554}, - {LM3556_NAME, CHIP_LM3556}, - {} + { .name = LM3554_NAME, .driver_data = CHIP_LM3554 }, + { .name = LM3556_NAME, .driver_data = CHIP_LM3556 }, + { } }; MODULE_DEVICE_TABLE(i2c, lm355x_id); diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index 36246267b096..2a893399e05f 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -388,8 +388,8 @@ static void lm3642_remove(struct i2c_client *client) } static const struct i2c_device_id lm3642_id[] = { - { LM3642_NAME }, - {} + { .name = LM3642_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lm3642_id); diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c index 1d64ceb5ac85..95b850a3b31c 100644 --- a/drivers/leds/leds-lm3692x.c +++ b/drivers/leds/leds-lm3692x.c @@ -503,8 +503,8 @@ static void lm3692x_remove(struct i2c_client *client) } static const struct i2c_device_id lm3692x_id[] = { - { "lm36922", LM36922_MODEL }, - { "lm36923", LM36923_MODEL }, + { .name = "lm36922", .driver_data = LM36922_MODEL }, + { .name = "lm36923", .driver_data = LM36923_MODEL }, { } }; MODULE_DEVICE_TABLE(i2c, lm3692x_id); diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c index 7ad232780a31..933191fb2be0 100644 --- a/drivers/leds/leds-lm3697.c +++ b/drivers/leds/leds-lm3697.c @@ -354,7 +354,7 @@ static void lm3697_remove(struct i2c_client *client) } static const struct i2c_device_id lm3697_id[] = { - { "lm3697" }, + { .name = "lm3697" }, { } }; MODULE_DEVICE_TABLE(i2c, lm3697_id); diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index ccfeee49ea78..8e95dcedce40 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -417,8 +417,8 @@ static void lp3944_remove(struct i2c_client *client) /* lp3944 i2c driver struct */ static const struct i2c_device_id lp3944_id[] = { - { "lp3944" }, - {} + { .name = "lp3944" }, + { } }; MODULE_DEVICE_TABLE(i2c, lp3944_id); diff --git a/drivers/leds/leds-lp3952.c b/drivers/leds/leds-lp3952.c index 17219a582704..0a1af284f52b 100644 --- a/drivers/leds/leds-lp3952.c +++ b/drivers/leds/leds-lp3952.c @@ -266,8 +266,8 @@ static int lp3952_probe(struct i2c_client *client) } static const struct i2c_device_id lp3952_id[] = { - { LP3952_NAME }, - {} + { .name = LP3952_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lp3952_id); diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index 69c3550f1a31..259169214aaf 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -601,12 +601,12 @@ static void lp50xx_remove(struct i2c_client *client) } static const struct i2c_device_id lp50xx_id[] = { - { "lp5009", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5009] }, - { "lp5012", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5012] }, - { "lp5018", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5018] }, - { "lp5024", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5024] }, - { "lp5030", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5030] }, - { "lp5036", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5036] }, + { .name = "lp5009", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5009] }, + { .name = "lp5012", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5012] }, + { .name = "lp5018", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5018] }, + { .name = "lp5024", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5024] }, + { .name = "lp5030", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5030] }, + { .name = "lp5036", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5036] }, { } }; MODULE_DEVICE_TABLE(i2c, lp50xx_id); diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 7564b9953408..4937fc968011 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -253,7 +253,7 @@ static struct lp55xx_device_config lp5521_cfg = { }; static const struct i2c_device_id lp5521_id[] = { - { "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */ + { .name = "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg }, /* Three channel chip */ { } }; MODULE_DEVICE_TABLE(i2c, lp5521_id); diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 4ed3e735260c..39cb26d343c1 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -346,8 +346,8 @@ static struct lp55xx_device_config lp5523_cfg = { }; static const struct i2c_device_id lp5523_id[] = { - { "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg, }, - { "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg, }, + { .name = "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg }, + { .name = "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg }, { } }; diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index 14a4af361b26..9e6056821eb5 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -395,7 +395,7 @@ static struct lp55xx_device_config lp5562_cfg = { }; static const struct i2c_device_id lp5562_id[] = { - { "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg, }, + { .name = "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg }, { } }; MODULE_DEVICE_TABLE(i2c, lp5562_id); diff --git a/drivers/leds/leds-lp5569.c b/drivers/leds/leds-lp5569.c index a252ba6c455d..199db3efca65 100644 --- a/drivers/leds/leds-lp5569.c +++ b/drivers/leds/leds-lp5569.c @@ -514,7 +514,7 @@ static struct lp55xx_device_config lp5569_cfg = { }; static const struct i2c_device_id lp5569_id[] = { - { "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg, }, + { .name = "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg }, { } }; diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c index ee4ff4586bc0..8009689db7b4 100644 --- a/drivers/leds/leds-lp8501.c +++ b/drivers/leds/leds-lp8501.c @@ -130,7 +130,7 @@ static struct lp55xx_device_config lp8501_cfg = { }; static const struct i2c_device_id lp8501_id[] = { - { "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg, }, + { .name = "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg }, { } }; MODULE_DEVICE_TABLE(i2c, lp8501_id); diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 7a436861c4b7..69f064781f69 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -333,7 +333,7 @@ static int lp8860_probe(struct i2c_client *client) } static const struct i2c_device_id lp8860_id[] = { - { "lp8860" }, + { .name = "lp8860" }, { } }; MODULE_DEVICE_TABLE(i2c, lp8860_id); diff --git a/drivers/leds/leds-lp8864.c b/drivers/leds/leds-lp8864.c index 3afd729d2f8a..204727f2f350 100644 --- a/drivers/leds/leds-lp8864.c +++ b/drivers/leds/leds-lp8864.c @@ -270,8 +270,8 @@ static int lp8864_probe(struct i2c_client *client) } static const struct i2c_device_id lp8864_id[] = { - { "lp8864" }, - {} + { .name = "lp8864" }, + { } }; MODULE_DEVICE_TABLE(i2c, lp8864_id); diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index dae7c6760508..f3bf59495b68 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -67,10 +67,10 @@ enum { }; static const struct i2c_device_id pca9532_id[] = { - { "pca9530", pca9530 }, - { "pca9531", pca9531 }, - { "pca9532", pca9532 }, - { "pca9533", pca9533 }, + { .name = "pca9530", .driver_data = pca9530 }, + { .name = "pca9531", .driver_data = pca9531 }, + { .name = "pca9532", .driver_data = pca9532 }, + { .name = "pca9533", .driver_data = pca9533 }, { } }; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 2007fe6217ec..273383351ba0 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -764,12 +764,12 @@ static int pca955x_probe(struct i2c_client *client) } static const struct i2c_device_id pca955x_id[] = { - { "pca9550", (kernel_ulong_t)&pca955x_chipdefs[pca9550] }, - { "pca9551", (kernel_ulong_t)&pca955x_chipdefs[pca9551] }, - { "pca9552", (kernel_ulong_t)&pca955x_chipdefs[pca9552] }, - { "ibm-pca9552", (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] }, - { "pca9553", (kernel_ulong_t)&pca955x_chipdefs[pca9553] }, - {} + { .name = "pca9550", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9550] }, + { .name = "pca9551", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9551] }, + { .name = "pca9552", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9552] }, + { .name = "ibm-pca9552", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] }, + { .name = "pca9553", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9553] }, + { } }; MODULE_DEVICE_TABLE(i2c, pca955x_id); diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 050e93b04884..e3a81c60ee27 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -88,10 +88,10 @@ static struct pca963x_chipdef pca963x_chipdefs[] = { #define PCA963X_BLINK_PERIOD_MAX 10667 static const struct i2c_device_id pca963x_id[] = { - { "pca9632", pca9633 }, - { "pca9633", pca9633 }, - { "pca9634", pca9634 }, - { "pca9635", pca9635 }, + { .name = "pca9632", .driver_data = pca9633 }, + { .name = "pca9633", .driver_data = pca9633 }, + { .name = "pca9634", .driver_data = pca9634 }, + { .name = "pca9635", .driver_data = pca9635 }, { } }; MODULE_DEVICE_TABLE(i2c, pca963x_id); diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c index 6ad06ce2bf64..59951207fd04 100644 --- a/drivers/leds/leds-pca995x.c +++ b/drivers/leds/leds-pca995x.c @@ -188,10 +188,10 @@ static int pca995x_probe(struct i2c_client *client) } static const struct i2c_device_id pca995x_id[] = { - { "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef }, - { "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef }, - { "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef }, - {} + { .name = "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef }, + { .name = "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef }, + { .name = "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef }, + { } }; MODULE_DEVICE_TABLE(i2c, pca995x_id); diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 2b68cd3c45f8..7f68d956f694 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -389,7 +389,7 @@ static int st1202_probe(struct i2c_client *client) } static const struct i2c_device_id st1202_id[] = { - { "st1202-i2c" }, + { .name = "st1202-i2c" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, st1202_id); diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index fd0e8bab9a4b..9afe2722986c 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -182,7 +182,7 @@ struct tca6507_chip { }; static const struct i2c_device_id tca6507_id[] = { - { "tca6507" }, + { .name = "tca6507" }, { } }; MODULE_DEVICE_TABLE(i2c, tca6507_id); diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c index 6605e08a042a..2f272cfd2e91 100644 --- a/drivers/leds/leds-tlc591xx.c +++ b/drivers/leds/leds-tlc591xx.c @@ -214,9 +214,9 @@ tlc591xx_probe(struct i2c_client *client) } static const struct i2c_device_id tlc591xx_id[] = { - { "tlc59116" }, - { "tlc59108" }, - {}, + { .name = "tlc59116" }, + { .name = "tlc59108" }, + { } }; MODULE_DEVICE_TABLE(i2c, tlc591xx_id); diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 25ee5c1eb820..ed6a47bbb44f 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -532,7 +532,7 @@ static const struct of_device_id of_omnia_leds_match[] = { MODULE_DEVICE_TABLE(of, of_omnia_leds_match); static const struct i2c_device_id omnia_id[] = { - { "omnia" }, + { .name = "omnia" }, { } }; MODULE_DEVICE_TABLE(i2c, omnia_id); diff --git a/drivers/leds/rgb/leds-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c index e4f0f25a5e45..143020945e23 100644 --- a/drivers/leds/rgb/leds-ktd202x.c +++ b/drivers/leds/rgb/leds-ktd202x.c @@ -605,9 +605,9 @@ static void ktd202x_shutdown(struct i2c_client *client) } static const struct i2c_device_id ktd202x_id[] = { - {"ktd2026", KTD2026_NUM_LEDS}, - {"ktd2027", KTD2027_NUM_LEDS}, - {} + { .name = "ktd2026", .driver_data = KTD2026_NUM_LEDS }, + { .name = "ktd2027", .driver_data = KTD2027_NUM_LEDS }, + { } }; MODULE_DEVICE_TABLE(i2c, ktd202x_id); From c19fe864f667afc49d1391d764e20b66555bcf7a Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 25 May 2026 01:55:53 +0200 Subject: [PATCH 27/30] leds: uleds: Fix potential buffer overread The name string supplied by userspace is not guaranteed to be null-terminated, so using strchr() on it might result in a buffer overread. The same thing will happen when said string is used by the LED class device. Fix this by using strnchr() instead and explicitly check that the name string is properly null-terminated. Cc: stable@vger.kernel.org Fixes: e381322b0190 ("leds: Introduce userspace LED class driver") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260524235553.189134-1-W_Armin@gmx.de Signed-off-by: Lee Jones --- drivers/leds/uleds.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c index 470015e3f802..6affa581b61d 100644 --- a/drivers/leds/uleds.c +++ b/drivers/leds/uleds.c @@ -102,7 +102,8 @@ static ssize_t uleds_write(struct file *file, const char __user *buffer, name = udev->user_dev.name; if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") || - strchr(name, '/')) { + strnchr(name, sizeof(udev->user_dev.name), '/') || + !strnchr(name, sizeof(udev->user_dev.name), '\0')) { ret = -EINVAL; goto out; } From 9ba15b2eb87ec33a9037343785e44594df4a59fc Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 26 May 2026 12:37:32 +0200 Subject: [PATCH 28/30] leds: Fix CONFIG_OF dependency for LEDS_LP5860_CORE Building LEDS_LP5860_SPI without CONFIG_OF leads to a build time warning: WARNING: unmet direct dependencies detected for LEDS_LP5860_CORE Depends on [n]: NEW_LEDS [=y] && LEDS_CLASS_MULTICOLOR [=y] && LEDS_CLASS [=y] && OF [=n] Selected by [y]: - LEDS_LP5860_SPI [=y] && NEW_LEDS [=y] && LEDS_CLASS_MULTICOLOR [=y] && SPI [=y] Address this by adding the same dependency here as well. Fixes: 3daf2c4ef82b ("leds: Add support for TI LP5860 LED driver chip") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260526103738.3389272-1-arnd@kernel.org Signed-off-by: Lee Jones --- drivers/leds/rgb/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 5599b71be54d..6e9ab5f60714 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -56,6 +56,7 @@ config LEDS_LP5860_CORE config LEDS_LP5860_SPI tristate "LED Support for TI LP5860 SPI LED driver chip" depends on SPI + depends on OF select LEDS_LP5860_CORE help If you say yes here you get support for the Texas Instruments From 938fda9671dd07196f4e591d30977b2e69d8f320 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 27 May 2026 02:14:22 +0200 Subject: [PATCH 29/30] leds: Fix sysfs ABI date The "multi_max_intensity" sysfs attribute was not included in kernel 7.1, so update the KernelVersion and Date tags accordingly. Fixes: b1a9b7a904af ("leds: Introduce the multi_max_intensity sysfs attribute") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260527001422.51111-1-W_Armin@gmx.de Signed-off-by: Lee Jones --- Documentation/ABI/testing/sysfs-class-led-multicolor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor b/Documentation/ABI/testing/sysfs-class-led-multicolor index 197da3e775b4..62ce58f393d6 100644 --- a/Documentation/ABI/testing/sysfs-class-led-multicolor +++ b/Documentation/ABI/testing/sysfs-class-led-multicolor @@ -26,8 +26,8 @@ Description: read/write Documentation/leds/leds-class-multicolor.rst. What: /sys/class/leds//multi_max_intensity -Date: March 2026 -KernelVersion: 7.1 +Date: May 2026 +KernelVersion: 7.2 Contact: Armin Wolf Description: read This file contains an array of integers describing the maximum From d43f1d792902ba0a53fd311bff2cf96095c7606d Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Wed, 27 May 2026 12:26:51 +0200 Subject: [PATCH 30/30] leds: tps6131x: Increase overvoltage protection threshold to 6V Currently, there may be cases where the overvoltage detection is triggered even with a valid and generally functioning hardware setup. This occurs, for example, when the input voltage exceeds the currently used overvoltage threshold of 4.65V (typical). Since input voltages up to 5V are supported, the threshold should be adjusted accordingly. While the target output voltage setting has no effect on the LED operation used here, it indirectly selects the threshold for overvoltage detection. Set this to a value of 4.95V to select a threshold of 6V (typical). Signed-off-by: Matthias Fend Link: https://patch.msgid.link/20260527-leds-tps6131x-ovp-v1-1-1ac70d03c9eb@emfend.at Signed-off-by: Lee Jones --- drivers/leds/flash/leds-tps6131x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c index f0f1f2b77d5a..5c9a5af5af05 100644 --- a/drivers/leds/flash/leds-tps6131x.c +++ b/drivers/leds/flash/leds-tps6131x.c @@ -277,7 +277,7 @@ static int tps6131x_init_chip(struct tps6131x *tps6131x) if (ret) return ret; - val = TPS6131X_REG_6_ENTS; + val = TPS6131X_REG_6_ENTS | (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT); ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val); if (ret)