diff options
Diffstat (limited to 'drivers/leds/trigger/ledtrig-transient.c')
| -rw-r--r-- | drivers/leds/trigger/ledtrig-transient.c | 237 | 
1 files changed, 237 insertions, 0 deletions
diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c new file mode 100644 index 00000000000..e5abc00bb00 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -0,0 +1,237 @@ +/* + * LED Kernel Transient Trigger + * + * Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com> + * + * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's + * ledtrig-heartbeat.c + * Design and use-case input from Jonas Bonn <jonas@southpole.se> and + * Neil Brown <neilb@suse.de> + * + * 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. + * + */ +/* + * Transient trigger allows one shot timer activation. Please refer to + * Documentation/leds/ledtrig-transient.txt for details +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/leds.h> +#include "../leds.h" + +struct transient_trig_data { +	int activate; +	int state; +	int restore_state; +	unsigned long duration; +	struct timer_list timer; +}; + +static void transient_timer_function(unsigned long data) +{ +	struct led_classdev *led_cdev = (struct led_classdev *) data; +	struct transient_trig_data *transient_data = led_cdev->trigger_data; + +	transient_data->activate = 0; +	__led_set_brightness(led_cdev, transient_data->restore_state); +} + +static ssize_t transient_activate_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct transient_trig_data *transient_data = led_cdev->trigger_data; + +	return sprintf(buf, "%d\n", transient_data->activate); +} + +static ssize_t transient_activate_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct transient_trig_data *transient_data = led_cdev->trigger_data; +	unsigned long state; +	ssize_t ret; + +	ret = kstrtoul(buf, 10, &state); +	if (ret) +		return ret; + +	if (state != 1 && state != 0) +		return -EINVAL; + +	/* cancel the running timer */ +	if (state == 0 && transient_data->activate == 1) { +		del_timer(&transient_data->timer); +		transient_data->activate = state; +		__led_set_brightness(led_cdev, transient_data->restore_state); +		return size; +	} + +	/* start timer if there is no active timer */ +	if (state == 1 && transient_data->activate == 0 && +	    transient_data->duration != 0) { +		transient_data->activate = state; +		__led_set_brightness(led_cdev, transient_data->state); +		transient_data->restore_state = +		    (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; +		mod_timer(&transient_data->timer, +			  jiffies + transient_data->duration); +	} + +	/* state == 0 && transient_data->activate == 0 +		timer is not active - just return */ +	/* state == 1 && transient_data->activate == 1 +		timer is already active - just return */ + +	return size; +} + +static ssize_t transient_duration_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct transient_trig_data *transient_data = led_cdev->trigger_data; + +	return sprintf(buf, "%lu\n", transient_data->duration); +} + +static ssize_t transient_duration_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct transient_trig_data *transient_data = led_cdev->trigger_data; +	unsigned long state; +	ssize_t ret; + +	ret = kstrtoul(buf, 10, &state); +	if (ret) +		return ret; + +	transient_data->duration = state; +	return size; +} + +static ssize_t transient_state_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct transient_trig_data *transient_data = led_cdev->trigger_data; +	int state; + +	state = (transient_data->state == LED_FULL) ? 1 : 0; +	return sprintf(buf, "%d\n", state); +} + +static ssize_t transient_state_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct transient_trig_data *transient_data = led_cdev->trigger_data; +	unsigned long state; +	ssize_t ret; + +	ret = kstrtoul(buf, 10, &state); +	if (ret) +		return ret; + +	if (state != 1 && state != 0) +		return -EINVAL; + +	transient_data->state = (state == 1) ? LED_FULL : LED_OFF; +	return size; +} + +static DEVICE_ATTR(activate, 0644, transient_activate_show, +		   transient_activate_store); +static DEVICE_ATTR(duration, 0644, transient_duration_show, +		   transient_duration_store); +static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store); + +static void transient_trig_activate(struct led_classdev *led_cdev) +{ +	int rc; +	struct transient_trig_data *tdata; + +	tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL); +	if (!tdata) { +		dev_err(led_cdev->dev, +			"unable to allocate transient trigger\n"); +		return; +	} +	led_cdev->trigger_data = tdata; + +	rc = device_create_file(led_cdev->dev, &dev_attr_activate); +	if (rc) +		goto err_out; + +	rc = device_create_file(led_cdev->dev, &dev_attr_duration); +	if (rc) +		goto err_out_duration; + +	rc = device_create_file(led_cdev->dev, &dev_attr_state); +	if (rc) +		goto err_out_state; + +	setup_timer(&tdata->timer, transient_timer_function, +		    (unsigned long) led_cdev); +	led_cdev->activated = true; + +	return; + +err_out_state: +	device_remove_file(led_cdev->dev, &dev_attr_duration); +err_out_duration: +	device_remove_file(led_cdev->dev, &dev_attr_activate); +err_out: +	dev_err(led_cdev->dev, "unable to register transient trigger\n"); +	led_cdev->trigger_data = NULL; +	kfree(tdata); +} + +static void transient_trig_deactivate(struct led_classdev *led_cdev) +{ +	struct transient_trig_data *transient_data = led_cdev->trigger_data; + +	if (led_cdev->activated) { +		del_timer_sync(&transient_data->timer); +		__led_set_brightness(led_cdev, transient_data->restore_state); +		device_remove_file(led_cdev->dev, &dev_attr_activate); +		device_remove_file(led_cdev->dev, &dev_attr_duration); +		device_remove_file(led_cdev->dev, &dev_attr_state); +		led_cdev->trigger_data = NULL; +		led_cdev->activated = false; +		kfree(transient_data); +	} +} + +static struct led_trigger transient_trigger = { +	.name     = "transient", +	.activate = transient_trig_activate, +	.deactivate = transient_trig_deactivate, +}; + +static int __init transient_trig_init(void) +{ +	return led_trigger_register(&transient_trigger); +} + +static void __exit transient_trig_exit(void) +{ +	led_trigger_unregister(&transient_trigger); +} + +module_init(transient_trig_init); +module_exit(transient_trig_exit); + +MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>"); +MODULE_DESCRIPTION("Transient LED trigger"); +MODULE_LICENSE("GPL");  | 
