leds: trigger: Add hw_offloaded() callback and provide trigger_may_offload_to_hw attribute

There are multiple triggers implementing hardware control. However, the
LED trigger core doesn't really know the hardware control (offloaded)
state since the coordination is done directly between the trigger and
the LED driver. It can only assume private triggers as offloaded and
generic ones as not offloaded.

Add an hw_offloaded() callback so that triggers can report their
offloaded states to the LED trigger core. When unimplemented, it
defaults to true for private triggers and false for generic ones to keep
the current behavior unchanged.

With that, provide a new attribute "trigger_may_offload_to_hw", so that
userspace can determine:

- if the LED device supports hardware control (supported => visible)
- which trigger is the hardware control trigger selected by the LED
  device and offloaded to hardware ("[foo_trigger]")

Note: the documentation describes the attribute as "returning a list"
despite the LED core currently only supports one hardware control
trigger per LED device. This is intentional to make the attribute
extensible in the future without breaking userspace. IOW, userspace
should parse the new attribute in the same way as
/sys/class/leds/<led>/trigger.

Acked-by: Ike Panhc <ikepanhc@gmail.com>
Signed-off-by: Rong Zhang <i@rong.moe>
Link: https://patch.msgid.link/20260921-leds-trigger-hw-changed-v7-5-fe3cdb6dec51@rong.moe
Signed-off-by: Lee Jones <lee@kernel.org>
This commit is contained in:
Rong Zhang 2026-09-21 03:40:19 +08:00 committed by Lee Jones
parent 43b83e7462
commit 8e04e5382a
4 changed files with 78 additions and 0 deletions

View File

@ -80,6 +80,27 @@ Description:
(which would often be configured in the device tree for the
hardware).
What: /sys/class/leds/<led>/trigger_may_offload_to_hw
Date: September 2026
KernelVersion: 7.4
Contact: linux-leds@vger.kernel.org
Description:
Names and states of triggers that may be offloaded to hardware.
Such triggers are also called "hardware control trigger" in some
context.
Only exists when the LED supports trigger offload.
Reading this file returns a list of triggers that are capable to
be offloaded. The optional brackets around the trigger name
indicate the state of the current trigger:
- `foo_trigger`: the trigger is not selected, or selected but
falls back to software blink for some reasons, e.g.,
incompatible trigger parameters.
- `[foo_trigger]`: the trigger is selected and offloaded to
hardware.
What: /sys/class/leds/<led>/inverted
Date: January 2011
KernelVersion: 2.6.38

View File

@ -242,6 +242,9 @@ ops and needs to declare specific support for the supported triggers.
With hw control we refer to the LED driven by hardware.
A sysfs attribute `trigger_may_offload_to_hw` is provided for userspace
to query supported triggers and their states.
LED driver must define the following value to support hw control:
- hw_control_trigger:
@ -298,6 +301,15 @@ LED driver must implement the following API to support hw control:
Returns a pointer to a struct device or NULL if nothing
is currently attached.
LED trigger should implement the following API to indicate hw control:
- hw_offloaded:
return a boolean indicating if the trigger is currently
offloaded to hardware.
If a trigger doesn't implement this callback, the default
value will be true for private triggers and false for generic
ones.
LED driver can activate additional modes by default to workaround the
impossibility of supporting each different mode on the supported trigger.
Examples are hardcoding the blink speed to a set interval, enable special
@ -311,6 +323,14 @@ the end use hw_control_set to activate hw control.
A trigger can use hw_control_get to check if a LED is already in hw control
and init their flags.
Alternatively, a private trigger can be implemented along with the LED driver if
the LED's hardware control doesn't fit any generic trigger. To associate the
private trigger with the LED classdev, their `trigger_type` must be the same. To
declare that the private trigger provides hardware control for the associated
LED classdev, set the `hw_control_trigger` string to the trigger's name. Since
both the LED classdev and the private trigger are in the same LED driver, it's
not necessary for them to coordinate via `hw_control_*` callbacks.
When the LED is in hw control, no software blink is possible and doing so
will effectively disable hw control.

View File

@ -42,6 +42,10 @@ static bool __led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
if (!led_cdev->trigger)
return false;
if (led_cdev->trigger->hw_offloaded)
return led_cdev->trigger->hw_offloaded(led_cdev);
/* Otherwise assume private triggers are always offloaded. */
return led_cdev->trigger->trigger_type;
}
@ -185,8 +189,40 @@ static const struct bin_attribute *const led_trigger_bin_attrs[] = {
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);

View File

@ -485,6 +485,7 @@ struct led_trigger {
const char *name;
int (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
bool (*hw_offloaded)(struct led_classdev *led_cdev);
/* Brightness set by led_trigger_event */
enum led_brightness brightness;