led-class.c 10.0 KB
Newer Older
R
Richard Purdie 已提交
1 2 3 4
/*
 * LED Class Core
 *
 * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
5
 * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com>
R
Richard Purdie 已提交
6 7 8 9 10 11
 *
 * 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.
 */

12 13 14
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/err.h>
R
Richard Purdie 已提交
15
#include <linux/init.h>
16 17
#include <linux/kernel.h>
#include <linux/leds.h>
R
Richard Purdie 已提交
18
#include <linux/list.h>
19 20
#include <linux/module.h>
#include <linux/slab.h>
R
Richard Purdie 已提交
21
#include <linux/spinlock.h>
22
#include <linux/timer.h>
23
#include <uapi/linux/uleds.h>
R
Richard Purdie 已提交
24 25 26 27
#include "leds.h"

static struct class *leds_class;

28
static ssize_t brightness_show(struct device *dev,
29
		struct device_attribute *attr, char *buf)
R
Richard Purdie 已提交
30
{
31
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
R
Richard Purdie 已提交
32 33

	/* no lock needed for this */
34
	led_update_brightness(led_cdev);
R
Richard Purdie 已提交
35

S
Sven Wegener 已提交
36
	return sprintf(buf, "%u\n", led_cdev->brightness);
R
Richard Purdie 已提交
37 38
}

39
static ssize_t brightness_store(struct device *dev,
40
		struct device_attribute *attr, const char *buf, size_t size)
R
Richard Purdie 已提交
41
{
42
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
S
Shuah Khan 已提交
43
	unsigned long state;
44 45 46 47 48 49 50 51
	ssize_t ret;

	mutex_lock(&led_cdev->led_access);

	if (led_sysfs_is_disabled(led_cdev)) {
		ret = -EBUSY;
		goto unlock;
	}
R
Richard Purdie 已提交
52

S
Shuah Khan 已提交
53 54
	ret = kstrtoul(buf, 10, &state);
	if (ret)
55
		goto unlock;
56

S
Shuah Khan 已提交
57 58
	if (state == LED_OFF)
		led_trigger_remove(led_cdev);
59
	led_set_brightness(led_cdev, state);
60

61 62 63 64
	ret = size;
unlock:
	mutex_unlock(&led_cdev->led_access);
	return ret;
R
Richard Purdie 已提交
65
}
66
static DEVICE_ATTR_RW(brightness);
R
Richard Purdie 已提交
67

68
static ssize_t max_brightness_show(struct device *dev,
69 70 71 72 73 74
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	return sprintf(buf, "%u\n", led_cdev->max_brightness);
}
75
static DEVICE_ATTR_RO(max_brightness);
76

77
#ifdef CONFIG_LEDS_TRIGGERS
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
	&dev_attr_trigger.attr,
	NULL,
};
static const struct attribute_group led_trigger_group = {
	.attrs = led_trigger_attrs,
};
#endif

static struct attribute *led_class_attrs[] = {
	&dev_attr_brightness.attr,
	&dev_attr_max_brightness.attr,
	NULL,
};

static const struct attribute_group led_group = {
	.attrs = led_class_attrs,
};

static const struct attribute_group *led_groups[] = {
	&led_group,
#ifdef CONFIG_LEDS_TRIGGERS
	&led_trigger_group,
102
#endif
103
	NULL,
104
};
R
Richard Purdie 已提交
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
static ssize_t brightness_hw_changed_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->brightness_hw_changed == -1)
		return -ENODATA;

	return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
}

static DEVICE_ATTR_RO(brightness_hw_changed);

static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
{
	struct device *dev = led_cdev->dev;
	int ret;

	ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
	if (ret) {
		dev_err(dev, "Error creating brightness_hw_changed\n");
		return ret;
	}

	led_cdev->brightness_hw_changed_kn =
		sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
	if (!led_cdev->brightness_hw_changed_kn) {
		dev_err(dev, "Error getting brightness_hw_changed kn\n");
		device_remove_file(dev, &dev_attr_brightness_hw_changed);
		return -ENXIO;
	}

	return 0;
}

static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
{
	sysfs_put(led_cdev->brightness_hw_changed_kn);
	device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
}

void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
					       enum led_brightness brightness)
{
	if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
		return;

	led_cdev->brightness_hw_changed = brightness;
	sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
}
EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
#else
static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
{
	return 0;
}
static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
{
}
#endif

R
Richard Purdie 已提交
168 169 170 171 172 173 174
/**
 * led_classdev_suspend - suspend an led_classdev.
 * @led_cdev: the led_classdev to suspend.
 */
void led_classdev_suspend(struct led_classdev *led_cdev)
{
	led_cdev->flags |= LED_SUSPENDED;
175
	led_set_brightness_nopm(led_cdev, 0);
R
Richard Purdie 已提交
176 177 178 179 180 181 182 183 184
}
EXPORT_SYMBOL_GPL(led_classdev_suspend);

/**
 * led_classdev_resume - resume an led_classdev.
 * @led_cdev: the led_classdev to resume.
 */
void led_classdev_resume(struct led_classdev *led_cdev)
{
185
	led_set_brightness_nopm(led_cdev, led_cdev->brightness);
186 187 188 189

	if (led_cdev->flash_resume)
		led_cdev->flash_resume(led_cdev);

R
Richard Purdie 已提交
190 191 192 193
	led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);

194
#ifdef CONFIG_PM_SLEEP
195
static int led_suspend(struct device *dev)
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_suspend(led_cdev);

	return 0;
}

static int led_resume(struct device *dev)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_resume(led_cdev);

	return 0;
}
214
#endif
215

216
static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
217

218 219 220 221 222 223 224 225 226 227 228 229
static int match_name(struct device *dev, const void *data)
{
	if (!dev_name(dev))
		return 0;
	return !strcmp(dev_name(dev), (char *)data);
}

static int led_classdev_next_name(const char *init_name, char *name,
				  size_t len)
{
	unsigned int i = 0;
	int ret = 0;
230
	struct device *dev;
231 232 233

	strlcpy(name, init_name, len);

234 235 236
	while ((ret < len) &&
	       (dev = class_find_device(leds_class, NULL, name, match_name))) {
		put_device(dev);
237
		ret = snprintf(name, len, "%s_%u", init_name, ++i);
238
	}
239 240 241 242 243 244 245

	if (ret >= len)
		return -ENOMEM;

	return i;
}

R
Richard Purdie 已提交
246
/**
247 248 249
 * of_led_classdev_register - register a new object of led_classdev class.
 *
 * @parent: parent of LED device
R
Richard Purdie 已提交
250
 * @led_cdev: the led_classdev structure for this device.
251
 * @np: DT node describing this LED
R
Richard Purdie 已提交
252
 */
253 254
int of_led_classdev_register(struct device *parent, struct device_node *np,
			    struct led_classdev *led_cdev)
R
Richard Purdie 已提交
255
{
256
	char name[LED_MAX_NAME_SIZE];
257 258 259 260 261 262
	int ret;

	ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
	if (ret < 0)
		return ret;

263
	led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
264
				led_cdev, led_cdev->groups, "%s", name);
265
	if (IS_ERR(led_cdev->dev))
266
		return PTR_ERR(led_cdev->dev);
267
	led_cdev->dev->of_node = np;
R
Richard Purdie 已提交
268

269
	if (ret)
270
		dev_warn(parent, "Led %s renamed to %s due to name collision",
271 272
				led_cdev->name, dev_name(led_cdev->dev));

273 274 275 276 277 278 279 280
	if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
		ret = led_add_brightness_hw_changed(led_cdev);
		if (ret) {
			device_unregister(led_cdev->dev);
			return ret;
		}
	}

281
	led_cdev->work_flags = 0;
282 283
#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
284 285 286
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
	led_cdev->brightness_hw_changed = -1;
287
#endif
288
	mutex_init(&led_cdev->led_access);
R
Richard Purdie 已提交
289
	/* add to the list of leds */
290
	down_write(&leds_list_lock);
R
Richard Purdie 已提交
291
	list_add_tail(&led_cdev->node, &leds_list);
292
	up_write(&leds_list_lock);
R
Richard Purdie 已提交
293

294 295 296
	if (!led_cdev->max_brightness)
		led_cdev->max_brightness = LED_FULL;

297 298
	led_update_brightness(led_cdev);

299
	led_init_core(led_cdev);
300

301
#ifdef CONFIG_LEDS_TRIGGERS
302
	led_trigger_set_default(led_cdev);
303 304
#endif

305
	dev_dbg(parent, "Registered led device: %s\n",
306
			led_cdev->name);
R
Richard Purdie 已提交
307 308 309

	return 0;
}
310
EXPORT_SYMBOL_GPL(of_led_classdev_register);
R
Richard Purdie 已提交
311 312

/**
Q
Qinghuang Feng 已提交
313
 * led_classdev_unregister - unregisters a object of led_properties class.
H
Henrik Kretzschmar 已提交
314
 * @led_cdev: the led device to unregister
R
Richard Purdie 已提交
315 316 317
 *
 * Unregisters a previously registered via led_classdev_register object.
 */
318
void led_classdev_unregister(struct led_classdev *led_cdev)
R
Richard Purdie 已提交
319
{
320
#ifdef CONFIG_LEDS_TRIGGERS
321
	down_write(&led_cdev->trigger_lock);
322 323
	if (led_cdev->trigger)
		led_trigger_set(led_cdev, NULL);
324
	up_write(&led_cdev->trigger_lock);
325
#endif
R
Richard Purdie 已提交
326

327 328
	led_cdev->flags |= LED_UNREGISTERING;

329
	/* Stop blinking */
330
	led_stop_software_blink(led_cdev);
331

332
	led_set_brightness(led_cdev, LED_OFF);
333

334 335
	flush_work(&led_cdev->set_brightness_work);

336 337 338
	if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
		led_remove_brightness_hw_changed(led_cdev);

339
	device_unregister(led_cdev->dev);
R
Richard Purdie 已提交
340

341
	down_write(&leds_list_lock);
R
Richard Purdie 已提交
342
	list_del(&led_cdev->node);
343
	up_write(&leds_list_lock);
344 345

	mutex_destroy(&led_cdev->led_access);
R
Richard Purdie 已提交
346
}
347
EXPORT_SYMBOL_GPL(led_classdev_unregister);
R
Richard Purdie 已提交
348

349 350 351 352 353 354
static void devm_led_classdev_release(struct device *dev, void *res)
{
	led_classdev_unregister(*(struct led_classdev **)res);
}

/**
355 356 357
 * devm_of_led_classdev_register - resource managed led_classdev_register()
 *
 * @parent: parent of LED device
358 359
 * @led_cdev: the led_classdev structure for this device.
 */
360 361 362
int devm_of_led_classdev_register(struct device *parent,
				  struct device_node *np,
				  struct led_classdev *led_cdev)
363 364 365 366 367 368 369 370
{
	struct led_classdev **dr;
	int rc;

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

371
	rc = of_led_classdev_register(parent, np, led_cdev);
372 373 374 375 376 377 378 379 380 381
	if (rc) {
		devres_free(dr);
		return rc;
	}

	*dr = led_cdev;
	devres_add(parent, dr);

	return 0;
}
382
EXPORT_SYMBOL_GPL(devm_of_led_classdev_register);
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

static int devm_led_classdev_match(struct device *dev, void *res, void *data)
{
	struct led_cdev **p = res;

	if (WARN_ON(!p || !*p))
		return 0;

	return *p == data;
}

/**
 * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
 * @parent: The device to unregister.
 * @led_cdev: the led_classdev structure for this device.
 */
void devm_led_classdev_unregister(struct device *dev,
				  struct led_classdev *led_cdev)
{
	WARN_ON(devres_release(dev,
			       devm_led_classdev_release,
			       devm_led_classdev_match, led_cdev));
}
EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);

R
Richard Purdie 已提交
408 409 410 411 412
static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
413
	leds_class->pm = &leds_class_dev_pm_ops;
414
	leds_class->dev_groups = led_groups;
R
Richard Purdie 已提交
415 416 417 418 419 420 421 422 423 424 425 426 427 428
	return 0;
}

static void __exit leds_exit(void)
{
	class_destroy(leds_class);
}

subsys_initcall(leds_init);
module_exit(leds_exit);

MODULE_AUTHOR("John Lenz, Richard Purdie");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED Class Interface");