summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-06-18 14:45:08 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-06-18 14:45:08 -0700
commit6e717507bfbe8d6955f3f4c5604857a392c7e6fa (patch)
treea7dfa10b0fede59860521867cbcaf2aa425a52ce /drivers
parent6beaec3aee9852438b89e4d7891caf5e84d45851 (diff)
parentd43f1d792902ba0a53fd311bff2cf96095c7606d (diff)
Merge tag 'leds-next-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
Pull LED updates from Lee Jones: "New Support & Features: - Samsung S2MU005: Add support for the Samsung S2MU005 PMIC which includes flash and RGB LED controllers - Texas Instruments: - LP5812: Add support for the TI LP5812 LED driver - LP5860: Add support for the Texas Instruments LP5860 LED matrix driver via SPI Improvements & Fixes: - Core: - Adjust the brightness sysfs node documentation to clarify that only decimal values are accepted - Fix a race condition in the software blink logic when stopping blinking and setting brightness simultaneously - Introduce the `multi_max_intensity` sysfs attribute for multicolor LEDs to support hardware-based global brightness control - Replace OF-based device lookup with firmware node equivalents to support ACPI and software nodes - Return `ENODATA` when reading brightness from hardware-controlled LEDs - Set the coherent DMA mask to zero for the Samsung PMIC device to suppress unnecessary "DMA mask not set" messages - ams OSRAM AS3668: Fix a Kconfig symbol name mismatch in the Makefile that prevented the driver from being built - BlinkM: Fix spelling and comment style issues in the driver - DAC124S085: Declare the SPI command word as `__le16` to ensure correct endianness and pass sparse checks - GPIO Trigger: Use `GPIOD_FLAGS_BIT_NONEXCLUSIVE` to allow sharing GPIOs between the LED trigger and other drivers - NXP PCA9532: Fix an issue where the LED would stop blinking when changing brightness to a non-zero value - Qualcomm: Unify the user-visible company name to "Qualcomm" across flash LED config options - Qualcomm LPG: Optimize memory allocation by combining main structure and channels into a single allocation using flexible array members - Texas Instruments - LP5860: Add missing `CONFIG_OF` dependency to prevent build warnings - TPS6131x: Increase the overvoltage protection threshold to 6V to avoid false triggers with 5V input supplies - Userspace LEDs (uLEDs): - Fix a potential buffer overread by using `strnchr()` for name string validation - Return `-EFAULT` on `copy_to_user()` failure to properly handle read errors Cleanups & Refactoring: - Core: - Convert various `i2c_device_id` arrays to use named initializers for improved robustness and readability - Multi-color: Fix incorrect `KernelVersion` and `Date` tags for the `multi_max_intensity` ABI - Broadcom BCM63138 / ChromeOS EC: Move `MODULE_DEVICE_TABLE` declarations next to the ID tables for consistency - LP5812: Fix a sysfs ABI reference in the documentation - ST1202: Remove an unused legacy GPIO header include Device Tree Binding Updates: - Class: Document the keyboard backlight LED class naming conventions, including a new scheme for zoned backlights - Core: Dual-license the common LED bindings header under GPLv2 and BSD-2-Clause - IR SPI LED: Add a new 30% duty-cycle value for the IR transmitter used in Xiaomi Redmi Note 8 - Samsung S2M series: - Document the flash LED device bindings for Samsung S2M series PMICs - Document the pattern behavior for Samsung S2M series PMIC RGB - S2MU005: Add device tree bindings for the S2MU005 PMIC, including its flash and RGB LED sub-devices - TI LM3560: Document the TI LM3559 and LM3560 synchronous boost flash drivers" * tag 'leds-next-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (30 commits) leds: tps6131x: Increase overvoltage protection threshold to 6V leds: Fix sysfs ABI date leds: Fix CONFIG_OF dependency for LEDS_LP5860_CORE leds: uleds: Fix potential buffer overread leds: Use named initializers for arrays of i2c_device_data leds: uleds: Return -EFAULT on copy_to_user() failure leds: core: Report ENODATA for brightness of hardware controlled LED leds: class: Use firmware nodes for device lookup Documentation: leds: Document pattern behavior of Samsung S2M series PMIC RGB LEDs leds: rgb: Add support for Samsung S2M series PMIC RGB LED device leds: flash: Add support for Samsung S2M series PMIC flash LED device dt-bindings: leds: Document Samsung S2M series PMIC flash LED device leds: core: Fix race condition for software blink leds: Adjust documentation of brightness sysfs node leds: dac124s085: Declare SPI command word as __le16 leds: Introduce the multi_max_intensity sysfs attribute dt-bindings: leds: Document TI LM3560 Synchronous Boost Flash Driver leds: bcm63138/cros_ec: Move MODULE_DEVICE_TABLE next to the table itself leds: Add support for TI LP5860 LED driver chip Documentation: leds: leds-class: Document keyboard backlight LED class naming ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/leds/Makefile2
-rw-r--r--drivers/leds/blink/leds-bcm63138.c2
-rw-r--r--drivers/leds/flash/Kconfig13
-rw-r--r--drivers/leds/flash/Makefile1
-rw-r--r--drivers/leds/flash/leds-as3645a.c2
-rw-r--r--drivers/leds/flash/leds-lm3601x.c4
-rw-r--r--drivers/leds/flash/leds-s2m-flash.c350
-rw-r--r--drivers/leds/flash/leds-tps6131x.c2
-rw-r--r--drivers/leds/led-class-multicolor.c47
-rw-r--r--drivers/leds/led-class.c43
-rw-r--r--drivers/leds/led-core.c33
-rw-r--r--drivers/leds/leds-an30259a.c2
-rw-r--r--drivers/leds/leds-as3668.c2
-rw-r--r--drivers/leds/leds-aw200xx.c10
-rw-r--r--drivers/leds/leds-bd2802.c2
-rw-r--r--drivers/leds/leds-blinkm.c7
-rw-r--r--drivers/leds/leds-cros_ec.c2
-rw-r--r--drivers/leds/leds-dac124s085.c2
-rw-r--r--drivers/leds/leds-is31fl319x.c22
-rw-r--r--drivers/leds/leds-is31fl32xx.c18
-rw-r--r--drivers/leds/leds-lm3530.c4
-rw-r--r--drivers/leds/leds-lm3532.c4
-rw-r--r--drivers/leds/leds-lm355x.c6
-rw-r--r--drivers/leds/leds-lm3642.c4
-rw-r--r--drivers/leds/leds-lm3692x.c4
-rw-r--r--drivers/leds/leds-lm3697.c2
-rw-r--r--drivers/leds/leds-lp3944.c4
-rw-r--r--drivers/leds/leds-lp3952.c4
-rw-r--r--drivers/leds/leds-lp50xx.c13
-rw-r--r--drivers/leds/leds-lp5521.c2
-rw-r--r--drivers/leds/leds-lp5523.c4
-rw-r--r--drivers/leds/leds-lp5562.c2
-rw-r--r--drivers/leds/leds-lp5569.c2
-rw-r--r--drivers/leds/leds-lp8501.c2
-rw-r--r--drivers/leds/leds-lp8860.c2
-rw-r--r--drivers/leds/leds-lp8864.c4
-rw-r--r--drivers/leds/leds-pca9532.c16
-rw-r--r--drivers/leds/leds-pca955x.c12
-rw-r--r--drivers/leds/leds-pca963x.c8
-rw-r--r--drivers/leds/leds-pca995x.c8
-rw-r--r--drivers/leds/leds-st1202.c3
-rw-r--r--drivers/leds/leds-tca6507.c2
-rw-r--r--drivers/leds/leds-tlc591xx.c6
-rw-r--r--drivers/leds/leds-turris-omnia.c2
-rw-r--r--drivers/leds/rgb/Kconfig36
-rw-r--r--drivers/leds/rgb/Makefile3
-rw-r--r--drivers/leds/rgb/leds-ktd202x.c6
-rw-r--r--drivers/leds/rgb/leds-lp5860-core.c234
-rw-r--r--drivers/leds/rgb/leds-lp5860-spi.c98
-rw-r--r--drivers/leds/rgb/leds-lp5860.h268
-rw-r--r--drivers/leds/rgb/leds-ncp5623.c4
-rw-r--r--drivers/leds/rgb/leds-qcom-lpg.c120
-rw-r--r--drivers/leds/rgb/leds-s2m-rgb.c426
-rw-r--r--drivers/leds/trigger/ledtrig-gpio.c3
-rw-r--r--drivers/leds/uleds.c14
55 files changed, 1697 insertions, 201 deletions
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
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/flash/Kconfig b/drivers/leds/flash/Kconfig
index 5e08102a6784..9b6dd3f1ffe8 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
@@ -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-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/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 <kauschluss@disroot.org>
+ */
+
+#include <linux/container_of.h>
+#include <linux/led-class-flash.h>
+#include <linux/mfd/samsung/core.h>
+#include <linux/mfd/samsung/s2mu005.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+#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", &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 <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");
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)
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 <linux/init.h>
#include <linux/led-class-multicolor.h>
#include <linux/math.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
+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/led-class.c b/drivers/leds/led-class.c
index 9e14ae588f78..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;
@@ -249,32 +262,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 +347,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 +369,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;
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 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 software blink disable is pending, also queue brightness setting.
+ * If software blink is active, delay brightness setting
+ * until the next timer tick.
*/
- if (!brightness) {
- set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
+ 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);
- } else {
- set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
- &led_cdev->work_flags);
+ 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.
+ */
+ set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
return;
}
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 577497b9d426..ee1589015826 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;
@@ -805,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-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 <linux@weissschuh.net");
MODULE_LICENSE("GPL");
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);
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 e2a9c8592953..259169214aaf 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++;
}
@@ -600,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 0344189bb991..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 },
{ }
};
@@ -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)
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 4e5dd76d714d..7f68d956f694 100644
--- a/drivers/leds/leds-st1202.c
+++ b/drivers/leds/leds-st1202.c
@@ -9,7 +9,6 @@
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/err.h>
-#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
@@ -390,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/Kconfig b/drivers/leds/rgb/Kconfig
index 28ef4c487367..6e9ab5f60714 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -39,6 +39,32 @@ 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
+ depends on OF
+ 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
@@ -75,6 +101,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 be45991f63f5..cc0f2df66286 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -3,7 +3,10 @@
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
+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-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);
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 <kernel@pengutronix.de>
+ */
+
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#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 <kernel@pengutronix.de>");
+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 <kernel@pengutronix.de>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#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 <kernel@pengutronix.de>");
+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 <kernel@pengutronix.de>
+ */
+
+#ifndef _DRIVERS_LEDS_RGB_LP5860_H
+#define _DRIVERS_LEDS_RGB_LP5860_H
+
+#include <linux/led-class-multicolor.h>
+#include <linux/regmap.h>
+
+#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 */
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/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,59 +80,9 @@
#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
* @base: base address of the PWM channel
@@ -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;
@@ -217,6 +167,55 @@ struct lpg_led {
};
/**
+ * 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
* @base: base address for PWM channel registers
@@ -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);
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 <kauschluss@disroot.org>
+ */
+
+#include <linux/container_of.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mfd/samsung/core.h>
+#include <linux/mfd/samsung/s2mu005.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+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 <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");
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);
diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c
index ace71ffc0591..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;
}
@@ -147,10 +148,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);