[RFC PATCH 1/1] leds: add generic inverted led trigger support

From: Tobias Schramm
Date: Sun Mar 08 2020 - 10:27:39 EST


This commit allows inversion of triggers by appending "-inverted" to the
name of a trigger.

This can be useful when dealing with device that lack separate LEDs for
indicating e.g. disk activity. With this commit applied the power LED
can be used as a disk activity indicator by setting its default state to
on and using the "disk-activity-inverted" trigger. The LED will then turn
off briefly when there is disk activity.

Signed-off-by: Tobias Schramm <t.schramm@xxxxxxxxxxx>
---
drivers/leds/led-triggers.c | 148 +++++++++++++++++++++++++++---------
include/linux/leds.h | 1 +
2 files changed, 111 insertions(+), 38 deletions(-)

diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index 79e30d2cb7a5..c40c58c6e345 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -27,14 +27,80 @@ LIST_HEAD(trigger_list);

/* Used by LED Class */

+#define TRIGGER_INVERT_SUFFIX "-inverted"
+
+/*
+ * Check suffix of trigger name agains TRIGGER_INVERT_SUFFIX
+ */
+static bool led_trigger_is_inverted(const char *trigname)
+{
+ if (strlen(trigname) >= strlen(TRIGGER_INVERT_SUFFIX)) {
+ return !strcmp(trigname + strlen(trigname) -
+ strlen(TRIGGER_INVERT_SUFFIX),
+ TRIGGER_INVERT_SUFFIX);
+ }
+
+ return false;
+}
+
+/*
+ * Get length of trigger name name without TRIGGER_INVERT_SUFFIX
+ */
+static size_t led_trigger_get_name_len(const char *trigname)
+{
+ // Subtract length of TRIGGER_INVERT_SUFFIX if trigger is inverted
+ if (led_trigger_is_inverted(trigname))
+ return strlen(trigname) - strlen(TRIGGER_INVERT_SUFFIX);
+ return strlen(trigname);
+}
+
+/*
+ * Find and set led trigger by name
+ */
+static int led_trigger_set_str_(struct led_classdev *led_cdev,
+ const char *trigname, bool lock)
+{
+ struct led_trigger *trig;
+ bool inverted = led_trigger_is_inverted(trigname);
+ size_t len = led_trigger_get_name_len(trigname);
+
+ down_read(&triggers_list_lock);
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ /* Compare trigger name without inversion suffix */
+ if (strlen(trig->name) == len &&
+ !strncmp(trigname, trig->name, len)) {
+ if (lock)
+ down_write(&led_cdev->trigger_lock);
+ led_trigger_set(led_cdev, trig);
+ if (inverted)
+ led_cdev->flags |= LED_INVERT_TRIGGER;
+ else
+ led_cdev->flags &= ~LED_INVERT_TRIGGER;
+ if (lock)
+ up_write(&led_cdev->trigger_lock);
+
+ up_read(&triggers_list_lock);
+ return 0;
+ }
+ }
+ /* we come here only if trigname matches no trigger */
+ up_read(&triggers_list_lock);
+ return -EINVAL;
+}
+
+#define led_trigger_set_str(cdev, name) led_trigger_set_str_(cdev, name, true)
+#define led_trigger_set_str_unlocked(cdev, name) \
+ led_trigger_set_str_(cdev, name, false)
+
+
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
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);
- struct led_trigger *trig;
int ret = count;
+ char *name;

mutex_lock(&led_cdev->led_access);

@@ -48,20 +114,10 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
goto unlock;
}

- down_read(&triggers_list_lock);
- list_for_each_entry(trig, &trigger_list, next_trig) {
- if (sysfs_streq(buf, trig->name)) {
- down_write(&led_cdev->trigger_lock);
- led_trigger_set(led_cdev, trig);
- up_write(&led_cdev->trigger_lock);
-
- up_read(&triggers_list_lock);
- goto unlock;
- }
- }
- /* we come here only if buf matches no trigger */
- ret = -EINVAL;
- up_read(&triggers_list_lock);
+ name = strim(buf);
+ ret = led_trigger_set_str(led_cdev, name);
+ if (!ret)
+ ret = count;

unlock:
mutex_unlock(&led_cdev->led_access);
@@ -93,12 +149,22 @@ static int led_trigger_format(char *buf, size_t size,
led_cdev->trigger ? "none" : "[none]");

list_for_each_entry(trig, &trigger_list, next_trig) {
- bool hit = led_cdev->trigger &&
- !strcmp(led_cdev->trigger->name, trig->name);
+ bool hit = led_cdev->trigger == trig;
+ bool inverted = led_cdev->flags & LED_INVERT_TRIGGER;
+
+ /* print non-inverted trigger */
+ len += led_trigger_snprintf(buf + len, size - len,
+ " %s%s%s",
+ hit && !inverted ? "[" : "",
+ trig->name,
+ hit && !inverted ? "]" : "");

+ /* print inverted trigger */
len += led_trigger_snprintf(buf + len, size - len,
- " %s%s%s", hit ? "[" : "",
- trig->name, hit ? "]" : "");
+ " %s%s"TRIGGER_INVERT_SUFFIX"%s",
+ hit && inverted ? "[" : "",
+ trig->name,
+ hit && inverted ? "]" : "");
}

len += led_trigger_snprintf(buf + len, size - len, "\n");
@@ -235,21 +301,15 @@ EXPORT_SYMBOL_GPL(led_trigger_remove);

void led_trigger_set_default(struct led_classdev *led_cdev)
{
- struct led_trigger *trig;
+ bool found;

if (!led_cdev->default_trigger)
return;

down_read(&triggers_list_lock);
- down_write(&led_cdev->trigger_lock);
- list_for_each_entry(trig, &trigger_list, next_trig) {
- if (!strcmp(led_cdev->default_trigger, trig->name)) {
- led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
- led_trigger_set(led_cdev, trig);
- break;
- }
- }
- up_write(&led_cdev->trigger_lock);
+ found = !led_trigger_set_str(led_cdev, led_cdev->default_trigger);
+ if (found)
+ led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
up_read(&triggers_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_set_default);
@@ -292,11 +352,14 @@ int led_trigger_register(struct led_trigger *trig)
/* Register with any LEDs that have this as a default trigger */
down_read(&leds_list_lock);
list_for_each_entry(led_cdev, &leds_list, node) {
+ bool found;
+
down_write(&led_cdev->trigger_lock);
- if (!led_cdev->trigger && led_cdev->default_trigger &&
- !strcmp(led_cdev->default_trigger, trig->name)) {
- led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
- led_trigger_set(led_cdev, trig);
+ if (!led_cdev->trigger && led_cdev->default_trigger) {
+ found = !led_trigger_set_str_unlocked(led_cdev,
+ led_cdev->default_trigger);
+ if (found)
+ led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
}
up_write(&led_cdev->trigger_lock);
}
@@ -369,8 +432,14 @@ void led_trigger_event(struct led_trigger *trig,
return;

read_lock(&trig->leddev_list_lock);
- list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list)
- led_set_brightness(led_cdev, brightness);
+ list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
+ /* Reverse brightness if LED is inverted */
+ if (led_cdev->flags & LED_INVERT_TRIGGER)
+ led_set_brightness(led_cdev,
+ led_cdev->max_brightness - brightness);
+ else
+ led_set_brightness(led_cdev, brightness);
+ }
read_unlock(&trig->leddev_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_event);
@@ -388,10 +457,13 @@ static void led_trigger_blink_setup(struct led_trigger *trig,

read_lock(&trig->leddev_list_lock);
list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
- if (oneshot)
+ bool trigger_inverted =
+ !!(led_cdev->flags & LED_INVERT_TRIGGER);
+ if (oneshot) {
+ /* use logical xnor to determine inversion parameter */
led_blink_set_oneshot(led_cdev, delay_on, delay_off,
- invert);
- else
+ (!!invert) == trigger_inverted);
+ } else
led_blink_set(led_cdev, delay_on, delay_off);
}
read_unlock(&trig->leddev_list_lock);
diff --git a/include/linux/leds.h b/include/linux/leds.h
index 75353e5f9d13..fa707170b69e 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -74,6 +74,7 @@ struct led_classdev {
#define LED_BRIGHT_HW_CHANGED BIT(21)
#define LED_RETAIN_AT_SHUTDOWN BIT(22)
#define LED_INIT_DEFAULT_TRIGGER BIT(23)
+#define LED_INVERT_TRIGGER BIT(24)

/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
--
2.24.1