led-triggers.c 8.8 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
 *
 * 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/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/timer.h>
20
#include <linux/rwsem.h>
21
#include <linux/leds.h>
22
#include <linux/slab.h>
23 24 25 26 27
#include "leds.h"

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

31 32
 /* Used by LED Class */

33 34
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
35
{
36
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
37
	struct led_trigger *trig;
38 39 40 41 42 43 44 45
	int ret = count;

	mutex_lock(&led_cdev->led_access);

	if (led_sysfs_is_disabled(led_cdev)) {
		ret = -EBUSY;
		goto unlock;
	}
46

47
	if (sysfs_streq(buf, "none")) {
48
		led_trigger_remove(led_cdev);
49
		goto unlock;
50 51
	}

52
	down_read(&triggers_list_lock);
53
	list_for_each_entry(trig, &trigger_list, next_trig) {
54
		if (sysfs_streq(buf, trig->name)) {
55
			down_write(&led_cdev->trigger_lock);
56
			led_trigger_set(led_cdev, trig);
57
			up_write(&led_cdev->trigger_lock);
58

59
			up_read(&triggers_list_lock);
60
			goto unlock;
61 62
		}
	}
63
	up_read(&triggers_list_lock);
64

65 66 67
unlock:
	mutex_unlock(&led_cdev->led_access);
	return ret;
68
}
69
EXPORT_SYMBOL_GPL(led_trigger_store);
70

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

	len += sprintf(len+buf, "\n");
	return len;
}
99
EXPORT_SYMBOL_GPL(led_trigger_show);
100 101

/* Caller must ensure led_cdev->trigger_lock held */
102
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
103 104
{
	unsigned long flags;
105 106 107 108 109 110
	char *event = NULL;
	char *envp[2];
	const char *name;

	name = trig ? trig->name : "none";
	event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);
111 112 113 114 115

	/* Remove any existing trigger */
	if (led_cdev->trigger) {
		write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
		list_del(&led_cdev->trig_list);
116 117
		write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
			flags);
118 119
		cancel_work_sync(&led_cdev->set_brightness_work);
		led_stop_software_blink(led_cdev);
120 121
		if (led_cdev->trigger->deactivate)
			led_cdev->trigger->deactivate(led_cdev);
122
		led_cdev->trigger = NULL;
123
		led_set_brightness(led_cdev, LED_OFF);
124
	}
125 126 127 128 129 130 131
	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);
132
	}
133 134 135 136 137 138 139

	if (event) {
		envp[0] = event;
		envp[1] = NULL;
		kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp);
		kfree(event);
	}
140
}
141
EXPORT_SYMBOL_GPL(led_trigger_set);
142

143 144 145 146 147 148
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);
}
149
EXPORT_SYMBOL_GPL(led_trigger_remove);
150

151 152 153 154 155 156 157
void led_trigger_set_default(struct led_classdev *led_cdev)
{
	struct led_trigger *trig;

	if (!led_cdev->default_trigger)
		return;

158 159
	down_read(&triggers_list_lock);
	down_write(&led_cdev->trigger_lock);
160 161 162 163
	list_for_each_entry(trig, &trigger_list, next_trig) {
		if (!strcmp(led_cdev->default_trigger, trig->name))
			led_trigger_set(led_cdev, trig);
	}
164 165
	up_write(&led_cdev->trigger_lock);
	up_read(&triggers_list_lock);
166
}
167 168
EXPORT_SYMBOL_GPL(led_trigger_set_default);

169 170 171 172 173 174 175 176 177 178 179 180 181
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);

182
/* LED Trigger Interface */
183

184
int led_trigger_register(struct led_trigger *trig)
185 186
{
	struct led_classdev *led_cdev;
187
	struct led_trigger *_trig;
188

189 190
	rwlock_init(&trig->leddev_list_lock);
	INIT_LIST_HEAD(&trig->led_cdevs);
191

192
	down_write(&triggers_list_lock);
193
	/* Make sure the trigger's name isn't already in use */
194 195
	list_for_each_entry(_trig, &trigger_list, next_trig) {
		if (!strcmp(_trig->name, trig->name)) {
196 197 198 199 200
			up_write(&triggers_list_lock);
			return -EEXIST;
		}
	}
	/* Add to the list of led triggers */
201
	list_add_tail(&trig->next_trig, &trigger_list);
202
	up_write(&triggers_list_lock);
203 204

	/* Register with any LEDs that have this as a default trigger */
205
	down_read(&leds_list_lock);
206
	list_for_each_entry(led_cdev, &leds_list, node) {
207
		down_write(&led_cdev->trigger_lock);
208
		if (!led_cdev->trigger && led_cdev->default_trigger &&
209 210
			    !strcmp(led_cdev->default_trigger, trig->name))
			led_trigger_set(led_cdev, trig);
211
		up_write(&led_cdev->trigger_lock);
212
	}
213
	up_read(&leds_list_lock);
214 215 216

	return 0;
}
217
EXPORT_SYMBOL_GPL(led_trigger_register);
218

219
void led_trigger_unregister(struct led_trigger *trig)
220 221 222
{
	struct led_classdev *led_cdev;

223 224 225
	if (list_empty_careful(&trig->next_trig))
		return;

226
	/* Remove from the list of led triggers */
227
	down_write(&triggers_list_lock);
228
	list_del_init(&trig->next_trig);
229
	up_write(&triggers_list_lock);
230 231

	/* Remove anyone actively using this trigger */
232
	down_read(&leds_list_lock);
233
	list_for_each_entry(led_cdev, &leds_list, node) {
234
		down_write(&led_cdev->trigger_lock);
235
		if (led_cdev->trigger == trig)
236
			led_trigger_set(led_cdev, NULL);
237
		up_write(&led_cdev->trigger_lock);
238
	}
239
	up_read(&leds_list_lock);
240
}
241
EXPORT_SYMBOL_GPL(led_trigger_unregister);
242

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
static void devm_led_trigger_release(struct device *dev, void *res)
{
	led_trigger_unregister(*(struct led_trigger **)res);
}

int devm_led_trigger_register(struct device *dev,
			      struct led_trigger *trig)
{
	struct led_trigger **dr;
	int rc;

	dr = devres_alloc(devm_led_trigger_release, sizeof(*dr),
			  GFP_KERNEL);
	if (!dr)
		return -ENOMEM;

	*dr = trig;

	rc = led_trigger_register(trig);
	if (rc)
		devres_free(dr);
	else
		devres_add(dev, dr);

	return rc;
}
EXPORT_SYMBOL_GPL(devm_led_trigger_register);

271 272
/* Simple LED Tigger Interface */

273
void led_trigger_event(struct led_trigger *trig,
274
			enum led_brightness brightness)
275
{
276
	struct led_classdev *led_cdev;
277

278
	if (!trig)
279 280
		return;

281
	read_lock(&trig->leddev_list_lock);
282
	list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list)
283
		led_set_brightness(led_cdev, brightness);
284
	read_unlock(&trig->leddev_list_lock);
285
}
286
EXPORT_SYMBOL_GPL(led_trigger_event);
287

288
static void led_trigger_blink_setup(struct led_trigger *trig,
289 290 291 292
			     unsigned long *delay_on,
			     unsigned long *delay_off,
			     int oneshot,
			     int invert)
293
{
294
	struct led_classdev *led_cdev;
295

296
	if (!trig)
297 298
		return;

299
	read_lock(&trig->leddev_list_lock);
300
	list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
301 302 303 304 305
		if (oneshot)
			led_blink_set_oneshot(led_cdev, delay_on, delay_off,
					      invert);
		else
			led_blink_set(led_cdev, delay_on, delay_off);
306
	}
307
	read_unlock(&trig->leddev_list_lock);
308
}
309 310 311 312 313 314 315

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);
}
316 317
EXPORT_SYMBOL_GPL(led_trigger_blink);

318 319 320 321 322 323 324 325 326
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);

327 328
void led_trigger_register_simple(const char *name, struct led_trigger **tp)
{
329
	struct led_trigger *trig;
330
	int err;
331

332
	trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
333

334 335 336
	if (trig) {
		trig->name = name;
		err = led_trigger_register(trig);
337
		if (err < 0) {
338 339
			kfree(trig);
			trig = NULL;
340 341
			pr_warn("LED trigger %s failed to register (%d)\n",
				name, err);
342
		}
343 344 345 346
	} else {
		pr_warn("LED trigger %s failed to register (no memory)\n",
			name);
	}
347
	*tp = trig;
348
}
349
EXPORT_SYMBOL_GPL(led_trigger_register_simple);
350

351
void led_trigger_unregister_simple(struct led_trigger *trig)
352
{
353 354 355
	if (trig)
		led_trigger_unregister(trig);
	kfree(trig);
356
}
357 358 359 360 361
EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);

MODULE_AUTHOR("Richard Purdie");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED Triggers Core");