| Richard Purdie | c3bc995 | 2006-03-31 02:31:05 -0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * LED Triggers Core | 
|  | 3 | * | 
|  | 4 | * Copyright 2005-2006 Openedhand Ltd. | 
|  | 5 | * | 
|  | 6 | * Author: Richard Purdie <rpurdie@openedhand.com> | 
|  | 7 | * | 
|  | 8 | * This program is free software; you can redistribute it and/or modify | 
|  | 9 | * it under the terms of the GNU General Public License version 2 as | 
|  | 10 | * published by the Free Software Foundation. | 
|  | 11 | * | 
|  | 12 | */ | 
|  | 13 |  | 
| Richard Purdie | c3bc995 | 2006-03-31 02:31:05 -0800 | [diff] [blame] | 14 | #include <linux/module.h> | 
|  | 15 | #include <linux/kernel.h> | 
|  | 16 | #include <linux/init.h> | 
|  | 17 | #include <linux/list.h> | 
|  | 18 | #include <linux/spinlock.h> | 
|  | 19 | #include <linux/device.h> | 
|  | 20 | #include <linux/sysdev.h> | 
|  | 21 | #include <linux/timer.h> | 
|  | 22 | #include <linux/leds.h> | 
|  | 23 | #include "leds.h" | 
|  | 24 |  | 
|  | 25 | /* | 
|  | 26 | * Nests outside led_cdev->trigger_lock | 
|  | 27 | */ | 
| Ingo Molnar | 34af946 | 2006-06-27 02:53:55 -0700 | [diff] [blame] | 28 | static DEFINE_RWLOCK(triggers_list_lock); | 
| Richard Purdie | c3bc995 | 2006-03-31 02:31:05 -0800 | [diff] [blame] | 29 | static LIST_HEAD(trigger_list); | 
|  | 30 |  | 
|  | 31 | ssize_t led_trigger_store(struct class_device *dev, const char *buf, | 
|  | 32 | size_t count) | 
|  | 33 | { | 
|  | 34 | struct led_classdev *led_cdev = class_get_devdata(dev); | 
|  | 35 | char trigger_name[TRIG_NAME_MAX]; | 
|  | 36 | struct led_trigger *trig; | 
|  | 37 | size_t len; | 
|  | 38 |  | 
|  | 39 | trigger_name[sizeof(trigger_name) - 1] = '\0'; | 
|  | 40 | strncpy(trigger_name, buf, sizeof(trigger_name) - 1); | 
|  | 41 | len = strlen(trigger_name); | 
|  | 42 |  | 
|  | 43 | if (len && trigger_name[len - 1] == '\n') | 
|  | 44 | trigger_name[len - 1] = '\0'; | 
|  | 45 |  | 
|  | 46 | if (!strcmp(trigger_name, "none")) { | 
|  | 47 | write_lock(&led_cdev->trigger_lock); | 
|  | 48 | led_trigger_set(led_cdev, NULL); | 
|  | 49 | write_unlock(&led_cdev->trigger_lock); | 
|  | 50 | return count; | 
|  | 51 | } | 
|  | 52 |  | 
|  | 53 | read_lock(&triggers_list_lock); | 
|  | 54 | list_for_each_entry(trig, &trigger_list, next_trig) { | 
|  | 55 | if (!strcmp(trigger_name, trig->name)) { | 
|  | 56 | write_lock(&led_cdev->trigger_lock); | 
|  | 57 | led_trigger_set(led_cdev, trig); | 
|  | 58 | write_unlock(&led_cdev->trigger_lock); | 
|  | 59 |  | 
|  | 60 | read_unlock(&triggers_list_lock); | 
|  | 61 | return count; | 
|  | 62 | } | 
|  | 63 | } | 
|  | 64 | read_unlock(&triggers_list_lock); | 
|  | 65 |  | 
|  | 66 | return -EINVAL; | 
|  | 67 | } | 
|  | 68 |  | 
|  | 69 |  | 
|  | 70 | ssize_t led_trigger_show(struct class_device *dev, char *buf) | 
|  | 71 | { | 
|  | 72 | struct led_classdev *led_cdev = class_get_devdata(dev); | 
|  | 73 | struct led_trigger *trig; | 
|  | 74 | int len = 0; | 
|  | 75 |  | 
|  | 76 | read_lock(&triggers_list_lock); | 
|  | 77 | read_lock(&led_cdev->trigger_lock); | 
|  | 78 |  | 
|  | 79 | if (!led_cdev->trigger) | 
|  | 80 | len += sprintf(buf+len, "[none] "); | 
|  | 81 | else | 
|  | 82 | len += sprintf(buf+len, "none "); | 
|  | 83 |  | 
|  | 84 | list_for_each_entry(trig, &trigger_list, next_trig) { | 
|  | 85 | if (led_cdev->trigger && !strcmp(led_cdev->trigger->name, | 
|  | 86 | trig->name)) | 
|  | 87 | len += sprintf(buf+len, "[%s] ", trig->name); | 
|  | 88 | else | 
|  | 89 | len += sprintf(buf+len, "%s ", trig->name); | 
|  | 90 | } | 
|  | 91 | read_unlock(&led_cdev->trigger_lock); | 
|  | 92 | read_unlock(&triggers_list_lock); | 
|  | 93 |  | 
|  | 94 | len += sprintf(len+buf, "\n"); | 
|  | 95 | return len; | 
|  | 96 | } | 
|  | 97 |  | 
|  | 98 | void led_trigger_event(struct led_trigger *trigger, | 
|  | 99 | enum led_brightness brightness) | 
|  | 100 | { | 
|  | 101 | struct list_head *entry; | 
|  | 102 |  | 
|  | 103 | if (!trigger) | 
|  | 104 | return; | 
|  | 105 |  | 
|  | 106 | read_lock(&trigger->leddev_list_lock); | 
|  | 107 | list_for_each(entry, &trigger->led_cdevs) { | 
|  | 108 | struct led_classdev *led_cdev; | 
|  | 109 |  | 
|  | 110 | led_cdev = list_entry(entry, struct led_classdev, trig_list); | 
|  | 111 | led_set_brightness(led_cdev, brightness); | 
|  | 112 | } | 
|  | 113 | read_unlock(&trigger->leddev_list_lock); | 
|  | 114 | } | 
|  | 115 |  | 
|  | 116 | /* Caller must ensure led_cdev->trigger_lock held */ | 
|  | 117 | void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) | 
|  | 118 | { | 
|  | 119 | unsigned long flags; | 
|  | 120 |  | 
|  | 121 | /* Remove any existing trigger */ | 
|  | 122 | if (led_cdev->trigger) { | 
|  | 123 | write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags); | 
|  | 124 | list_del(&led_cdev->trig_list); | 
|  | 125 | write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags); | 
|  | 126 | if (led_cdev->trigger->deactivate) | 
|  | 127 | led_cdev->trigger->deactivate(led_cdev); | 
|  | 128 | } | 
|  | 129 | if (trigger) { | 
|  | 130 | write_lock_irqsave(&trigger->leddev_list_lock, flags); | 
|  | 131 | list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); | 
|  | 132 | write_unlock_irqrestore(&trigger->leddev_list_lock, flags); | 
|  | 133 | if (trigger->activate) | 
|  | 134 | trigger->activate(led_cdev); | 
|  | 135 | } | 
|  | 136 | led_cdev->trigger = trigger; | 
|  | 137 | } | 
|  | 138 |  | 
|  | 139 | void led_trigger_set_default(struct led_classdev *led_cdev) | 
|  | 140 | { | 
|  | 141 | struct led_trigger *trig; | 
|  | 142 |  | 
|  | 143 | if (!led_cdev->default_trigger) | 
|  | 144 | return; | 
|  | 145 |  | 
|  | 146 | read_lock(&triggers_list_lock); | 
|  | 147 | write_lock(&led_cdev->trigger_lock); | 
|  | 148 | list_for_each_entry(trig, &trigger_list, next_trig) { | 
|  | 149 | if (!strcmp(led_cdev->default_trigger, trig->name)) | 
|  | 150 | led_trigger_set(led_cdev, trig); | 
|  | 151 | } | 
|  | 152 | write_unlock(&led_cdev->trigger_lock); | 
|  | 153 | read_unlock(&triggers_list_lock); | 
|  | 154 | } | 
|  | 155 |  | 
|  | 156 | int led_trigger_register(struct led_trigger *trigger) | 
|  | 157 | { | 
|  | 158 | struct led_classdev *led_cdev; | 
|  | 159 |  | 
|  | 160 | rwlock_init(&trigger->leddev_list_lock); | 
|  | 161 | INIT_LIST_HEAD(&trigger->led_cdevs); | 
|  | 162 |  | 
|  | 163 | /* Add to the list of led triggers */ | 
|  | 164 | write_lock(&triggers_list_lock); | 
|  | 165 | list_add_tail(&trigger->next_trig, &trigger_list); | 
|  | 166 | write_unlock(&triggers_list_lock); | 
|  | 167 |  | 
|  | 168 | /* Register with any LEDs that have this as a default trigger */ | 
|  | 169 | read_lock(&leds_list_lock); | 
|  | 170 | list_for_each_entry(led_cdev, &leds_list, node) { | 
|  | 171 | write_lock(&led_cdev->trigger_lock); | 
|  | 172 | if (!led_cdev->trigger && led_cdev->default_trigger && | 
|  | 173 | !strcmp(led_cdev->default_trigger, trigger->name)) | 
|  | 174 | led_trigger_set(led_cdev, trigger); | 
|  | 175 | write_unlock(&led_cdev->trigger_lock); | 
|  | 176 | } | 
|  | 177 | read_unlock(&leds_list_lock); | 
|  | 178 |  | 
|  | 179 | return 0; | 
|  | 180 | } | 
|  | 181 |  | 
|  | 182 | void led_trigger_register_simple(const char *name, struct led_trigger **tp) | 
|  | 183 | { | 
|  | 184 | struct led_trigger *trigger; | 
|  | 185 |  | 
|  | 186 | trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); | 
|  | 187 |  | 
|  | 188 | if (trigger) { | 
|  | 189 | trigger->name = name; | 
|  | 190 | led_trigger_register(trigger); | 
|  | 191 | } | 
|  | 192 | *tp = trigger; | 
|  | 193 | } | 
|  | 194 |  | 
|  | 195 | void led_trigger_unregister(struct led_trigger *trigger) | 
|  | 196 | { | 
|  | 197 | struct led_classdev *led_cdev; | 
|  | 198 |  | 
|  | 199 | /* Remove from the list of led triggers */ | 
|  | 200 | write_lock(&triggers_list_lock); | 
|  | 201 | list_del(&trigger->next_trig); | 
|  | 202 | write_unlock(&triggers_list_lock); | 
|  | 203 |  | 
|  | 204 | /* Remove anyone actively using this trigger */ | 
|  | 205 | read_lock(&leds_list_lock); | 
|  | 206 | list_for_each_entry(led_cdev, &leds_list, node) { | 
|  | 207 | write_lock(&led_cdev->trigger_lock); | 
|  | 208 | if (led_cdev->trigger == trigger) | 
|  | 209 | led_trigger_set(led_cdev, NULL); | 
|  | 210 | write_unlock(&led_cdev->trigger_lock); | 
|  | 211 | } | 
|  | 212 | read_unlock(&leds_list_lock); | 
|  | 213 | } | 
|  | 214 |  | 
|  | 215 | void led_trigger_unregister_simple(struct led_trigger *trigger) | 
|  | 216 | { | 
|  | 217 | led_trigger_unregister(trigger); | 
|  | 218 | kfree(trigger); | 
|  | 219 | } | 
|  | 220 |  | 
|  | 221 | /* Used by LED Class */ | 
|  | 222 | EXPORT_SYMBOL_GPL(led_trigger_set); | 
|  | 223 | EXPORT_SYMBOL_GPL(led_trigger_set_default); | 
|  | 224 | EXPORT_SYMBOL_GPL(led_trigger_show); | 
|  | 225 | EXPORT_SYMBOL_GPL(led_trigger_store); | 
|  | 226 |  | 
|  | 227 | /* LED Trigger Interface */ | 
|  | 228 | EXPORT_SYMBOL_GPL(led_trigger_register); | 
|  | 229 | EXPORT_SYMBOL_GPL(led_trigger_unregister); | 
|  | 230 |  | 
|  | 231 | /* Simple LED Tigger Interface */ | 
|  | 232 | EXPORT_SYMBOL_GPL(led_trigger_register_simple); | 
|  | 233 | EXPORT_SYMBOL_GPL(led_trigger_unregister_simple); | 
|  | 234 | EXPORT_SYMBOL_GPL(led_trigger_event); | 
|  | 235 |  | 
|  | 236 | MODULE_AUTHOR("Richard Purdie"); | 
|  | 237 | MODULE_LICENSE("GPL"); | 
|  | 238 | MODULE_DESCRIPTION("LED Triggers Core"); |