aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorRichard Purdie <rpurdie@rpsys.net>2006-03-31 02:31:05 -0800
committerLinus Torvalds <torvalds@g5.osdl.org>2006-03-31 12:18:56 -0800
commitc3bc9956ec52fb2c70f29aa894d8eec766116584 (patch)
treec93aa56e6793cfa283d13a28cfa6c02a30f6c794 /drivers
parentc72a1d608dd0eb3d553a08bfdf1c0041bebaa8a0 (diff)
[PATCH] LED: add LED trigger tupport
Add support for LED triggers to the LED subsystem. "Triggers" are events which change the state of an LED. Two kinds of trigger are available, simple ones which can be added to exising code with minimum disruption and complex ones for implementing new or more complex functionality. Signed-off-by: Richard Purdie <rpurdie@rpsys.net> Cc: Russell King <rmk@arm.linux.org.uk> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/leds/Kconfig8
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/led-class.c20
-rw-r--r--drivers/leds/led-triggers.c239
-rw-r--r--drivers/leds/leds.h13
5 files changed, 281 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 106f9aeca04..a4d12ecaf6a 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -14,5 +14,13 @@ config LEDS_CLASS
This option enables the led sysfs class in /sys/class/leds. You'll
need this to do anything useful with LEDs. If unsure, say N.
+config LEDS_TRIGGERS
+ bool "LED Trigger support"
+ depends NEW_LEDS
+ help
+ This option enables trigger support for the leds class.
+ These triggers allow kernel events to drive the LEDs and can
+ be configured via sysfs. If unsure, say Y.
+
endmenu
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 8a62871437f..a9d8becd618 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -2,3 +2,4 @@
# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
+obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 0292df4101b..b0b5d05fadd 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -54,6 +54,9 @@ static ssize_t led_brightness_store(struct class_device *dev,
static CLASS_DEVICE_ATTR(brightness, 0644, led_brightness_show,
led_brightness_store);
+#ifdef CONFIG_LEDS_TRIGGERS
+static CLASS_DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
+#endif
/**
* led_classdev_suspend - suspend an led_classdev.
@@ -100,6 +103,15 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
list_add_tail(&led_cdev->node, &leds_list);
write_unlock(&leds_list_lock);
+#ifdef CONFIG_LEDS_TRIGGERS
+ rwlock_init(&led_cdev->trigger_lock);
+
+ led_trigger_set_default(led_cdev);
+
+ class_device_create_file(led_cdev->class_dev,
+ &class_device_attr_trigger);
+#endif
+
printk(KERN_INFO "Registered led device: %s\n",
led_cdev->class_dev->class_id);
@@ -117,6 +129,14 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
{
class_device_remove_file(led_cdev->class_dev,
&class_device_attr_brightness);
+#ifdef CONFIG_LEDS_TRIGGERS
+ class_device_remove_file(led_cdev->class_dev,
+ &class_device_attr_trigger);
+ write_lock(&led_cdev->trigger_lock);
+ if (led_cdev->trigger)
+ led_trigger_set(led_cdev, NULL);
+ write_unlock(&led_cdev->trigger_lock);
+#endif
class_device_unregister(led_cdev->class_dev);
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
new file mode 100644
index 00000000000..5e2cd8be119
--- /dev/null
+++ b/drivers/leds/led-triggers.c
@@ -0,0 +1,239 @@
+/*
+ * LED Triggers Core
+ *
+ * Copyright 2005-2006 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <rpurdie@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/sysdev.h>
+#include <linux/timer.h>
+#include <linux/leds.h>
+#include "leds.h"
+
+/*
+ * Nests outside led_cdev->trigger_lock
+ */
+static rwlock_t triggers_list_lock = RW_LOCK_UNLOCKED;
+static LIST_HEAD(trigger_list);
+
+ssize_t led_trigger_store(struct class_device *dev, const char *buf,
+ size_t count)
+{
+ struct led_classdev *led_cdev = class_get_devdata(dev);
+ char trigger_name[TRIG_NAME_MAX];
+ struct led_trigger *trig;
+ size_t len;
+
+ trigger_name[sizeof(trigger_name) - 1] = '\0';
+ strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
+ len = strlen(trigger_name);
+
+ if (len && trigger_name[len - 1] == '\n')
+ trigger_name[len - 1] = '\0';
+
+ if (!strcmp(trigger_name, "none")) {
+ write_lock(&led_cdev->trigger_lock);
+ led_trigger_set(led_cdev, NULL);
+ write_unlock(&led_cdev->trigger_lock);
+ return count;
+ }
+
+ read_lock(&triggers_list_lock);
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ if (!strcmp(trigger_name, trig->name)) {
+ write_lock(&led_cdev->trigger_lock);
+ led_trigger_set(led_cdev, trig);
+ write_unlock(&led_cdev->trigger_lock);
+
+ read_unlock(&triggers_list_lock);
+ return count;
+ }
+ }
+ read_unlock(&triggers_list_lock);
+
+ return -EINVAL;
+}
+
+
+ssize_t led_trigger_show(struct class_device *dev, char *buf)
+{
+ struct led_classdev *led_cdev = class_get_devdata(dev);
+ struct led_trigger *trig;
+ int len = 0;
+
+ read_lock(&triggers_list_lock);
+ read_lock(&led_cdev->trigger_lock);
+
+ if (!led_cdev->trigger)
+ len += sprintf(buf+len, "[none] ");
+ else
+ len += sprintf(buf+len, "none ");
+
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
+ trig->name))
+ len += sprintf(buf+len, "[%s] ", trig->name);
+ else
+ len += sprintf(buf+len, "%s ", trig->name);
+ }
+ read_unlock(&led_cdev->trigger_lock);
+ read_unlock(&triggers_list_lock);
+
+ len += sprintf(len+buf, "\n");
+ return len;
+}
+
+void led_trigger_event(struct led_trigger *trigger,
+ enum led_brightness brightness)
+{
+ struct list_head *entry;
+
+ if (!trigger)
+ return;
+
+ read_lock(&trigger->leddev_list_lock);
+ list_for_each(entry, &trigger->led_cdevs) {
+ struct led_classdev *led_cdev;
+
+ led_cdev = list_entry(entry, struct led_classdev, trig_list);
+ led_set_brightness(led_cdev, brightness);
+ }
+ read_unlock(&trigger->leddev_list_lock);
+}
+
+/* Caller must ensure led_cdev->trigger_lock held */
+void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
+{
+ unsigned long flags;
+
+ /* Remove any existing trigger */
+ if (led_cdev->trigger) {
+ write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
+ list_del(&led_cdev->trig_list);
+ write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags);
+ if (led_cdev->trigger->deactivate)
+ led_cdev->trigger->deactivate(led_cdev);
+ }
+ if (trigger) {
+ write_lock_irqsave(&trigger->leddev_list_lock, flags);
+ list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
+ write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
+ if (trigger->activate)
+ trigger->activate(led_cdev);
+ }
+ led_cdev->trigger = trigger;
+}
+
+void led_trigger_set_default(struct led_classdev *led_cdev)
+{
+ struct led_trigger *trig;
+
+ if (!led_cdev->default_trigger)
+ return;
+
+ read_lock(&triggers_list_lock);
+ write_lock(&led_cdev->trigger_lock);
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ if (!strcmp(led_cdev->default_trigger, trig->name))
+ led_trigger_set(led_cdev, trig);
+ }
+ write_unlock(&led_cdev->trigger_lock);
+ read_unlock(&triggers_list_lock);
+}
+
+int led_trigger_register(struct led_trigger *trigger)
+{
+ struct led_classdev *led_cdev;
+
+ rwlock_init(&trigger->leddev_list_lock);
+ INIT_LIST_HEAD(&trigger->led_cdevs);
+
+ /* Add to the list of led triggers */
+ write_lock(&triggers_list_lock);
+ list_add_tail(&trigger->next_trig, &trigger_list);
+ write_unlock(&triggers_list_lock);
+
+ /* Register with any LEDs that have this as a default trigger */
+ read_lock(&leds_list_lock);
+ list_for_each_entry(led_cdev, &leds_list, node) {
+ write_lock(&led_cdev->trigger_lock);
+ if (!led_cdev->trigger && led_cdev->default_trigger &&
+ !strcmp(led_cdev->default_trigger, trigger->name))
+ led_trigger_set(led_cdev, trigger);
+ write_unlock(&led_cdev->trigger_lock);
+ }
+ read_unlock(&leds_list_lock);
+
+ return 0;
+}
+
+void led_trigger_register_simple(const char *name, struct led_trigger **tp)
+{
+ struct led_trigger *trigger;
+
+ trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
+
+ if (trigger) {
+ trigger->name = name;
+ led_trigger_register(trigger);
+ }
+ *tp = trigger;
+}
+
+void led_trigger_unregister(struct led_trigger *trigger)
+{
+ struct led_classdev *led_cdev;
+
+ /* Remove from the list of led triggers */
+ write_lock(&triggers_list_lock);
+ list_del(&trigger->next_trig);
+ write_unlock(&triggers_list_lock);
+
+ /* Remove anyone actively using this trigger */
+ read_lock(&leds_list_lock);
+ list_for_each_entry(led_cdev, &leds_list, node) {
+ write_lock(&led_cdev->trigger_lock);
+ if (led_cdev->trigger == trigger)
+ led_trigger_set(led_cdev, NULL);
+ write_unlock(&led_cdev->trigger_lock);
+ }
+ read_unlock(&leds_list_lock);
+}
+
+void led_trigger_unregister_simple(struct led_trigger *trigger)
+{
+ led_trigger_unregister(trigger);
+ kfree(trigger);
+}
+
+/* Used by LED Class */
+EXPORT_SYMBOL_GPL(led_trigger_set);
+EXPORT_SYMBOL_GPL(led_trigger_set_default);
+EXPORT_SYMBOL_GPL(led_trigger_show);
+EXPORT_SYMBOL_GPL(led_trigger_store);
+
+/* LED Trigger Interface */
+EXPORT_SYMBOL_GPL(led_trigger_register);
+EXPORT_SYMBOL_GPL(led_trigger_unregister);
+
+/* Simple LED Tigger Interface */
+EXPORT_SYMBOL_GPL(led_trigger_register_simple);
+EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
+EXPORT_SYMBOL_GPL(led_trigger_event);
+
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("LED Triggers Core");
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index d26ca2f7e72..a715c4ed93f 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -28,4 +28,17 @@ static inline void led_set_brightness(struct led_classdev *led_cdev,
extern rwlock_t leds_list_lock;
extern struct list_head leds_list;
+#ifdef CONFIG_LEDS_TRIGGERS
+void led_trigger_set_default(struct led_classdev *led_cdev);
+void led_trigger_set(struct led_classdev *led_cdev,
+ struct led_trigger *trigger);
+#else
+#define led_trigger_set_default(x) do {} while(0)
+#define led_trigger_set(x, y) do {} while(0)
+#endif
+
+ssize_t led_trigger_store(struct class_device *dev, const char *buf,
+ size_t count);
+ssize_t led_trigger_show(struct class_device *dev, char *buf);
+
#endif /* __LEDS_H_INCLUDED */