summaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-26 08:55:14 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-26 08:55:14 -1000
commit5b93dc1c0c61fd0fe575cb473e9e25f929fda509 (patch)
treee9405ab03dda86826201dca3a7731924825bb982 /drivers/leds
parentd857e1f3faf998f2614dcdb52a90a27bc83bd71d (diff)
parent59cf91cf87abb67a49b1bb7b9d3b06486abde8cf (diff)
Merge tag 'leds-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
Pull LED updates from Lee Jones: "New Support & Features: - Core: Add `led_trigger_notify_hw_control_changed()` to allow drivers to notify the core of autonomous hardware control transitions - Core: Add the `hw_offloaded()` trigger callback and `trigger_may_offload_to_hw` sysfs attribute to expose hardware-offloaded triggers - Netdev Trigger: Implement the `hw_offloaded()` callback to report hardware control state to the LED core - Analog Devices LTC3208: Add support for the LTC3208 7-channel multi-display I2C LED driver - ChromeOS EC: Implement the `hw_offloaded()` callback for the `chromeos-auto` trigger - CZ.NIC Turris Omnia: Implement the `hw_offloaded()` callback and declare `omnia-mcu` as a hardware control trigger - STMicroelectronics ST1202: Add hardware-accelerated blink support via `blink_set()` Improvements & Fixes: - Class: Always hold the `led_access` mutex in `brightness_show()` to avoid exposing transient registration states - Class: Deactivate active hardware control triggers when writing to the `brightness` sysfs attribute - Core: Enforce strict validation in `led_trigger_is_hw_controlled()` and warn on legacy fallback paths - Flash Class: Initialize the fault variable in `flash_fault_show()` to prevent undefined behavior when callbacks perform bitwise ORs - Kinetic KTD2692: Pass the driver context directly to `regulator_disable_action()` to prevent a NULL pointer dereference during error unwinding - Maxim MAX77705: Use `fwnode_for_each_child_node_scoped()` in `max77705_add_led()` to prevent child node reference leaks on error paths - NXP PCA9532: Check the return value of `i2c_smbus_read_byte_data()` in `pca9532_getled()` to avoid integer underflow on read errors - Qualcomm WLED: Fix a NULL pointer dereference in `wled_remove()` by setting platform driver data in `wled_probe()` - Qualcomm WLED: Fix the WLED3 brightness register stride to prevent consecutive string writes from overlapping - Qualcomm WLED: Read back the hardware-programmed brightness at probe, expose it via `get_brightness()`, and prevent unbalanced OVP IRQ enabling - STMicroelectronics ST1202: Clear unused PWM pattern slots in `pattern_set()` and `blink_set()` to prevent unintended output when sharing the global sequencer - STMicroelectronics ST1202: Program and cap the channel current according to `max_brightness` across brightness, blink, and hardware pattern modes, and scale pattern brightness to the 12-bit PWM range - STMicroelectronics ST1202: Retain a reference to each child LED firmware node for the lifetime of the device - STMicroelectronics ST1202: Use only the blocking `brightness_set_blocking()` callback to avoid sleeping in atomic context during I2C transfers - TI LM3692x: Check the final register write in `lm3692x_leds_enable()` to prevent regulator/GPIO leaks and an inconsistent enabled state - TI LP8860: Fix a `device_node` reference leak in `lp8860_probe()` using `__free(device_node)` Cleanups & Refactoring: - Core: Move `led_trigger_is_hw_controlled()` and `led_trigger_group` from `led-class.c` into `led-triggers.c` - Core: Replace conditional `set_bit()`/`clear_bit()` patterns with `assign_bit()` and `__assign_bit()` - Flash Class: Simplify `flash_fault_show()` using `sysfs_emit_at()` and omit the trailing space - Maintainers: Add Griffin Kroah-Hartman as an LED subsystem reviewer - Intel SS4200: Fix a spelling typo in a comment - ISSI IS31FL32xx: Convert DT-specific parsing helpers to generic device and fwnode property APIs - NXP PCA955x: Drop redundant intermediate platform data and allocate the LED array using a flexible array member - Qualcomm LPG: Convert LED and channel parsing to fwnode APIs and avoid leaving `default_trigger` uninitialized - Skyworks AAT1290: Use `ARRAY_SIZE()` when allocating `mm_current_scale` to match the target pointer type for type-aware allocators - STMicroelectronics ST1202: Introduce a `st1202_iled_max()` helper to unify channel current ceiling calculations - STMicroelectronics ST1202: Standardize variable and macro naming, fix helper spelling, and use `u8` consistently instead of `uint8_t` - Syscon: Convert from OF-specific property helpers to the generic device property and fwnode APIs Device Tree Binding Updates: - Core: Allow PHY RX triggers (`phy[0-9]+rx`) in `linux,default-trigger` - Core: Standardize example indentation across multiple LED and backlight binding schemas - Analog Devices LTC3208: Document the LTC3208 multi-display LED driver - LaCie netxbig: Convert LED bindings to DT schema - Motorola CPCAP: Convert LED bindings to DT schemaLEDs for v7.4" * tag 'leds-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (49 commits) dt-bindings: leds: lacie,netxbig-leds: Convert to DT schema leds: pca955x: Drop platform data and allocate LEDs with the device dt-bindings: leds: Use consistent indentation in the example backlight: qcom-wled: Read back the programmed brightness at probe backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe backlight: qcom-wled: Fix WLED3 brightness register stride backlight: qcom-wled: Fix NULL pointer dereference in wled_remove() dt-bindings: leds: Allow PHY RX triggers MAINTAINERS: Add myself as a LED SUBSYSTEM reviewer leds: trigger: Add led_trigger_notify_hw_control_changed() interface leds: trigger: Enforce strict checks in led_trigger_is_hw_controlled() leds: trigger: netdev: Implement hw_offloaded() callback leds: turris-omnia: Implement hw_offloaded() trigger callback and declare hw_control_trigger leds: cros_ec: Implement hw_offloaded() trigger callback leds: trigger: Add hw_offloaded() callback and provide trigger_may_offload_to_hw attribute leds: trigger: Move led_trigger_group to the right place leds: class: Remove hardware control trigger when writing brightness leds: trigger: Move led_trigger_is_hw_controlled() to the right place leds: class: Always protect brightness_show() with led_access dt-bindings: leds: leds-cpcap: Convert to DT schema ...
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/Kconfig12
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/flash/leds-aat1290.c5
-rw-r--r--drivers/leds/flash/leds-ktd2692.c10
-rw-r--r--drivers/leds/led-class-flash.c21
-rw-r--r--drivers/leds/led-class.c46
-rw-r--r--drivers/leds/led-core.c5
-rw-r--r--drivers/leds/led-triggers.c177
-rw-r--r--drivers/leds/leds-cros_ec.c6
-rw-r--r--drivers/leds/leds-is31fl32xx.c20
-rw-r--r--drivers/leds/leds-lm3692x.c4
-rw-r--r--drivers/leds/leds-lp8860.c5
-rw-r--r--drivers/leds/leds-ltc3208.c234
-rw-r--r--drivers/leds/leds-max77705.c3
-rw-r--r--drivers/leds/leds-pca9532.c9
-rw-r--r--drivers/leds/leds-pca955x.c94
-rw-r--r--drivers/leds/leds-pca963x.c5
-rw-r--r--drivers/leds/leds-ss4200.c2
-rw-r--r--drivers/leds/leds-st1202.c240
-rw-r--r--drivers/leds/leds-syscon.c9
-rw-r--r--drivers/leds/leds-turris-omnia.c7
-rw-r--r--drivers/leds/leds.h8
-rw-r--r--drivers/leds/rgb/leds-qcom-lpg.c38
-rw-r--r--drivers/leds/trigger/Kconfig10
-rw-r--r--drivers/leds/trigger/ledtrig-netdev.c13
25 files changed, 756 insertions, 228 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index d0d3182236be..7ec2c8d78542 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -1042,6 +1042,18 @@ config LEDS_ACER_A500
This option enables support for the Power Button LED of
Acer Iconia Tab A500.
+config LEDS_LTC3208
+ tristate "LED Driver for Analog Devices LTC3208"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ Say Y to enable the LTC3208 LED driver.
+ This enables the LED device LTC3208, a 7-channel, 17-current source
+ multidisplay high-current LED driver, configured via I2C.
+
+ To compile this driver as a module, choose M here: the module will
+ be called ltc3208.
+
source "drivers/leds/blink/Kconfig"
comment "Flash and Torch LED drivers"
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index a68244bd50fb..4d4b089156e2 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
+obj-$(CONFIG_LEDS_LTC3208) += leds-ltc3208.o
obj-$(CONFIG_LEDS_LTC3220) += leds-ltc3220.o
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o
diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c
index 49251cfd3350..4bde0b07fe78 100644
--- a/drivers/leds/flash/leds-aat1290.c
+++ b/drivers/leds/flash/leds-aat1290.c
@@ -311,8 +311,9 @@ static int init_mm_current_scale(struct aat1290_led *led,
int i, max_mm_current =
AAT1290_MAX_MM_CURRENT(cfg->max_flash_current);
- led->mm_current_scale = devm_kzalloc(&led->pdev->dev,
- sizeof(max_mm_current_percent),
+ led->mm_current_scale = devm_kcalloc(&led->pdev->dev,
+ ARRAY_SIZE(max_mm_current_percent),
+ sizeof(*led->mm_current_scale),
GFP_KERNEL);
if (!led->mm_current_scale)
return -ENOMEM;
diff --git a/drivers/leds/flash/leds-ktd2692.c b/drivers/leds/flash/leds-ktd2692.c
index 22fbfccd4873..43d4d55503ab 100644
--- a/drivers/leds/flash/leds-ktd2692.c
+++ b/drivers/leds/flash/leds-ktd2692.c
@@ -69,6 +69,8 @@ static const struct expresswire_timing ktd2692_timing = {
};
struct ktd2692_context {
+ struct device *dev;
+
/* Common ExpressWire properties (ctrl GPIO and timing) */
struct expresswire_common_props props;
@@ -196,13 +198,12 @@ static void ktd2692_setup(struct ktd2692_context *led)
static void regulator_disable_action(void *_data)
{
- struct device *dev = _data;
- struct ktd2692_context *led = dev_get_drvdata(dev);
+ struct ktd2692_context *led = _data;
int ret;
ret = regulator_disable(led->regulator);
if (ret)
- dev_err(dev, "Failed to disable supply: %d\n", ret);
+ dev_err(led->dev, "Failed to disable supply: %d\n", ret);
}
static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
@@ -233,7 +234,7 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
dev_err(dev, "Failed to enable supply: %d\n", ret);
} else {
ret = devm_add_action_or_reset(dev,
- regulator_disable_action, dev);
+ regulator_disable_action, led);
if (ret)
return ret;
}
@@ -290,6 +291,7 @@ static int ktd2692_probe(struct platform_device *pdev)
if (!led)
return -ENOMEM;
+ led->dev = &pdev->dev;
fled_cdev = &led->fled_cdev;
led_cdev = &fled_cdev->led_cdev;
led->props.timing = ktd2692_timing;
diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
index 165035a8826c..181dfd49c6ad 100644
--- a/drivers/leds/led-class-flash.c
+++ b/drivers/leds/led-class-flash.c
@@ -187,26 +187,23 @@ static ssize_t flash_fault_show(struct device *dev,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
- u32 fault, mask = 0x1;
- char *pbuf = buf;
- int i, ret, buf_len;
+ u32 fault = 0, mask = 0x1;
+ int i, ret;
ret = led_get_flash_fault(fled_cdev, &fault);
if (ret < 0)
return -EINVAL;
- *buf = '\0';
-
- for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) {
- if (fault & mask) {
- buf_len = sprintf(pbuf, "%s ",
- led_flash_fault_names[i]);
- pbuf += buf_len;
- }
+ for (ret = 0, i = 0; i < LED_NUM_FLASH_FAULTS; ++i) {
+ if (fault & mask)
+ ret += sysfs_emit_at(buf, ret, "%s%s",
+ (ret ? " " : ""),
+ led_flash_fault_names[i]);
mask <<= 1;
}
- return strlen(strcat(buf, "\n"));
+ ret += sysfs_emit_at(buf, ret, "\n");
+ return ret;
}
static DEVICE_ATTR_RO(flash_fault);
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index a51b0ed53886..00be3cb96c00 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -27,29 +27,19 @@ 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;
+ scoped_guard(mutex, &led_cdev->led_access) {
+ 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;
- mutex_unlock(&led_cdev->led_access);
+ led_update_brightness(led_cdev);
+ brightness = led_cdev->brightness;
+ }
return sysfs_emit(buf, "%u\n", brightness);
}
@@ -74,6 +64,9 @@ static ssize_t brightness_store(struct device *dev,
if (state == LED_OFF)
led_trigger_remove(led_cdev);
+ else
+ led_trigger_remove_hw_control(led_cdev);
+
led_set_brightness(led_cdev, state);
ret = size;
@@ -97,17 +90,6 @@ static ssize_t max_brightness_show(struct device *dev,
}
static DEVICE_ATTR_RO(max_brightness);
-#ifdef CONFIG_LEDS_TRIGGERS
-static const BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
-static const struct bin_attribute *const led_trigger_bin_attrs[] = {
- &bin_attr_trigger,
- NULL,
-};
-static const struct attribute_group led_trigger_group = {
- .bin_attrs = led_trigger_bin_attrs,
-};
-#endif
-
static struct attribute *led_class_attrs[] = {
&dev_attr_brightness.attr,
&dev_attr_max_brightness.attr,
@@ -578,6 +560,11 @@ int led_classdev_register_ext(struct device *parent,
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
led_cdev->brightness_hw_changed = -1;
#endif
+#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
+ if (led_cdev->flags & LED_TRIG_HW_CHANGED)
+ INIT_WORK(&led_cdev->trigger_hw_changed_work,
+ led_trigger_hw_control_changed_worker);
+#endif
if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL;
@@ -616,6 +603,11 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
if (IS_ERR_OR_NULL(led_cdev->dev))
return;
+#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
+ if (led_cdev->flags & LED_TRIG_HW_CHANGED)
+ disable_work_sync(&led_cdev->trigger_hw_changed_work);
+#endif
+
#ifdef CONFIG_LEDS_TRIGGERS
down_write(&led_cdev->trigger_lock);
if (led_cdev->trigger)
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 073c547068cc..80e281dce195 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -267,10 +267,7 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev,
set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
- if (invert)
- set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
- else
- clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
+ assign_bit(LED_BLINK_INVERT, &led_cdev->work_flags, invert);
led_blink_setup(led_cdev, delay_on, delay_off);
}
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index b1223218bda1..f16603b5ff44 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -7,9 +7,13 @@
* Author: Richard Purdie <rpurdie@openedhand.com>
*/
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/compiler.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/timer.h>
@@ -33,9 +37,38 @@ trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig)
return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type;
}
-ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
- const struct bin_attribute *bin_attr, char *buf,
- loff_t pos, size_t count)
+static bool __led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
+{
+ lockdep_assert_held(&led_cdev->trigger_lock);
+
+ if (!led_cdev->trigger)
+ return false;
+
+ if (!led_cdev->hw_control_trigger ||
+ strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name))
+ return false;
+
+ if (led_cdev->trigger->hw_offloaded)
+ return led_cdev->trigger->hw_offloaded(led_cdev);
+
+ dev_warn_once(led_cdev->dev,
+ "Hardware control trigger %s doesn't provide offloaded state\n",
+ led_cdev->trigger->name);
+
+ /* Otherwise assume private triggers are always offloaded. */
+ return led_cdev->trigger->trigger_type;
+}
+
+bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
+{
+ guard(rwsem_read)(&led_cdev->trigger_lock);
+ return __led_trigger_is_hw_controlled(led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_trigger_is_hw_controlled);
+
+static ssize_t trigger_write(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -78,7 +111,6 @@ unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
}
-EXPORT_SYMBOL_GPL(led_trigger_write);
__printf(3, 4)
static int led_trigger_snprintf(char *buf, ssize_t size, const char *fmt, ...)
@@ -130,9 +162,9 @@ static int led_trigger_format(char *buf, size_t size,
* attribute, which is not limited by length. This is _not_ good design, do not
* copy it.
*/
-ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
- const struct bin_attribute *attr, char *buf,
- loff_t pos, size_t count)
+static ssize_t trigger_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf,
+ loff_t pos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -160,10 +192,53 @@ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
return len;
}
-EXPORT_SYMBOL_GPL(led_trigger_read);
+static const BIN_ATTR_RW(trigger, 0);
+
+static const struct bin_attribute *const led_trigger_bin_attrs[] = {
+ &bin_attr_trigger,
+ NULL
+};
+
+static ssize_t trigger_may_offload_to_hw_show(struct device *dev,
+ const struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ bool offloaded = led_trigger_is_hw_controlled(led_cdev);
+
+ return sysfs_emit(buf, "%s%s%s\n",
+ offloaded ? "[" : "",
+ led_cdev->hw_control_trigger,
+ offloaded ? "]" : "");
+}
+static const DEVICE_ATTR_RO(trigger_may_offload_to_hw);
+
+static const struct attribute *const led_trigger_attrs[] = {
+ &dev_attr_trigger_may_offload_to_hw.attr,
+ NULL
+};
+
+static umode_t led_trigger_is_visible(struct kobject *kobj,
+ const struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_trigger_may_offload_to_hw.attr)
+ return led_cdev->hw_control_trigger ? attr->mode : 0;
+
+ return attr->mode;
+}
+
+const struct attribute_group led_trigger_group = {
+ .bin_attrs = led_trigger_bin_attrs,
+ .attrs_const = led_trigger_attrs,
+ .is_visible_const = led_trigger_is_visible,
+};
+EXPORT_SYMBOL_GPL(led_trigger_group);
/* Caller must ensure led_cdev->trigger_lock held */
-int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
+static int __led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig,
+ bool hw_triggered)
{
char *event = NULL;
char *envp[2];
@@ -194,7 +269,21 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
- led_set_brightness(led_cdev, LED_OFF);
+
+ /*
+ * Hardware may have selected a new brightness level during its
+ * hardware control transition, so only reset brightness if we
+ * are switching to another trigger or if the switching is not
+ * hardware triggered.
+ *
+ * Note that this does not apply to the error path, as running
+ * into the error path implies a none => private trigger
+ * transition. This hints that the LED driver and its private
+ * trigger must have some fundamental bugs, so the error path
+ * always turns off the LED to reset it to a certain state.
+ */
+ if (trig || !hw_triggered)
+ led_set_brightness(led_cdev, LED_OFF);
}
if (trig) {
spin_lock(&trig->leddev_list_lock);
@@ -258,6 +347,11 @@ err_activate:
return ret;
}
+
+int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
+{
+ return __led_trigger_set(led_cdev, trig, false);
+}
EXPORT_SYMBOL_GPL(led_trigger_set);
void led_trigger_remove(struct led_classdev *led_cdev)
@@ -268,6 +362,15 @@ void led_trigger_remove(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_trigger_remove);
+void led_trigger_remove_hw_control(struct led_classdev *led_cdev)
+{
+ guard(rwsem_write)(&led_cdev->trigger_lock);
+
+ if (__led_trigger_is_hw_controlled(led_cdev))
+ led_trigger_set(led_cdev, NULL);
+}
+EXPORT_SYMBOL_GPL(led_trigger_remove_hw_control);
+
static bool led_match_default_trigger(struct led_classdev *led_cdev,
struct led_trigger *trig)
{
@@ -403,6 +506,60 @@ int devm_led_trigger_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_led_trigger_register);
+#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
+
+static void led_trigger_do_hw_control_transition(struct led_classdev *led_cdev, bool activate,
+ struct led_trigger *hc_trig)
+{
+ if (activate && !led_cdev->trigger) /* "none" => private trigger. */
+ __led_trigger_set(led_cdev, hc_trig, true);
+ else if (!activate && led_cdev->trigger == hc_trig) /* private trigger => "none". */
+ __led_trigger_set(led_cdev, NULL, true);
+
+ /* Already in the desired state, or another trigger is active, ignore. */
+}
+
+void led_trigger_hw_control_changed_worker(struct work_struct *work)
+{
+ struct led_classdev *led_cdev =
+ container_of(work, struct led_classdev, trigger_hw_changed_work);
+ bool activate = READ_ONCE(led_cdev->trigger_hw_changed);
+
+ scoped_guard(rwsem_read, &triggers_list_lock) {
+ struct led_trigger *trig;
+
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ if (trig->trigger_type == led_cdev->trigger_type &&
+ !strcmp(trig->name, led_cdev->hw_control_trigger)) {
+ guard(rwsem_write)(&led_cdev->trigger_lock);
+
+ led_trigger_do_hw_control_transition(led_cdev, activate, trig);
+ return;
+ }
+ }
+ }
+
+ dev_warn(led_cdev->dev,
+ "Private trigger %s is not registered, can't toggle hardware control\n",
+ led_cdev->hw_control_trigger);
+}
+EXPORT_SYMBOL_GPL(led_trigger_hw_control_changed_worker);
+
+void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate)
+{
+ /* Restricted to private triggers. */
+ if (WARN_ON(!(led_cdev->flags & LED_TRIG_HW_CHANGED) ||
+ !led_cdev->hw_control_trigger || !led_cdev->trigger_type))
+ return;
+
+ WRITE_ONCE(led_cdev->trigger_hw_changed, activate);
+
+ schedule_work(&led_cdev->trigger_hw_changed_work);
+}
+EXPORT_SYMBOL_GPL(led_trigger_notify_hw_control_changed);
+
+#endif /* CONFIG_LEDS_TRIGGERS_HW_CHANGED */
+
/* Simple LED Trigger Interface */
void led_trigger_event(struct led_trigger *trig,
diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c
index 1844d0cd5f52..0e93aa28a8b1 100644
--- a/drivers/leds/leds-cros_ec.c
+++ b/drivers/leds/leds-cros_ec.c
@@ -85,12 +85,18 @@ static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev)
return cros_ec_led_send_cmd(priv->cros_ec, &arg);
}
+static bool cros_ec_led_trigger_hw_offloaded(struct led_classdev *led_cdev)
+{
+ return true;
+}
+
static struct led_hw_trigger_type cros_ec_led_trigger_type;
static struct led_trigger cros_ec_led_trigger = {
.name = "chromeos-auto",
.trigger_type = &cros_ec_led_trigger_type,
.activate = cros_ec_led_trigger_activate,
+ .hw_offloaded = cros_ec_led_trigger_hw_offloaded,
};
static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev,
diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
index 6c8d6b833260..285085d27453 100644
--- a/drivers/leds/leds-is31fl32xx.c
+++ b/drivers/leds/leds-is31fl32xx.c
@@ -378,23 +378,23 @@ static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
}
static int is31fl32xx_parse_child_dt(const struct device *dev,
- const struct device_node *child,
+ const struct fwnode_handle *child,
struct is31fl32xx_led_data *led_data)
{
struct led_classdev *cdev = &led_data->cdev;
int ret = 0;
u32 reg;
- ret = of_property_read_u32(child, "reg", &reg);
+ ret = fwnode_property_read_u32(child, "reg", &reg);
if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
dev_err(dev,
- "Child node %pOF does not have a valid reg property\n",
+ "Child node %pfwP does not have a valid reg property\n",
child);
return -EINVAL;
}
led_data->channel = reg;
- of_property_read_u32(child, "led-max-microamp", &led_data->max_microamp);
+ fwnode_property_read_u32(child, "led-max-microamp", &led_data->max_microamp);
cdev->brightness_set_blocking = is31fl32xx_brightness_set;
@@ -422,7 +422,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
int ret = 0;
if ((cdef->output_frequency_setting_reg != IS31FL32XX_REG_NONE) &&
- of_property_read_bool(dev_of_node(dev), "issi,22khz-pwm")) {
+ device_property_read_bool(dev, "issi,22khz-pwm")) {
ret = is31fl32xx_write(priv, cdef->output_frequency_setting_reg,
IS31FL32XX_PWM_FREQUENCY_22KHZ);
@@ -433,7 +433,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
}
}
- for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_init_data init_data = {};
struct is31fl32xx_led_data *led_data =
&priv->leds[priv->num_leds];
@@ -451,17 +451,17 @@ static int is31fl32xx_parse_dt(struct device *dev,
led_data->channel);
if (other_led_data) {
dev_err(dev,
- "Node %pOF 'reg' conflicts with another LED\n",
+ "Node %pfwP 'reg' conflicts with another LED\n",
child);
return -EINVAL;
}
- init_data.fwnode = of_fwnode_handle(child);
+ init_data.fwnode = child;
ret = devm_led_classdev_register_ext(dev, &led_data->cdev,
&init_data);
if (ret) {
- dev_err(dev, "Failed to register LED for %pOF: %d\n",
+ dev_err(dev, "Failed to register LED for %pfwP: %d\n",
child, ret);
return ret;
}
@@ -576,7 +576,7 @@ static int is31fl32xx_probe(struct i2c_client *client)
cdef = device_get_match_data(dev);
- count = of_get_available_child_count(dev_of_node(dev));
+ count = device_get_child_node_count(dev);
if (!count)
return -EINVAL;
diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c
index 8d2678dc9e4f..9c58d54c5783 100644
--- a/drivers/leds/leds-lm3692x.c
+++ b/drivers/leds/leds-lm3692x.c
@@ -276,9 +276,11 @@ static int lm3692x_leds_enable(struct lm3692x_led *led)
ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_ENABLE_MASK,
enable_state | LM3692X_DEVICE_EN);
+ if (ret)
+ goto out;
led->enabled = true;
- return ret;
+ return 0;
out:
dev_err(&led->client->dev, "Fail writing initialization values\n");
diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c
index 69f064781f69..f6e4227de903 100644
--- a/drivers/leds/leds-lp8860.c
+++ b/drivers/leds/leds-lp8860.c
@@ -7,6 +7,7 @@
* Author: Dan Murphy <dmurphy@ti.com>
*/
+#include <linux/cleanup.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
@@ -274,7 +275,6 @@ static int lp8860_probe(struct i2c_client *client)
int ret;
struct lp8860_led *led;
struct device_node *np = dev_of_node(&client->dev);
- struct device_node *child_node;
struct led_init_data init_data = {};
struct gpio_desc *enable_gpio;
@@ -282,7 +282,8 @@ static int lp8860_probe(struct i2c_client *client)
if (!led)
return -ENOMEM;
- child_node = of_get_next_available_child(np, NULL);
+ struct device_node *child_node __free(device_node) =
+ of_get_next_available_child(np, NULL);
if (!child_node)
return -EINVAL;
diff --git a/drivers/leds/leds-ltc3208.c b/drivers/leds/leds-ltc3208.c
new file mode 100644
index 000000000000..64091f29f18a
--- /dev/null
+++ b/drivers/leds/leds-ltc3208.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LED driver for Analog Devices LTC3208 Multi-Display Driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ *
+ * Author: Jan Carlo Roleda <jancarlo.roleda@analog.com>
+ *
+ */
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+/* Registers */
+#define LTC3208_REG_A_GRNRED_CURRENT 0x1 /* Green and Red current DAC */
+#define LTC3208_REG_B_AUXBLU_CURRENT 0x2 /* AUX and Blue current DAC */
+#define LTC3208_REG_C_MAIN_CURRENT 0x3 /* Main current DAC */
+#define LTC3208_REG_D_SUB_CURRENT 0x4 /* Sub current DAC */
+#define LTC3208_REG_E_AUX_SELECT 0x5 /* AUX DAC Select */
+#define LTC3208_AUX1_MASK GENMASK(1, 0)
+#define LTC3208_AUX2_MASK GENMASK(3, 2)
+#define LTC3208_AUX3_MASK GENMASK(5, 4)
+#define LTC3208_AUX4_MASK GENMASK(7, 6)
+#define LTC3208_REG_F_CAM_CURRENT 0x6 /* CAM (High and Low) current DAC */
+#define LTC3208_REG_G_OPT 0x7 /* Device Options */
+#define LTC3208_OPT_CPO_MASK GENMASK(7, 6)
+#define LTC3208_OPT_DIS_RGBDROP BIT(3)
+#define LTC3208_OPT_DIS_CAMHILO BIT(2)
+#define LTC3208_OPT_EN_RGBS BIT(1)
+
+#define LTC3208_MAX_BRIGHTNESS_4BIT 0xF
+#define LTC3208_MAX_BRIGHTNESS_8BIT 0xFF
+
+#define LTC3208_NUM_AUX_LEDS 4 /* Number of configurable aux channels */
+#define LTC3208_NUM_REGS 7 /* Number of available registers to access */
+
+enum ltc3208_aux_channel {
+ LTC3208_AUX_CHAN_AUX = 0,
+ LTC3208_AUX_CHAN_MAIN,
+ LTC3208_AUX_CHAN_SUB,
+ LTC3208_AUX_CHAN_CAM
+};
+
+enum ltc3208_channel {
+ LTC3208_CHAN_MAIN = 0,
+ LTC3208_CHAN_SUB,
+ LTC3208_CHAN_AUX,
+ LTC3208_CHAN_CAML,
+ LTC3208_CHAN_CAMH,
+ LTC3208_CHAN_RED,
+ LTC3208_CHAN_BLUE,
+ LTC3208_CHAN_GREEN,
+ LTC3208_CHAN_N_COUNT,
+};
+
+static const char *const ltc3208_dt_aux_channels[] = {
+ "adi,aux1-channel",
+ "adi,aux2-channel",
+ "adi,aux3-channel",
+ "adi,aux4-channel"
+};
+
+static const char *const ltc3208_aux_opt[] = { "aux", "main", "sub", "cam" };
+
+struct ltc3208_led {
+ struct led_classdev cdev;
+ struct regmap_field *rfield;
+};
+
+static const struct reg_default ltc3208_reg_defaults[] = {
+ { LTC3208_REG_A_GRNRED_CURRENT, 0 },
+ { LTC3208_REG_B_AUXBLU_CURRENT, 0 },
+ { LTC3208_REG_C_MAIN_CURRENT, 0 },
+ { LTC3208_REG_D_SUB_CURRENT, 0 },
+ { LTC3208_REG_E_AUX_SELECT, 0 },
+ { LTC3208_REG_F_CAM_CURRENT, 0 },
+ { LTC3208_REG_G_OPT, 0 }
+};
+
+static const struct regmap_config ltc3208_regmap_cfg = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = LTC3208_REG_G_OPT,
+ .cache_type = REGCACHE_FLAT_S,
+ .reg_defaults = ltc3208_reg_defaults,
+ .num_reg_defaults = LTC3208_NUM_REGS,
+};
+
+static const struct reg_field ltc3208_led_reg_field[LTC3208_CHAN_N_COUNT] = {
+ [LTC3208_CHAN_MAIN] = REG_FIELD(LTC3208_REG_C_MAIN_CURRENT, 0, 7),
+ [LTC3208_CHAN_SUB] = REG_FIELD(LTC3208_REG_D_SUB_CURRENT, 0, 7),
+ [LTC3208_CHAN_BLUE] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 0, 3),
+ [LTC3208_CHAN_AUX] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 4, 7),
+ [LTC3208_CHAN_CAML] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 0, 3),
+ [LTC3208_CHAN_CAMH] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 4, 7),
+ [LTC3208_CHAN_RED] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 0, 3),
+ [LTC3208_CHAN_GREEN] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 4, 7),
+};
+
+static int ltc3208_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness)
+{
+ struct ltc3208_led *led = container_of(led_cdev, struct ltc3208_led, cdev);
+ u8 current_level = brightness;
+
+ return regmap_field_write(led->rfield, current_level);
+}
+
+static int ltc3208_probe(struct i2c_client *client)
+{
+ enum ltc3208_aux_channel aux_channels[LTC3208_NUM_AUX_LEDS];
+ DECLARE_BITMAP(registered_chans, LTC3208_CHAN_N_COUNT) = { };
+ struct regmap *regmap;
+ bool disable_rgb_aux4_dropout_signal;
+ bool disable_camhl_pin;
+ bool set_sub_control_pin;
+ int ret;
+ u8 dev_options;
+
+ regmap = devm_regmap_init_i2c(client, &ltc3208_regmap_cfg);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap), "Failed to initialize regmap");
+
+ regcache_mark_dirty(regmap);
+ ret = regcache_sync(regmap);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to sync register cache");
+
+ disable_camhl_pin = device_property_read_bool(&client->dev, "adi,disable-camhl-pin");
+
+ set_sub_control_pin = device_property_read_bool(&client->dev, "adi,cfg-enrgbs-pin");
+
+ disable_rgb_aux4_dropout_signal =
+ device_property_read_bool(&client->dev, "adi,disable-rgb-aux4-dropout");
+
+ dev_options = FIELD_PREP(LTC3208_OPT_EN_RGBS, set_sub_control_pin) |
+ FIELD_PREP(LTC3208_OPT_DIS_CAMHILO, disable_camhl_pin) |
+ FIELD_PREP(LTC3208_OPT_CPO_MASK, 0) |
+ FIELD_PREP(LTC3208_OPT_DIS_RGBDROP, disable_rgb_aux4_dropout_signal);
+
+ ret = regmap_write(regmap, LTC3208_REG_G_OPT, dev_options);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to set device options register");
+
+ /* Initialize AUX channel configurations */
+ for (int i = 0; i < LTC3208_NUM_AUX_LEDS; i++) {
+ /* Default value (AUX) if property is not present in DT */
+ if (!device_property_present(&client->dev, ltc3208_dt_aux_channels[i])) {
+ aux_channels[i] = LTC3208_AUX_CHAN_AUX;
+ continue;
+ }
+
+ ret = device_property_match_property_string(&client->dev,
+ ltc3208_dt_aux_channels[i],
+ ltc3208_aux_opt, LTC3208_NUM_AUX_LEDS);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "Error reading AUX Channel %d", i);
+
+ aux_channels[i] = ret;
+ }
+
+ dev_options = FIELD_PREP(LTC3208_AUX1_MASK, aux_channels[0]) |
+ FIELD_PREP(LTC3208_AUX2_MASK, aux_channels[1]) |
+ FIELD_PREP(LTC3208_AUX3_MASK, aux_channels[2]) |
+ FIELD_PREP(LTC3208_AUX4_MASK, aux_channels[3]);
+
+ ret = regmap_write(regmap, LTC3208_REG_E_AUX_SELECT, dev_options);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Error writing to aux channel register");
+
+ device_for_each_child_node_scoped(&client->dev, child) {
+ struct ltc3208_led *led;
+ struct led_init_data init_data = { };
+ u32 chan;
+
+ ret = fwnode_property_read_u32(child, "reg", &chan);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to get register value of LED");
+ if (chan >= LTC3208_CHAN_N_COUNT)
+ return dev_err_probe(&client->dev, -EINVAL, "%u is an invalid LED ID",
+ chan);
+ if (test_and_set_bit(chan, registered_chans))
+ return dev_err_probe(&client->dev, -EINVAL, "%u is already registered",
+ chan);
+
+ led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->rfield = devm_regmap_field_alloc(&client->dev, regmap,
+ ltc3208_led_reg_field[chan]);
+ if (IS_ERR(led->rfield))
+ return dev_err_probe(&client->dev, PTR_ERR(led->rfield),
+ "Cannot allocate regmap field");
+ led->cdev.brightness_set_blocking = ltc3208_led_set_brightness;
+ led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_4BIT;
+
+ if (chan == LTC3208_CHAN_MAIN || chan == LTC3208_CHAN_SUB)
+ led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_8BIT;
+
+ init_data.fwnode = child;
+
+ ret = devm_led_classdev_register_ext(&client->dev, &led->cdev, &init_data);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to register LED %u", chan);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id ltc3208_match_table[] = {
+ { .compatible = "adi,ltc3208" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ltc3208_match_table);
+
+static struct i2c_driver ltc3208_driver = {
+ .driver = {
+ .name = "ltc3208",
+ .of_match_table = ltc3208_match_table,
+ },
+ .probe = ltc3208_probe,
+};
+module_i2c_driver(ltc3208_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jan Carlo Roleda <jancarlo.roleda@analog.com>");
+MODULE_DESCRIPTION("LTC3208 LED Driver");
diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c
index 1e2054c1bf80..4fd803c95989 100644
--- a/drivers/leds/leds-max77705.c
+++ b/drivers/leds/leds-max77705.c
@@ -160,7 +160,6 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw
struct max77705_led *led;
struct led_classdev *cdev;
struct mc_subled *info;
- struct fwnode_handle *child;
struct led_init_data init_data = {};
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
@@ -191,7 +190,7 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw
cdev->brightness_set_blocking = max77705_led_brightness_set_multi;
cdev->blink_set = max77705_rgb_blink;
- fwnode_for_each_child_node(np, child) {
+ fwnode_for_each_child_node_scoped(np, child) {
ret = max77705_parse_subled(dev, child, &info[i]);
if (ret < 0)
return ret;
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index b2e081c8f139..1133fa348e24 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -298,12 +298,15 @@ static enum pca9532_state pca9532_getled(struct pca9532_led *led)
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
- char reg;
- enum pca9532_state ret;
+ int reg;
+ enum pca9532_state ret = PCA9532_OFF;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
- ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
+ if (reg >= 0)
+ ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
+ else
+ dev_warn(&client->dev, "failed to read LED register: %d\n", reg);
mutex_unlock(&data->update_lock);
return ret;
}
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c
index 273383351ba0..e4af7a3b639f 100644
--- a/drivers/leds/leds-pca955x.c
+++ b/drivers/leds/leds-pca955x.c
@@ -79,6 +79,17 @@ struct pca955x_chipdef {
int blink_div; /* PSC divider */
};
+struct pca955x_led {
+ struct pca955x *pca955x;
+ struct led_classdev led_cdev;
+ int led_num; /* 0 .. 15 potentially */
+ u32 type;
+ enum led_default_state default_state;
+ struct fwnode_handle *fwnode;
+};
+
+#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
+
static const struct pca955x_chipdef pca955x_chipdefs[] = {
[pca9550] = {
.bits = 2,
@@ -114,7 +125,6 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = {
struct pca955x {
struct mutex lock;
- struct pca955x_led *leds;
const struct pca955x_chipdef *chipdef;
struct i2c_client *client;
unsigned long active_blink;
@@ -123,22 +133,7 @@ struct pca955x {
#ifdef CONFIG_LEDS_PCA955X_GPIO
struct gpio_chip gpio;
#endif
-};
-
-struct pca955x_led {
- struct pca955x *pca955x;
- struct led_classdev led_cdev;
- int led_num; /* 0 .. 15 potentially */
- u32 type;
- enum led_default_state default_state;
- struct fwnode_handle *fwnode;
-};
-
-#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
-
-struct pca955x_platform_data {
- struct pca955x_led *leds;
- int num_leds;
+ struct pca955x_led leds[];
};
/* 8 bits per input register */
@@ -530,37 +525,27 @@ static int pca955x_gpio_direction_output(struct gpio_chip *gc,
}
#endif /* CONFIG_LEDS_PCA955X_GPIO */
-static struct pca955x_platform_data *
-pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip)
+static int pca955x_parse_fwnode(struct pca955x *pca955x)
{
- struct pca955x_platform_data *pdata;
+ struct device *dev = &pca955x->client->dev;
+ u8 bits = pca955x->chipdef->bits;
struct pca955x_led *led;
struct fwnode_handle *child;
int count;
- count = device_get_child_node_count(&client->dev);
- if (count > chip->bits)
- return ERR_PTR(-ENODEV);
-
- pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
- if (!pdata)
- return ERR_PTR(-ENOMEM);
-
- pdata->leds = devm_kcalloc(&client->dev,
- chip->bits, sizeof(struct pca955x_led),
- GFP_KERNEL);
- if (!pdata->leds)
- return ERR_PTR(-ENOMEM);
+ count = device_get_child_node_count(dev);
+ if (count > bits)
+ return -ENODEV;
- device_for_each_child_node(&client->dev, child) {
+ device_for_each_child_node(dev, child) {
u32 reg;
int res;
res = fwnode_property_read_u32(child, "reg", &reg);
- if ((res != 0) || (reg >= chip->bits))
+ if ((res != 0) || (reg >= bits))
continue;
- led = &pdata->leds[reg];
+ led = &pca955x->leds[reg];
led->type = PCA955X_TYPE_LED;
led->fwnode = child;
led->default_state = led_init_default_state_get(child);
@@ -568,9 +553,7 @@ pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip)
fwnode_property_read_u32(child, "type", &led->type);
}
- pdata->num_leds = chip->bits;
-
- return pdata;
+ return 0;
}
static int pca955x_probe(struct i2c_client *client)
@@ -584,7 +567,6 @@ static int pca955x_probe(struct i2c_client *client)
u8 i, nls, psc0;
u8 ls1[4];
u8 ls2[4];
- struct pca955x_platform_data *pdata;
bool keep_psc0 = false;
bool set_default_label = false;
char default_label[4];
@@ -595,12 +577,6 @@ static int pca955x_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, -ENODEV, "unknown chip\n");
adapter = client->adapter;
- pdata = dev_get_platdata(&client->dev);
- if (!pdata) {
- pdata = pca955x_get_pdata(client, chip);
- if (IS_ERR(pdata))
- return PTR_ERR(pdata);
- }
/* Make sure the slave address / chip type combo given is possible */
if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) !=
@@ -616,22 +592,11 @@ static int pca955x_probe(struct i2c_client *client)
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
- if (pdata->num_leds != chip->bits) {
- dev_err(&client->dev,
- "board info claims %d LEDs on a %u-bit chip\n",
- pdata->num_leds, chip->bits);
- return -ENODEV;
- }
-
- pca955x = devm_kzalloc(&client->dev, sizeof(*pca955x), GFP_KERNEL);
+ pca955x = devm_kzalloc(&client->dev,
+ struct_size(pca955x, leds, chip->bits), GFP_KERNEL);
if (!pca955x)
return -ENOMEM;
- pca955x->leds = devm_kcalloc(&client->dev, chip->bits,
- sizeof(*pca955x_led), GFP_KERNEL);
- if (!pca955x->leds)
- return -ENOMEM;
-
i2c_set_clientdata(client, pca955x);
mutex_init(&pca955x->lock);
@@ -639,6 +604,10 @@ static int pca955x_probe(struct i2c_client *client)
pca955x->chipdef = chip;
pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS;
+ err = pca955x_parse_fwnode(pca955x);
+ if (err)
+ return err;
+
init_data.devname_mandatory = false;
init_data.devicename = "pca955x";
@@ -657,7 +626,6 @@ static int pca955x_probe(struct i2c_client *client)
pca955x_led = &pca955x->leds[i];
pca955x_led->led_num = i;
pca955x_led->pca955x = pca955x;
- pca955x_led->type = pdata->leds[i].type;
switch (pca955x_led->type) {
case PCA955X_TYPE_NONE:
@@ -671,16 +639,16 @@ static int pca955x_probe(struct i2c_client *client)
led->brightness_get = pca955x_led_get;
led->blink_set = pca955x_led_blink;
- if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF)
+ if (pca955x_led->default_state == LEDS_DEFSTATE_OFF)
ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF);
- else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON)
+ else if (pca955x_led->default_state == LEDS_DEFSTATE_ON)
ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON);
else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) {
keep_psc0 = true;
set_bit(i, &pca955x->active_blink);
}
- init_data.fwnode = pdata->leds[i].fwnode;
+ init_data.fwnode = pca955x_led->fwnode;
if (is_of_node(init_data.fwnode)) {
if (to_of_node(init_data.fwnode)->name[0] ==
diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c
index 605cf7497e4c..5363afbe93ea 100644
--- a/drivers/leds/leds-pca963x.c
+++ b/drivers/leds/leds-pca963x.c
@@ -208,10 +208,7 @@ static void pca963x_track_power_state(struct pca963x_led *led, unsigned int led_
{
unsigned long *leds_on = &led->chip->leds_on;
- if (brightness)
- set_bit(led_num, leds_on);
- else
- clear_bit(led_num, leds_on);
+ assign_bit(led_num, leds_on, brightness);
}
static int pca963x_sync_power_state(struct pca963x_led *led, unsigned long cached_leds)
diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c
index f24ca75c7cb1..c782339b1330 100644
--- a/drivers/leds/leds-ss4200.c
+++ b/drivers/leds/leds-ss4200.c
@@ -339,7 +339,7 @@ static void ich7_lpc_cleanup(struct device *dev)
/*
* The OS has determined that the LPC of the Intel ICH7 Southbridge is present
- * so we can retrive the required operational information and prepare the GPIO.
+ * so we can retrieve the required operational information and prepare the GPIO.
*/
static struct pci_dev *nas_gpio_pci_dev;
static int ich7_lpc_probe(struct pci_dev *dev,
diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c
index 168df5ecf27b..8c2e1d58ba0c 100644
--- a/drivers/leds/leds-st1202.c
+++ b/drivers/leds/leds-st1202.c
@@ -12,9 +12,11 @@
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
+#define ST1202_BLINK_DEFAULT_DELAY 500
#define ST1202_CHAN_DISABLE_ALL 0x00
#define ST1202_CHAN_ENABLE_HIGH 0x03
#define ST1202_CHAN_ENABLE_LOW 0x02
@@ -23,7 +25,8 @@
#define ST1202_CONFIG_REG_PATS BIT(7)
/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */
#define ST1202_CONFIG_REG_PATSR BIT(6)
-#define ST1202_CONFIG_REG_SHFT BIT(3)
+/* SHFT: Phase-shift delay enable */
+#define ST1202_CONFIG_REG_PHASE_SHIFT BIT(3)
#define ST1202_DEV_ENABLE 0x01
#define ST1202_DEV_ENABLE_ON BIT(0)
#define ST1202_DEV_ENABLE_RESET BIT(7)
@@ -57,7 +60,7 @@ static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev)
return container_of(cdev, struct st1202_led, led_cdev);
}
-static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
+static int st1202_read_reg(struct st1202_chip *chip, int reg, u8 *val)
{
struct device *dev = &chip->client->dev;
int ret;
@@ -68,11 +71,11 @@ static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
return ret;
}
- *val = (uint8_t)ret;
+ *val = (u8)ret;
return 0;
}
-static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
+static int st1202_write_reg(struct st1202_chip *chip, int reg, u8 val)
{
struct device *dev = &chip->client->dev;
int ret;
@@ -84,11 +87,27 @@ static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
return ret;
}
-static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
+static u8 st1202_milliseconds_to_prescaler(unsigned int value)
{
return value / ST1202_MILLIS_PATTERN_DUR_MIN;
}
+/* The channel current register is 8 bits wide, whatever max-brightness says */
+static u8 st1202_iled_max(struct led_classdev *led_cdev)
+{
+ return min_t(unsigned int, led_cdev->max_brightness, U8_MAX);
+}
+
+static u16 st1202_brightness_to_pwm(int brightness, unsigned int max_brightness)
+{
+ if (!max_brightness)
+ return 0;
+
+ brightness = clamp_t(int, brightness, LED_OFF, max_brightness);
+
+ return brightness * ST1202_PATTERN_PWM_FULL / max_brightness;
+}
+
static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
int pattern, unsigned int value)
{
@@ -125,7 +144,7 @@ static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
unsigned int value)
{
return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern),
- st1202_prescalar_to_miliseconds(value));
+ st1202_milliseconds_to_prescaler(value));
}
static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
@@ -160,27 +179,6 @@ static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool acti
return 0;
}
-static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
-{
- guard(mutex)(&chip->lock);
-
- return __st1202_channel_set(chip, led_num, active);
-}
-
-static void st1202_brightness_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct st1202_led *led = cdev_to_st1202_led(led_cdev);
- struct st1202_chip *chip = led->chip;
-
- guard(mutex)(&chip->lock);
-
- for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++)
- st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL);
- st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value);
- __st1202_channel_set(chip, led->led_num, !!value);
-}
-
static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
{
struct st1202_led *led = cdev_to_st1202_led(led_cdev);
@@ -197,8 +195,30 @@ static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
{
struct st1202_led *led = cdev_to_st1202_led(ldev);
+ struct st1202_chip *chip = led->chip;
+ int ret;
+
+ guard(mutex)(&chip->lock);
- return st1202_channel_set(led->chip, led->led_num, !!value);
+ /*
+ * The output of a channel is ILED x Pattern_PWM / 4095. Setting every
+ * PWM slot to full scale makes it equal ILED whatever the state of the
+ * sequencer, so the brightness takes effect without stopping the
+ * sequencer, which is global and would disturb the other channels.
+ */
+ for (int pattern = 0; pattern < ST1202_MAX_PATTERNS; pattern++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, pattern,
+ ST1202_PATTERN_PWM_FULL);
+ if (ret)
+ return ret;
+ }
+
+ ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num,
+ min_t(unsigned int, value, st1202_iled_max(ldev)));
+ if (ret)
+ return ret;
+
+ return __st1202_channel_set(chip, led->led_num, !!value);
}
static int st1202_led_pattern_clear(struct led_classdev *ldev)
@@ -209,16 +229,25 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev)
guard(mutex)(&chip->lock);
- ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
+ ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, LED_OFF);
+ if (ret != 0)
+ return ret;
+
+ ret = __st1202_channel_set(chip, led->led_num, false);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret != 0)
return ret;
- for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
- ret = st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL);
+ for (int pattern = 0; pattern < ST1202_MAX_PATTERNS; pattern++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, pattern,
+ ST1202_PATTERN_PWM_FULL);
if (ret != 0)
return ret;
- ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + patt, 0);
+ ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + pattern, 0);
if (ret != 0)
return ret;
}
@@ -227,34 +256,45 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev)
}
static int st1202_led_pattern_set(struct led_classdev *ldev,
- struct led_pattern *pattern,
+ struct led_pattern *patterns,
u32 len, int repeat)
{
struct st1202_led *led = cdev_to_st1202_led(ldev);
struct st1202_chip *chip = led->chip;
+ unsigned int max_brightness;
int ret;
+ max_brightness = st1202_iled_max(ldev);
+
if (len > ST1202_MAX_PATTERNS)
return -EINVAL;
- for (int patt = 0; patt < len; patt++) {
- if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
- pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
+ for (int pattern = 0; pattern < len; pattern++) {
+ if (patterns[pattern].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
+ patterns[pattern].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
return -EINVAL;
}
guard(mutex)(&chip->lock);
- ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret != 0)
return ret;
- for (int patt = 0; patt < len; patt++) {
- ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness);
+ for (int pattern = 0; pattern < len; pattern++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, pattern,
+ st1202_brightness_to_pwm(patterns[pattern].brightness,
+ max_brightness));
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_duration_pattern_write(chip, pattern, patterns[pattern].delta_t);
if (ret != 0)
return ret;
+ }
- ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t);
+ for (int pattern = len; pattern < ST1202_MAX_PATTERNS; pattern++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, LED_OFF);
if (ret != 0)
return ret;
}
@@ -263,18 +303,117 @@ static int st1202_led_pattern_set(struct led_classdev *ldev,
if (ret != 0)
return ret;
+ ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, max_brightness);
+ if (ret != 0)
+ return ret;
+
ret = __st1202_channel_set(chip, led->led_num, true);
if (ret != 0)
return ret;
- ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR |
- ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT));
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
+ ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS |
+ ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret != 0)
return ret;
return 0;
}
+static int st1202_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct st1202_led *led = cdev_to_st1202_led(led_cdev);
+ struct st1202_chip *chip = led->chip;
+ unsigned long on, off;
+ int ret;
+
+ if (!*delay_on)
+ *delay_on = ST1202_BLINK_DEFAULT_DELAY;
+ if (!*delay_off)
+ *delay_off = ST1202_BLINK_DEFAULT_DELAY;
+
+ on = *delay_on;
+ off = *delay_off;
+
+ on = clamp_val(on, ST1202_MILLIS_PATTERN_DUR_MIN, ST1202_MILLIS_PATTERN_DUR_MAX);
+ off = clamp_val(off, ST1202_MILLIS_PATTERN_DUR_MIN, ST1202_MILLIS_PATTERN_DUR_MAX);
+ on = roundup(on, ST1202_MILLIS_PATTERN_DUR_MIN);
+ off = roundup(off, ST1202_MILLIS_PATTERN_DUR_MIN);
+
+ guard(mutex)(&chip->lock);
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
+ if (ret)
+ return ret;
+
+ /* Zero out PWM for all other active channels to prevent them from blinking */
+ for (int chan = 0; chan < ST1202_MAX_LEDS; chan++) {
+ if (!chip->leds[chan].is_active || chan == led->led_num)
+ continue;
+
+ ret = st1202_pwm_pattern_write(chip, chan, 0, LED_OFF);
+ if (ret)
+ return ret;
+
+ ret = st1202_pwm_pattern_write(chip, chan, 1, LED_OFF);
+ if (ret)
+ return ret;
+ }
+
+ ret = st1202_pwm_pattern_write(chip, led->led_num, 0, ST1202_PATTERN_PWM_FULL);
+ if (ret)
+ return ret;
+
+ for (int pattern = 1; pattern < ST1202_MAX_PATTERNS; pattern++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, LED_OFF);
+ if (ret)
+ return ret;
+ }
+
+ ret = st1202_duration_pattern_write(chip, 0, on);
+ if (ret)
+ return ret;
+
+ ret = st1202_duration_pattern_write(chip, 1, off);
+ if (ret)
+ return ret;
+
+ for (int pattern = 2; pattern < ST1202_MAX_PATTERNS; pattern++) {
+ ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + pattern, 0);
+ if (ret)
+ return ret;
+ }
+
+ ret = st1202_write_reg(chip, ST1202_PATTERN_REP, U8_MAX);
+ if (ret)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, st1202_iled_max(led_cdev));
+ if (ret)
+ return ret;
+
+ ret = __st1202_channel_set(chip, led->led_num, true);
+ if (ret)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
+ ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS |
+ ST1202_CONFIG_REG_PHASE_SHIFT);
+ if (ret)
+ return ret;
+
+ *delay_on = on;
+ *delay_off = off;
+
+ return 0;
+}
+
+static void st1202_fwnode_put(void *data)
+{
+ fwnode_handle_put(data);
+}
+
static int st1202_dt_init(struct st1202_chip *chip)
{
struct device *dev = &chip->client->dev;
@@ -294,14 +433,18 @@ static int st1202_dt_init(struct st1202_chip *chip)
led = &chip->leds[reg];
led->is_active = true;
- led->fwnode = of_fwnode_handle(child);
+ led->fwnode = fwnode_handle_get(of_fwnode_handle(child));
+
+ err = devm_add_action_or_reset(dev, st1202_fwnode_put, led->fwnode);
+ if (err)
+ return err;
led->led_cdev.max_brightness = U8_MAX;
led->led_cdev.brightness_set_blocking = st1202_led_set;
led->led_cdev.pattern_set = st1202_led_pattern_set;
led->led_cdev.pattern_clear = st1202_led_pattern_clear;
led->led_cdev.default_trigger = "pattern";
- led->led_cdev.brightness_set = st1202_brightness_set;
+ led->led_cdev.blink_set = st1202_blink_set;
led->led_cdev.brightness_get = st1202_brightness_get;
}
@@ -330,7 +473,7 @@ static int st1202_setup(struct st1202_chip *chip)
return ret;
/* Enable phase-shift delay feature */
- ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret < 0)
return ret;
@@ -342,7 +485,7 @@ static int st1202_setup(struct st1202_chip *chip)
/* Duration of initialization */
usleep_range(6500, 10000);
- /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */
+ /* Deactivate all LEDs (channels); each is enabled when it is lit */
ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL);
if (ret < 0)
return ret;
@@ -389,11 +532,6 @@ static int st1202_probe(struct i2c_client *client)
if (!led->is_active)
continue;
- ret = st1202_channel_set(led->chip, led->led_num, true);
- if (ret < 0)
- return dev_err_probe(&client->dev, ret,
- "Failed to activate LED channel\n");
-
ret = st1202_led_pattern_clear(&led->led_cdev);
if (ret < 0)
return dev_err_probe(&client->dev, ret,
diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c
index d633ad519d0c..741850f35f19 100644
--- a/drivers/leds/leds-syscon.c
+++ b/drivers/leds/leds-syscon.c
@@ -56,7 +56,6 @@ static int syscon_led_probe(struct platform_device *pdev)
{
struct led_init_data init_data = {};
struct device *dev = &pdev->dev;
- struct device_node *np = dev_of_node(dev);
struct device *parent;
struct regmap *map;
struct syscon_led *sled;
@@ -81,13 +80,13 @@ static int syscon_led_probe(struct platform_device *pdev)
sled->map = map;
- if (of_property_read_u32(np, "reg", &sled->offset) &&
- of_property_read_u32(np, "offset", &sled->offset))
+ if (device_property_read_u32(dev, "reg", &sled->offset) &&
+ device_property_read_u32(dev, "offset", &sled->offset))
return -EINVAL;
- if (of_property_read_u32(np, "mask", &sled->mask))
+ if (device_property_read_u32(dev, "mask", &sled->mask))
return -EINVAL;
- init_data.fwnode = of_fwnode_handle(np);
+ init_data.fwnode = dev_fwnode(dev);
state = led_init_default_state_get(init_data.fwnode);
switch (state) {
diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
index ed6a47bbb44f..57128ae7e8fd 100644
--- a/drivers/leds/leds-turris-omnia.c
+++ b/drivers/leds/leds-turris-omnia.c
@@ -195,10 +195,16 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
err);
}
+static bool omnia_hwtrig_hw_offloaded(struct led_classdev *cdev)
+{
+ return true;
+}
+
static struct led_trigger omnia_hw_trigger = {
.name = "omnia-mcu",
.activate = omnia_hwtrig_activate,
.deactivate = omnia_hwtrig_deactivate,
+ .hw_offloaded = omnia_hwtrig_hw_offloaded,
.trigger_type = &omnia_hw_trigger_type,
};
@@ -251,6 +257,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
* by LED class from the linux,default-trigger property.
*/
cdev->default_trigger = omnia_hw_trigger.name;
+ cdev->hw_control_trigger = omnia_hw_trigger.name;
/* Put the LED into software mode */
ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) |
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index bee46651e068..6d00e6f44126 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -21,14 +21,10 @@ void led_init_core(struct led_classdev *led_cdev);
void led_stop_software_blink(struct led_classdev *led_cdev);
void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value);
void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value);
-ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
- const struct bin_attribute *attr, char *buf,
- loff_t pos, size_t count);
-ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
- const struct bin_attribute *bin_attr, char *buf,
- loff_t pos, size_t count);
+void led_trigger_hw_control_changed_worker(struct work_struct *work);
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
+extern const struct attribute_group led_trigger_group;
#endif /* __LEDS_H_INCLUDED */
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index d7d6518de30f..24ffe079f221 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -11,6 +11,7 @@
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -1342,7 +1343,7 @@ static int lpg_add_pwm(struct lpg *lpg)
return ret;
}
-static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
+static int lpg_parse_channel(struct lpg *lpg, struct fwnode_handle *fw,
struct lpg_channel **channel)
{
struct lpg_channel *chan;
@@ -1350,17 +1351,18 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
u32 reg;
int ret;
- ret = of_property_read_u32(np, "reg", &reg);
+ ret = fwnode_property_read_u32(fw, "reg", &reg);
if (ret || !reg || reg > lpg->num_channels)
- return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
+ return dev_err_probe(lpg->dev, -EINVAL,
+ "invalid \"reg\" of %s\n", fwnode_get_name(fw));
chan = &lpg->channels[reg - 1];
chan->in_use = true;
- ret = of_property_read_u32(np, "color", &color);
+ ret = fwnode_property_read_u32(fw, "color", &color);
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(lpg->dev, ret,
- "failed to parse \"color\" of %pOF\n", np);
+ "failed to parse \"color\" of %s\n", fwnode_get_name(fw));
chan->color = color;
@@ -1369,25 +1371,26 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
return 0;
}
-static int lpg_add_led(struct lpg *lpg, struct device_node *np)
+static int lpg_add_led(struct lpg *lpg, struct fwnode_handle *fw)
{
struct led_init_data init_data = {};
struct led_classdev *cdev;
struct mc_subled *info;
struct lpg_led *led;
+ const char *trigger;
const char *state;
int num_channels;
u32 color = 0;
int ret;
int i;
- ret = of_property_read_u32(np, "color", &color);
+ ret = fwnode_property_read_u32(fw, "color", &color);
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(lpg->dev, ret,
- "failed to parse \"color\" of %pOF\n", np);
+ "failed to parse \"color\" of %s\n", fwnode_get_name(fw));
if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
- num_channels = of_get_available_child_count(np);
+ num_channels = fwnode_get_child_node_count(fw);
else
num_channels = 1;
@@ -1403,7 +1406,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
if (!info)
return -ENOMEM;
i = 0;
- for_each_available_child_of_node_scoped(np, child) {
+ fwnode_for_each_available_child_node_scoped(fw, child) {
ret = lpg_parse_channel(lpg, child, &led->channels[i]);
if (ret < 0)
return ret;
@@ -1414,7 +1417,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
}
led->mcdev.subled_info = info;
- led->mcdev.num_colors = num_channels;
+ led->mcdev.num_colors = i;
cdev = &led->mcdev.led_cdev;
cdev->brightness_set_blocking = lpg_brightness_mc_set;
@@ -1426,7 +1429,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
cdev->pattern_clear = lpg_pattern_mc_clear;
}
} else {
- ret = lpg_parse_channel(lpg, np, &led->channels[0]);
+ ret = lpg_parse_channel(lpg, fw, &led->channels[0]);
if (ret < 0)
return ret;
@@ -1441,14 +1444,15 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
}
}
- cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
+ if (!fwnode_property_read_string(fw, "linux,default-trigger", &trigger))
+ cdev->default_trigger = trigger;
if (lpg->lpg_chan_sdam)
cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS;
else
cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
- if (!of_property_read_string(np, "default-state", &state) &&
+ if (!fwnode_property_read_string(fw, "default-state", &state) &&
!strcmp(state, "on"))
cdev->brightness = cdev->max_brightness;
else
@@ -1456,7 +1460,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
cdev->brightness_set_blocking(cdev, cdev->brightness);
- init_data.fwnode = of_fwnode_handle(np);
+ init_data.fwnode = fw;
if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
ret = devm_led_classdev_multicolor_register_ext(lpg->dev, &led->mcdev, &init_data);
@@ -1638,8 +1642,8 @@ static int lpg_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- for_each_available_child_of_node_scoped(pdev->dev.of_node, np) {
- ret = lpg_add_led(lpg, np);
+ device_for_each_child_node_scoped(&pdev->dev, child) {
+ ret = lpg_add_led(lpg, child);
if (ret)
return ret;
}
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index c11282a74b5a..a11d04ce4ab2 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -9,6 +9,16 @@ menuconfig LEDS_TRIGGERS
if LEDS_TRIGGERS
+config LEDS_TRIGGERS_HW_CHANGED
+ bool "LED hardware-initiated trigger transition support"
+ default LEDS_BRIGHTNESS_HW_CHANGED
+ help
+ This option enables support for hardware initiated hardware control
+ transitions, where the LED hardware autonomously switches between
+ "none" (i.e., no trigger) and its private trigger.
+
+ See Documentation/leds/leds-class.rst for details.
+
config LEDS_TRIGGER_TIMER
tristate "LED Timer Trigger"
help
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index 5b4e92c14dbb..5b0132484594 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -414,10 +414,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
return -EINVAL;
}
- if (state)
- set_bit(bit, &mode);
- else
- clear_bit(bit, &mode);
+ assign_bit(bit, &mode, state);
if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
(test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
@@ -798,10 +795,18 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
kfree(trigger_data);
}
+static bool netdev_trig_hw_offloaded(struct led_classdev *led_cdev)
+{
+ struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev);
+
+ return trigger_data->hw_control;
+}
+
static struct led_trigger netdev_led_trigger = {
.name = "netdev",
.activate = netdev_trig_activate,
.deactivate = netdev_trig_deactivate,
+ .hw_offloaded = netdev_trig_hw_offloaded,
.groups = netdev_trig_groups,
};