diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-26 08:55:14 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-26 08:55:14 -1000 |
| commit | 5b93dc1c0c61fd0fe575cb473e9e25f929fda509 (patch) | |
| tree | e9405ab03dda86826201dca3a7731924825bb982 /drivers/leds | |
| parent | d857e1f3faf998f2614dcdb52a90a27bc83bd71d (diff) | |
| parent | 59cf91cf87abb67a49b1bb7b9d3b06486abde8cf (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/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/leds/Makefile | 1 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-aat1290.c | 5 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-ktd2692.c | 10 | ||||
| -rw-r--r-- | drivers/leds/led-class-flash.c | 21 | ||||
| -rw-r--r-- | drivers/leds/led-class.c | 46 | ||||
| -rw-r--r-- | drivers/leds/led-core.c | 5 | ||||
| -rw-r--r-- | drivers/leds/led-triggers.c | 177 | ||||
| -rw-r--r-- | drivers/leds/leds-cros_ec.c | 6 | ||||
| -rw-r--r-- | drivers/leds/leds-is31fl32xx.c | 20 | ||||
| -rw-r--r-- | drivers/leds/leds-lm3692x.c | 4 | ||||
| -rw-r--r-- | drivers/leds/leds-lp8860.c | 5 | ||||
| -rw-r--r-- | drivers/leds/leds-ltc3208.c | 234 | ||||
| -rw-r--r-- | drivers/leds/leds-max77705.c | 3 | ||||
| -rw-r--r-- | drivers/leds/leds-pca9532.c | 9 | ||||
| -rw-r--r-- | drivers/leds/leds-pca955x.c | 94 | ||||
| -rw-r--r-- | drivers/leds/leds-pca963x.c | 5 | ||||
| -rw-r--r-- | drivers/leds/leds-ss4200.c | 2 | ||||
| -rw-r--r-- | drivers/leds/leds-st1202.c | 240 | ||||
| -rw-r--r-- | drivers/leds/leds-syscon.c | 9 | ||||
| -rw-r--r-- | drivers/leds/leds-turris-omnia.c | 7 | ||||
| -rw-r--r-- | drivers/leds/leds.h | 8 | ||||
| -rw-r--r-- | drivers/leds/rgb/leds-qcom-lpg.c | 38 | ||||
| -rw-r--r-- | drivers/leds/trigger/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-netdev.c | 13 |
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", ®); + ret = fwnode_property_read_u32(child, "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, <c3208_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", ®); - 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", ®); + ret = fwnode_property_read_u32(fw, "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, }; |
