diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led index f6d4e17e1a3b..123e3a15b7d6 100644 --- a/Documentation/ABI/testing/sysfs-class-led +++ b/Documentation/ABI/testing/sysfs-class-led @@ -80,6 +80,27 @@ Description: (which would often be configured in the device tree for the hardware). +What: /sys/class/leds//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//inverted Date: January 2011 KernelVersion: 2.6.38 diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst index 3913966cfdac..ea478989aae2 100644 --- a/Documentation/leds/leds-class.rst +++ b/Documentation/leds/leds-class.rst @@ -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. diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index de6056bc80e6..38ce3350c870 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -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); diff --git a/include/linux/leds.h b/include/linux/leds.h index d778709f5b1b..9a0bfd985b46 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -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;