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 	struct led_classdev *led_cdev;
37 };
38 
39 static void transient_timer_function(struct timer_list *t)
40 {
41 	struct transient_trig_data *transient_data =
42 		from_timer(transient_data, t, timer);
43 	struct led_classdev *led_cdev = transient_data->led_cdev;
44 
45 	transient_data->activate = 0;
46 	led_set_brightness_nosleep(led_cdev, transient_data->restore_state);
47 }
48 
49 static ssize_t transient_activate_show(struct device *dev,
50 		struct device_attribute *attr, char *buf)
51 {
52 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
53 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
54 
55 	return sprintf(buf, "%d\n", transient_data->activate);
56 }
57 
58 static ssize_t transient_activate_store(struct device *dev,
59 		struct device_attribute *attr, const char *buf, size_t size)
60 {
61 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
62 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
63 	unsigned long state;
64 	ssize_t ret;
65 
66 	ret = kstrtoul(buf, 10, &state);
67 	if (ret)
68 		return ret;
69 
70 	if (state != 1 && state != 0)
71 		return -EINVAL;
72 
73 	/* cancel the running timer */
74 	if (state == 0 && transient_data->activate == 1) {
75 		del_timer(&transient_data->timer);
76 		transient_data->activate = state;
77 		led_set_brightness_nosleep(led_cdev,
78 					transient_data->restore_state);
79 		return size;
80 	}
81 
82 	/* start timer if there is no active timer */
83 	if (state == 1 && transient_data->activate == 0 &&
84 	    transient_data->duration != 0) {
85 		transient_data->activate = state;
86 		led_set_brightness_nosleep(led_cdev, transient_data->state);
87 		transient_data->restore_state =
88 		    (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL;
89 		mod_timer(&transient_data->timer,
90 			  jiffies + msecs_to_jiffies(transient_data->duration));
91 	}
92 
93 	/* state == 0 && transient_data->activate == 0
94 		timer is not active - just return */
95 	/* state == 1 && transient_data->activate == 1
96 		timer is already active - just return */
97 
98 	return size;
99 }
100 
101 static ssize_t transient_duration_show(struct device *dev,
102 		struct device_attribute *attr, char *buf)
103 {
104 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
105 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
106 
107 	return sprintf(buf, "%lu\n", transient_data->duration);
108 }
109 
110 static ssize_t transient_duration_store(struct device *dev,
111 		struct device_attribute *attr, const char *buf, size_t size)
112 {
113 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
114 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
115 	unsigned long state;
116 	ssize_t ret;
117 
118 	ret = kstrtoul(buf, 10, &state);
119 	if (ret)
120 		return ret;
121 
122 	transient_data->duration = state;
123 	return size;
124 }
125 
126 static ssize_t transient_state_show(struct device *dev,
127 		struct device_attribute *attr, char *buf)
128 {
129 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
130 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
131 	int state;
132 
133 	state = (transient_data->state == LED_FULL) ? 1 : 0;
134 	return sprintf(buf, "%d\n", state);
135 }
136 
137 static ssize_t transient_state_store(struct device *dev,
138 		struct device_attribute *attr, const char *buf, size_t size)
139 {
140 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
141 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
142 	unsigned long state;
143 	ssize_t ret;
144 
145 	ret = kstrtoul(buf, 10, &state);
146 	if (ret)
147 		return ret;
148 
149 	if (state != 1 && state != 0)
150 		return -EINVAL;
151 
152 	transient_data->state = (state == 1) ? LED_FULL : LED_OFF;
153 	return size;
154 }
155 
156 static DEVICE_ATTR(activate, 0644, transient_activate_show,
157 		   transient_activate_store);
158 static DEVICE_ATTR(duration, 0644, transient_duration_show,
159 		   transient_duration_store);
160 static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store);
161 
162 static void transient_trig_activate(struct led_classdev *led_cdev)
163 {
164 	int rc;
165 	struct transient_trig_data *tdata;
166 
167 	tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL);
168 	if (!tdata) {
169 		dev_err(led_cdev->dev,
170 			"unable to allocate transient trigger\n");
171 		return;
172 	}
173 	led_cdev->trigger_data = tdata;
174 	tdata->led_cdev = led_cdev;
175 
176 	rc = device_create_file(led_cdev->dev, &dev_attr_activate);
177 	if (rc)
178 		goto err_out;
179 
180 	rc = device_create_file(led_cdev->dev, &dev_attr_duration);
181 	if (rc)
182 		goto err_out_duration;
183 
184 	rc = device_create_file(led_cdev->dev, &dev_attr_state);
185 	if (rc)
186 		goto err_out_state;
187 
188 	timer_setup(&tdata->timer, transient_timer_function, 0);
189 	led_cdev->activated = true;
190 
191 	return;
192 
193 err_out_state:
194 	device_remove_file(led_cdev->dev, &dev_attr_duration);
195 err_out_duration:
196 	device_remove_file(led_cdev->dev, &dev_attr_activate);
197 err_out:
198 	dev_err(led_cdev->dev, "unable to register transient trigger\n");
199 	led_cdev->trigger_data = NULL;
200 	kfree(tdata);
201 }
202 
203 static void transient_trig_deactivate(struct led_classdev *led_cdev)
204 {
205 	struct transient_trig_data *transient_data = led_cdev->trigger_data;
206 
207 	if (led_cdev->activated) {
208 		del_timer_sync(&transient_data->timer);
209 		led_set_brightness_nosleep(led_cdev,
210 					transient_data->restore_state);
211 		device_remove_file(led_cdev->dev, &dev_attr_activate);
212 		device_remove_file(led_cdev->dev, &dev_attr_duration);
213 		device_remove_file(led_cdev->dev, &dev_attr_state);
214 		led_cdev->trigger_data = NULL;
215 		led_cdev->activated = false;
216 		kfree(transient_data);
217 	}
218 }
219 
220 static struct led_trigger transient_trigger = {
221 	.name     = "transient",
222 	.activate = transient_trig_activate,
223 	.deactivate = transient_trig_deactivate,
224 };
225 
226 static int __init transient_trig_init(void)
227 {
228 	return led_trigger_register(&transient_trigger);
229 }
230 
231 static void __exit transient_trig_exit(void)
232 {
233 	led_trigger_unregister(&transient_trigger);
234 }
235 
236 module_init(transient_trig_init);
237 module_exit(transient_trig_exit);
238 
239 MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>");
240 MODULE_DESCRIPTION("Transient LED trigger");
241 MODULE_LICENSE("GPL");
242