/* * 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/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 DEFINE_RWLOCK(triggers_list_lock); 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); 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); } 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");