1 /*
2  * LED Kernel Transient Trigger
3  *
4  * Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com>
5  *
6  * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's
7  * ledtrig-heartbeat.c
8  * Design and use-case input from Jonas Bonn <jonas@southpole.se> and
9  * Neil Brown <neilb@suse.de>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License version 2 as
13  * published by the Free Software Foundation.
14  *
15  */
16 /*
17  * Transient trigger allows one shot timer activation. Please refer to
18  * Documentation/leds/ledtrig-transient.txt for details
19 */
20 
21 #include <linux/module.h>
22 #include <linux/kernel.h>
23 #include <linux/init.h>
24 #include <linux/device.h>
25 #include <linux/slab.h>
26 #include <linux/timer.h>
27 #include <linux/leds.h>
28 #include "../leds.h"
29 
30 struct transient_trig_data {
31 	int activate;
32 	int state;
33 	int restore_state;
34 	unsigned long duration;
35 	struct timer_list timer;
36 };
37 
38 static void transient_timer_function(unsigned long data)
39 {
40 	struct led_classdev *led_cdev = (struct led_classdev *) data;
41 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
42 
43 	transient_data->activate = 0;
44 	__led_set_brightness(led_cdev, transient_data->restore_state);
45 }
46 
47 static ssize_t transient_activate_show(struct device *dev,
48 		struct device_attribute *attr, char *buf)
49 {
50 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
51 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
52 
53 	return sprintf(buf, "%d\n", transient_data->activate);
54 }
55 
56 static ssize_t transient_activate_store(struct device *dev,
57 		struct device_attribute *attr, const char *buf, size_t size)
58 {
59 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
60 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
61 	unsigned long state;
62 	ssize_t ret;
63 
64 	ret = kstrtoul(buf, 10, &state);
65 	if (ret)
66 		return ret;
67 
68 	if (state != 1 && state != 0)
69 		return -EINVAL;
70 
71 	/* cancel the running timer */
72 	if (state == 0 && transient_data->activate == 1) {
73 		del_timer(&transient_data->timer);
74 		transient_data->activate = state;
75 		__led_set_brightness(led_cdev, transient_data->restore_state);
76 		return size;
77 	}
78 
79 	/* start timer if there is no active timer */
80 	if (state == 1 && transient_data->activate == 0 &&
81 	    transient_data->duration != 0) {
82 		transient_data->activate = state;
83 		__led_set_brightness(led_cdev, transient_data->state);
84 		transient_data->restore_state =
85 		    (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL;
86 		mod_timer(&transient_data->timer,
87 			  jiffies + transient_data->duration);
88 	}
89 
90 	/* state == 0 && transient_data->activate == 0
91 		timer is not active - just return */
92 	/* state == 1 && transient_data->activate == 1
93 		timer is already active - just return */
94 
95 	return size;
96 }
97 
98 static ssize_t transient_duration_show(struct device *dev,
99 		struct device_attribute *attr, char *buf)
100 {
101 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
102 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
103 
104 	return sprintf(buf, "%lu\n", transient_data->duration);
105 }
106 
107 static ssize_t transient_duration_store(struct device *dev,
108 		struct device_attribute *attr, const char *buf, size_t size)
109 {
110 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
111 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
112 	unsigned long state;
113 	ssize_t ret;
114 
115 	ret = kstrtoul(buf, 10, &state);
116 	if (ret)
117 		return ret;
118 
119 	transient_data->duration = state;
120 	return size;
121 }
122 
123 static ssize_t transient_state_show(struct device *dev,
124 		struct device_attribute *attr, char *buf)
125 {
126 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
127 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
128 	int state;
129 
130 	state = (transient_data->state == LED_FULL) ? 1 : 0;
131 	return sprintf(buf, "%d\n", state);
132 }
133 
134 static ssize_t transient_state_store(struct device *dev,
135 		struct device_attribute *attr, const char *buf, size_t size)
136 {
137 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
138 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
139 	unsigned long state;
140 	ssize_t ret;
141 
142 	ret = kstrtoul(buf, 10, &state);
143 	if (ret)
144 		return ret;
145 
146 	if (state != 1 && state != 0)
147 		return -EINVAL;
148 
149 	transient_data->state = (state == 1) ? LED_FULL : LED_OFF;
150 	return size;
151 }
152 
153 static DEVICE_ATTR(activate, 0644, transient_activate_show,
154 		   transient_activate_store);
155 static DEVICE_ATTR(duration, 0644, transient_duration_show,
156 		   transient_duration_store);
157 static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store);
158 
159 static void transient_trig_activate(struct led_classdev *led_cdev)
160 {
161 	int rc;
162 	struct transient_trig_data *tdata;
163 
164 	tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL);
165 	if (!tdata) {
166 		dev_err(led_cdev->dev,
167 			"unable to allocate transient trigger\n");
168 		return;
169 	}
170 	led_cdev->trigger_data = tdata;
171 
172 	rc = device_create_file(led_cdev->dev, &dev_attr_activate);
173 	if (rc)
174 		goto err_out;
175 
176 	rc = device_create_file(led_cdev->dev, &dev_attr_duration);
177 	if (rc)
178 		goto err_out_duration;
179 
180 	rc = device_create_file(led_cdev->dev, &dev_attr_state);
181 	if (rc)
182 		goto err_out_state;
183 
184 	setup_timer(&tdata->timer, transient_timer_function,
185 		    (unsigned long) led_cdev);
186 	led_cdev->activated = true;
187 
188 	return;
189 
190 err_out_state:
191 	device_remove_file(led_cdev->dev, &dev_attr_duration);
192 err_out_duration:
193 	device_remove_file(led_cdev->dev, &dev_attr_activate);
194 err_out:
195 	dev_err(led_cdev->dev, "unable to register transient trigger\n");
196 	led_cdev->trigger_data = NULL;
197 	kfree(tdata);
198 }
199 
200 static void transient_trig_deactivate(struct led_classdev *led_cdev)
201 {
202 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
203 
204 	if (led_cdev->activated) {
205 		del_timer_sync(&transient_data->timer);
206 		__led_set_brightness(led_cdev, transient_data->restore_state);
207 		device_remove_file(led_cdev->dev, &dev_attr_activate);
208 		device_remove_file(led_cdev->dev, &dev_attr_duration);
209 		device_remove_file(led_cdev->dev, &dev_attr_state);
210 		led_cdev->trigger_data = NULL;
211 		led_cdev->activated = false;
212 		kfree(transient_data);
213 	}
214 }
215 
216 static struct led_trigger transient_trigger = {
217 	.name     = "transient",
218 	.activate = transient_trig_activate,
219 	.deactivate = transient_trig_deactivate,
220 };
221 
222 static int __init transient_trig_init(void)
223 {
224 	return led_trigger_register(&transient_trigger);
225 }
226 
227 static void __exit transient_trig_exit(void)
228 {
229 	led_trigger_unregister(&transient_trigger);
230 }
231 
232 module_init(transient_trig_init);
233 module_exit(transient_trig_exit);
234 
235 MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>");
236 MODULE_DESCRIPTION("Transient LED trigger");
237 MODULE_LICENSE("GPL");
238