diff options
Diffstat (limited to 'drivers/leds/led-triggers.c')
| -rw-r--r-- | drivers/leds/led-triggers.c | 291 |
1 files changed, 192 insertions, 99 deletions
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 454fb0901f8..c3734f10fdd 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -1,7 +1,7 @@ /* * LED Triggers Core * - * Copyright 2005-2006 Openedhand Ltd. + * Copyright 2005-2007 Openedhand Ltd. * * Author: Richard Purdie <rpurdie@openedhand.com> * @@ -13,25 +13,27 @@ #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/rwsem.h> #include <linux/leds.h> +#include <linux/slab.h> #include "leds.h" /* * Nests outside led_cdev->trigger_lock */ -static DEFINE_RWLOCK(triggers_list_lock); +static DECLARE_RWSEM(triggers_list_lock); static LIST_HEAD(trigger_list); -ssize_t led_trigger_store(struct class_device *dev, const char *buf, - size_t count) + /* Used by LED Class */ + +ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { - struct led_classdev *led_cdev = class_get_devdata(dev); + struct led_classdev *led_cdev = dev_get_drvdata(dev); char trigger_name[TRIG_NAME_MAX]; struct led_trigger *trig; size_t len; @@ -44,37 +46,36 @@ ssize_t led_trigger_store(struct class_device *dev, const char *buf, 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); + led_trigger_remove(led_cdev); return count; } - read_lock(&triggers_list_lock); + down_read(&triggers_list_lock); list_for_each_entry(trig, &trigger_list, next_trig) { if (!strcmp(trigger_name, trig->name)) { - write_lock(&led_cdev->trigger_lock); + down_write(&led_cdev->trigger_lock); led_trigger_set(led_cdev, trig); - write_unlock(&led_cdev->trigger_lock); + up_write(&led_cdev->trigger_lock); - read_unlock(&triggers_list_lock); + up_read(&triggers_list_lock); return count; } } - read_unlock(&triggers_list_lock); + up_read(&triggers_list_lock); return -EINVAL; } +EXPORT_SYMBOL_GPL(led_trigger_store); - -ssize_t led_trigger_show(struct class_device *dev, char *buf) +ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, + char *buf) { - struct led_classdev *led_cdev = class_get_devdata(dev); + struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_trigger *trig; int len = 0; - read_lock(&triggers_list_lock); - read_lock(&led_cdev->trigger_lock); + down_read(&triggers_list_lock); + down_read(&led_cdev->trigger_lock); if (!led_cdev->trigger) len += sprintf(buf+len, "[none] "); @@ -88,54 +89,63 @@ ssize_t led_trigger_show(struct class_device *dev, char *buf) else len += sprintf(buf+len, "%s ", trig->name); } - read_unlock(&led_cdev->trigger_lock); - read_unlock(&triggers_list_lock); + up_read(&led_cdev->trigger_lock); + up_read(&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); -} +EXPORT_SYMBOL_GPL(led_trigger_show); /* Caller must ensure led_cdev->trigger_lock held */ -void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) +void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) { unsigned long flags; + char *event = NULL; + char *envp[2]; + const char *name; + + name = trig ? trig->name : "none"; + event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name); /* 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); + write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, + flags); + cancel_work_sync(&led_cdev->set_brightness_work); + led_stop_software_blink(led_cdev); if (led_cdev->trigger->deactivate) led_cdev->trigger->deactivate(led_cdev); + led_cdev->trigger = NULL; led_set_brightness(led_cdev, LED_OFF); } - 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); + if (trig) { + write_lock_irqsave(&trig->leddev_list_lock, flags); + list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); + write_unlock_irqrestore(&trig->leddev_list_lock, flags); + led_cdev->trigger = trig; + if (trig->activate) + trig->activate(led_cdev); } - led_cdev->trigger = trigger; + + if (event) { + envp[0] = event; + envp[1] = NULL; + kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp); + kfree(event); + } +} +EXPORT_SYMBOL_GPL(led_trigger_set); + +void led_trigger_remove(struct led_classdev *led_cdev) +{ + down_write(&led_cdev->trigger_lock); + led_trigger_set(led_cdev, NULL); + up_write(&led_cdev->trigger_lock); } +EXPORT_SYMBOL_GPL(led_trigger_remove); void led_trigger_set_default(struct led_classdev *led_cdev) { @@ -144,95 +154,178 @@ void led_trigger_set_default(struct led_classdev *led_cdev) if (!led_cdev->default_trigger) return; - read_lock(&triggers_list_lock); - write_lock(&led_cdev->trigger_lock); + 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_trigger_set(led_cdev, trig); } - write_unlock(&led_cdev->trigger_lock); - read_unlock(&triggers_list_lock); + up_write(&led_cdev->trigger_lock); + up_read(&triggers_list_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_set_default); + +void led_trigger_rename_static(const char *name, struct led_trigger *trig) +{ + /* new name must be on a temporary string to prevent races */ + BUG_ON(name == trig->name); + + down_write(&triggers_list_lock); + /* this assumes that trig->name was originaly allocated to + * non constant storage */ + strcpy((char *)trig->name, name); + up_write(&triggers_list_lock); } +EXPORT_SYMBOL_GPL(led_trigger_rename_static); -int led_trigger_register(struct led_trigger *trigger) +/* LED Trigger Interface */ + +int led_trigger_register(struct led_trigger *trig) { struct led_classdev *led_cdev; + struct led_trigger *_trig; - rwlock_init(&trigger->leddev_list_lock); - INIT_LIST_HEAD(&trigger->led_cdevs); + rwlock_init(&trig->leddev_list_lock); + INIT_LIST_HEAD(&trig->led_cdevs); + down_write(&triggers_list_lock); + /* Make sure the trigger's name isn't already in use */ + list_for_each_entry(_trig, &trigger_list, next_trig) { + if (!strcmp(_trig->name, trig->name)) { + up_write(&triggers_list_lock); + return -EEXIST; + } + } /* 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); + list_add_tail(&trig->next_trig, &trigger_list); + up_write(&triggers_list_lock); /* Register with any LEDs that have this as a default trigger */ - read_lock(&leds_list_lock); + down_read(&leds_list_lock); list_for_each_entry(led_cdev, &leds_list, node) { - write_lock(&led_cdev->trigger_lock); + down_write(&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); + !strcmp(led_cdev->default_trigger, trig->name)) + led_trigger_set(led_cdev, trig); + up_write(&led_cdev->trigger_lock); } - read_unlock(&leds_list_lock); + up_read(&leds_list_lock); return 0; } +EXPORT_SYMBOL_GPL(led_trigger_register); -void led_trigger_register_simple(const char *name, struct led_trigger **tp) +void led_trigger_unregister(struct led_trigger *trig) { - struct led_trigger *trigger; + struct led_classdev *led_cdev; + + if (list_empty_careful(&trig->next_trig)) + return; - trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); + /* Remove from the list of led triggers */ + down_write(&triggers_list_lock); + list_del_init(&trig->next_trig); + up_write(&triggers_list_lock); - if (trigger) { - trigger->name = name; - led_trigger_register(trigger); + /* Remove anyone actively using this trigger */ + down_read(&leds_list_lock); + list_for_each_entry(led_cdev, &leds_list, node) { + down_write(&led_cdev->trigger_lock); + if (led_cdev->trigger == trig) + led_trigger_set(led_cdev, NULL); + up_write(&led_cdev->trigger_lock); } - *tp = trigger; + up_read(&leds_list_lock); } +EXPORT_SYMBOL_GPL(led_trigger_unregister); -void led_trigger_unregister(struct led_trigger *trigger) +/* Simple LED Tigger Interface */ + +void led_trigger_event(struct led_trigger *trig, + enum led_brightness brightness) { 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); + if (!trig) + return; - /* 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); + read_lock(&trig->leddev_list_lock); + list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) + led_set_brightness(led_cdev, brightness); + read_unlock(&trig->leddev_list_lock); } +EXPORT_SYMBOL_GPL(led_trigger_event); -void led_trigger_unregister_simple(struct led_trigger *trigger) +static void led_trigger_blink_setup(struct led_trigger *trig, + unsigned long *delay_on, + unsigned long *delay_off, + int oneshot, + int invert) { - led_trigger_unregister(trigger); - kfree(trigger); + struct led_classdev *led_cdev; + + if (!trig) + return; + + read_lock(&trig->leddev_list_lock); + list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) { + if (oneshot) + led_blink_set_oneshot(led_cdev, delay_on, delay_off, + invert); + else + led_blink_set(led_cdev, delay_on, delay_off); + } + read_unlock(&trig->leddev_list_lock); } -/* 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); +void led_trigger_blink(struct led_trigger *trig, + unsigned long *delay_on, + unsigned long *delay_off) +{ + led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0); +} +EXPORT_SYMBOL_GPL(led_trigger_blink); -/* LED Trigger Interface */ -EXPORT_SYMBOL_GPL(led_trigger_register); -EXPORT_SYMBOL_GPL(led_trigger_unregister); +void led_trigger_blink_oneshot(struct led_trigger *trig, + unsigned long *delay_on, + unsigned long *delay_off, + int invert) +{ + led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert); +} +EXPORT_SYMBOL_GPL(led_trigger_blink_oneshot); -/* Simple LED Tigger Interface */ +void led_trigger_register_simple(const char *name, struct led_trigger **tp) +{ + struct led_trigger *trig; + int err; + + trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); + + if (trig) { + trig->name = name; + err = led_trigger_register(trig); + if (err < 0) { + kfree(trig); + trig = NULL; + pr_warn("LED trigger %s failed to register (%d)\n", + name, err); + } + } else { + pr_warn("LED trigger %s failed to register (no memory)\n", + name); + } + *tp = trig; +} EXPORT_SYMBOL_GPL(led_trigger_register_simple); + +void led_trigger_unregister_simple(struct led_trigger *trig) +{ + if (trig) + led_trigger_unregister(trig); + kfree(trig); +} EXPORT_SYMBOL_GPL(led_trigger_unregister_simple); -EXPORT_SYMBOL_GPL(led_trigger_event); MODULE_AUTHOR("Richard Purdie"); MODULE_LICENSE("GPL"); |
