led-triggers.c 6.4 KB
Newer Older
1 2 3
/*
 * LED Triggers Core
 *
4
 * Copyright 2005-2007 Openedhand Ltd.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * 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>
22
#include <linux/rwsem.h>
23 24 25 26 27 28
#include <linux/leds.h>
#include "leds.h"

/*
 * Nests outside led_cdev->trigger_lock
 */
29
static DECLARE_RWSEM(triggers_list_lock);
30 31
static LIST_HEAD(trigger_list);

32 33
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
34
{
35
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
36 37 38 39 40 41 42 43 44 45 46 47
	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")) {
48
		down_write(&led_cdev->trigger_lock);
49
		led_trigger_set(led_cdev, NULL);
50
		up_write(&led_cdev->trigger_lock);
51 52 53
		return count;
	}

54
	down_read(&triggers_list_lock);
55 56
	list_for_each_entry(trig, &trigger_list, next_trig) {
		if (!strcmp(trigger_name, trig->name)) {
57
			down_write(&led_cdev->trigger_lock);
58
			led_trigger_set(led_cdev, trig);
59
			up_write(&led_cdev->trigger_lock);
60

61
			up_read(&triggers_list_lock);
62 63 64
			return count;
		}
	}
65
	up_read(&triggers_list_lock);
66 67 68 69 70

	return -EINVAL;
}


71 72
ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
		char *buf)
73
{
74
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
75 76 77
	struct led_trigger *trig;
	int len = 0;

78 79
	down_read(&triggers_list_lock);
	down_read(&led_cdev->trigger_lock);
80 81 82 83 84 85 86 87 88 89 90 91 92

	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);
	}
93 94
	up_read(&led_cdev->trigger_lock);
	up_read(&triggers_list_lock);
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

	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);
130
		led_set_brightness(led_cdev, LED_OFF);
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
	}
	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;

149 150
	down_read(&triggers_list_lock);
	down_write(&led_cdev->trigger_lock);
151 152 153 154
	list_for_each_entry(trig, &trigger_list, next_trig) {
		if (!strcmp(led_cdev->default_trigger, trig->name))
			led_trigger_set(led_cdev, trig);
	}
155 156
	up_write(&led_cdev->trigger_lock);
	up_read(&triggers_list_lock);
157 158 159 160 161 162 163 164 165 166
}

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 */
167
	down_write(&triggers_list_lock);
168
	list_add_tail(&trigger->next_trig, &trigger_list);
169
	up_write(&triggers_list_lock);
170 171

	/* Register with any LEDs that have this as a default trigger */
172
	down_read(&leds_list_lock);
173
	list_for_each_entry(led_cdev, &leds_list, node) {
174
		down_write(&led_cdev->trigger_lock);
175 176 177
		if (!led_cdev->trigger && led_cdev->default_trigger &&
			    !strcmp(led_cdev->default_trigger, trigger->name))
			led_trigger_set(led_cdev, trigger);
178
		up_write(&led_cdev->trigger_lock);
179
	}
180
	up_read(&leds_list_lock);
181 182 183 184 185 186 187

	return 0;
}

void led_trigger_register_simple(const char *name, struct led_trigger **tp)
{
	struct led_trigger *trigger;
188
	int err;
189 190 191 192 193

	trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);

	if (trigger) {
		trigger->name = name;
194 195 196 197 198 199 200 201
		err = led_trigger_register(trigger);
		if (err < 0)
			printk(KERN_WARNING "LED trigger %s failed to register"
				" (%d)\n", name, err);
	} else
		printk(KERN_WARNING "LED trigger %s failed to register"
			" (no memory)\n", name);

202 203 204 205 206 207 208 209
	*tp = trigger;
}

void led_trigger_unregister(struct led_trigger *trigger)
{
	struct led_classdev *led_cdev;

	/* Remove from the list of led triggers */
210
	down_write(&triggers_list_lock);
211
	list_del(&trigger->next_trig);
212
	up_write(&triggers_list_lock);
213 214

	/* Remove anyone actively using this trigger */
215
	down_read(&leds_list_lock);
216
	list_for_each_entry(led_cdev, &leds_list, node) {
217
		down_write(&led_cdev->trigger_lock);
218 219
		if (led_cdev->trigger == trigger)
			led_trigger_set(led_cdev, NULL);
220
		up_write(&led_cdev->trigger_lock);
221
	}
222
	up_read(&leds_list_lock);
223 224 225 226
}

void led_trigger_unregister_simple(struct led_trigger *trigger)
{
227 228
	if (trigger)
		led_trigger_unregister(trigger);
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
	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");