blob: ce7acd115dd8da7578b4fc8d3fc3733692d90743 [file] [log] [blame]
Baolin Wang5fd752b2018-10-11 12:07:14 +08001// SPDX-License-Identifier: GPL-2.0
2
3/*
4 * LED pattern trigger
5 *
6 * Idea discussed with Pavel Machek. Raphael Teysseyre implemented
7 * the first version, Baolin Wang simplified and improved the approach.
8 */
9
10#include <linux/kernel.h>
11#include <linux/leds.h>
12#include <linux/module.h>
13#include <linux/mutex.h>
14#include <linux/slab.h>
15#include <linux/timer.h>
16
17#define MAX_PATTERNS 1024
18/*
19 * When doing gradual dimming, the led brightness will be updated
20 * every 50 milliseconds.
21 */
22#define UPDATE_INTERVAL 50
23
24struct pattern_trig_data {
25 struct led_classdev *led_cdev;
26 struct led_pattern patterns[MAX_PATTERNS];
27 struct led_pattern *curr;
28 struct led_pattern *next;
29 struct mutex lock;
30 u32 npatterns;
31 int repeat;
32 int last_repeat;
33 int delta_t;
34 bool is_indefinite;
35 bool is_hw_pattern;
36 struct timer_list timer;
37};
38
39static void pattern_trig_update_patterns(struct pattern_trig_data *data)
40{
41 data->curr = data->next;
42 if (!data->is_indefinite && data->curr == data->patterns)
43 data->repeat--;
44
45 if (data->next == data->patterns + data->npatterns - 1)
46 data->next = data->patterns;
47 else
48 data->next++;
49
50 data->delta_t = 0;
51}
52
53static int pattern_trig_compute_brightness(struct pattern_trig_data *data)
54{
55 int step_brightness;
56
57 /*
58 * If current tuple's duration is less than the dimming interval,
59 * we should treat it as a step change of brightness instead of
60 * doing gradual dimming.
61 */
62 if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL)
63 return data->curr->brightness;
64
65 step_brightness = abs(data->next->brightness - data->curr->brightness);
66 step_brightness = data->delta_t * step_brightness / data->curr->delta_t;
67
68 if (data->next->brightness > data->curr->brightness)
69 return data->curr->brightness + step_brightness;
70 else
71 return data->curr->brightness - step_brightness;
72}
73
74static void pattern_trig_timer_function(struct timer_list *t)
75{
76 struct pattern_trig_data *data = from_timer(data, t, timer);
77
78 mutex_lock(&data->lock);
79
80 for (;;) {
81 if (!data->is_indefinite && !data->repeat)
82 break;
83
84 if (data->curr->brightness == data->next->brightness) {
85 /* Step change of brightness */
86 led_set_brightness(data->led_cdev,
87 data->curr->brightness);
88 mod_timer(&data->timer,
89 jiffies + msecs_to_jiffies(data->curr->delta_t));
90
91 /* Skip the tuple with zero duration */
92 pattern_trig_update_patterns(data);
93 /* Select next tuple */
94 pattern_trig_update_patterns(data);
95 } else {
96 /* Gradual dimming */
97
98 /*
99 * If the accumulation time is larger than current
100 * tuple's duration, we should go next one and re-check
101 * if we repeated done.
102 */
103 if (data->delta_t > data->curr->delta_t) {
104 pattern_trig_update_patterns(data);
105 continue;
106 }
107
108 led_set_brightness(data->led_cdev,
109 pattern_trig_compute_brightness(data));
110 mod_timer(&data->timer,
111 jiffies + msecs_to_jiffies(UPDATE_INTERVAL));
112
113 /* Accumulate the gradual dimming time */
114 data->delta_t += UPDATE_INTERVAL;
115 }
116
117 break;
118 }
119
120 mutex_unlock(&data->lock);
121}
122
123static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
124{
125 struct pattern_trig_data *data = led_cdev->trigger_data;
126
127 if (!data->npatterns)
128 return 0;
129
130 if (data->is_hw_pattern) {
131 return led_cdev->pattern_set(led_cdev, data->patterns,
132 data->npatterns, data->repeat);
133 }
134
135 /* At least 2 tuples for software pattern. */
136 if (data->npatterns < 2)
137 return -EINVAL;
138
139 data->delta_t = 0;
140 data->curr = data->patterns;
141 data->next = data->patterns + 1;
142 data->timer.expires = jiffies;
143 add_timer(&data->timer);
144
145 return 0;
146}
147
148static ssize_t repeat_show(struct device *dev, struct device_attribute *attr,
149 char *buf)
150{
151 struct led_classdev *led_cdev = dev_get_drvdata(dev);
152 struct pattern_trig_data *data = led_cdev->trigger_data;
153 int repeat;
154
155 mutex_lock(&data->lock);
156
157 repeat = data->last_repeat;
158
159 mutex_unlock(&data->lock);
160
161 return scnprintf(buf, PAGE_SIZE, "%d\n", repeat);
162}
163
164static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
165 const char *buf, size_t count)
166{
167 struct led_classdev *led_cdev = dev_get_drvdata(dev);
168 struct pattern_trig_data *data = led_cdev->trigger_data;
169 int err, res;
170
171 err = kstrtos32(buf, 10, &res);
172 if (err)
173 return err;
174
175 /* Number 0 and negative numbers except -1 are invalid. */
176 if (res < -1 || res == 0)
177 return -EINVAL;
178
179 /*
180 * Clear previous patterns' performence firstly, and remove the timer
181 * without mutex lock to avoid dead lock.
182 */
183 del_timer_sync(&data->timer);
184
185 mutex_lock(&data->lock);
186
187 if (data->is_hw_pattern)
188 led_cdev->pattern_clear(led_cdev);
189
190 data->last_repeat = data->repeat = res;
191 /* -1 means repeat indefinitely */
192 if (data->repeat == -1)
193 data->is_indefinite = true;
194 else
195 data->is_indefinite = false;
196
197 err = pattern_trig_start_pattern(led_cdev);
198
199 mutex_unlock(&data->lock);
200 return err < 0 ? err : count;
201}
202
203static DEVICE_ATTR_RW(repeat);
204
205static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
206 char *buf, bool hw_pattern)
207{
208 ssize_t count = 0;
209 int i;
210
211 mutex_lock(&data->lock);
212
213 if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern))
214 goto out;
215
216 for (i = 0; i < data->npatterns; i++) {
217 count += scnprintf(buf + count, PAGE_SIZE - count,
218 "%d %u ",
219 data->patterns[i].brightness,
220 data->patterns[i].delta_t);
221 }
222
223 buf[count - 1] = '\n';
224
225out:
226 mutex_unlock(&data->lock);
227 return count;
228}
229
230static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
231 const char *buf, size_t count,
232 bool hw_pattern)
233{
234 struct pattern_trig_data *data = led_cdev->trigger_data;
235 int ccount, cr, offset = 0, err = 0;
236
237 /*
238 * Clear previous patterns' performence firstly, and remove the timer
239 * without mutex lock to avoid dead lock.
240 */
241 del_timer_sync(&data->timer);
242
243 mutex_lock(&data->lock);
244
245 if (data->is_hw_pattern)
246 led_cdev->pattern_clear(led_cdev);
247
248 data->is_hw_pattern = hw_pattern;
249 data->npatterns = 0;
250
251 while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
252 cr = 0;
253 ccount = sscanf(buf + offset, "%d %u %n",
254 &data->patterns[data->npatterns].brightness,
255 &data->patterns[data->npatterns].delta_t, &cr);
256 if (ccount != 2) {
257 data->npatterns = 0;
258 err = -EINVAL;
259 goto out;
260 }
261
262 offset += cr;
263 data->npatterns++;
264 }
265
266 err = pattern_trig_start_pattern(led_cdev);
267 if (err)
268 data->npatterns = 0;
269
270out:
271 mutex_unlock(&data->lock);
272 return err < 0 ? err : count;
273}
274
275static ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
276 char *buf)
277{
278 struct led_classdev *led_cdev = dev_get_drvdata(dev);
279 struct pattern_trig_data *data = led_cdev->trigger_data;
280
281 return pattern_trig_show_patterns(data, buf, false);
282}
283
284static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
285 const char *buf, size_t count)
286{
287 struct led_classdev *led_cdev = dev_get_drvdata(dev);
288
289 return pattern_trig_store_patterns(led_cdev, buf, count, false);
290}
291
292static DEVICE_ATTR_RW(pattern);
293
294static ssize_t hw_pattern_show(struct device *dev,
295 struct device_attribute *attr, char *buf)
296{
297 struct led_classdev *led_cdev = dev_get_drvdata(dev);
298 struct pattern_trig_data *data = led_cdev->trigger_data;
299
300 return pattern_trig_show_patterns(data, buf, true);
301}
302
303static ssize_t hw_pattern_store(struct device *dev,
304 struct device_attribute *attr,
305 const char *buf, size_t count)
306{
307 struct led_classdev *led_cdev = dev_get_drvdata(dev);
308
309 return pattern_trig_store_patterns(led_cdev, buf, count, true);
310}
311
312static DEVICE_ATTR_RW(hw_pattern);
313
314static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
315 struct attribute *attr, int index)
316{
317 struct device *dev = container_of(kobj, struct device, kobj);
318 struct led_classdev *led_cdev = dev_get_drvdata(dev);
319
320 if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
321 return attr->mode;
322 else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
323 return attr->mode;
324
325 return 0;
326}
327
328static struct attribute *pattern_trig_attrs[] = {
329 &dev_attr_pattern.attr,
330 &dev_attr_hw_pattern.attr,
331 &dev_attr_repeat.attr,
332 NULL
333};
334
335static const struct attribute_group pattern_trig_group = {
336 .attrs = pattern_trig_attrs,
337 .is_visible = pattern_trig_attrs_mode,
338};
339
340static const struct attribute_group *pattern_trig_groups[] = {
341 &pattern_trig_group,
342 NULL,
343};
344
345static int pattern_trig_activate(struct led_classdev *led_cdev)
346{
347 struct pattern_trig_data *data;
348
349 data = kzalloc(sizeof(*data), GFP_KERNEL);
350 if (!data)
351 return -ENOMEM;
352
353 if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) {
354 dev_warn(led_cdev->dev,
355 "Hardware pattern ops validation failed\n");
356 led_cdev->pattern_set = NULL;
357 led_cdev->pattern_clear = NULL;
358 }
359
360 data->is_indefinite = true;
361 data->last_repeat = -1;
362 mutex_init(&data->lock);
363 data->led_cdev = led_cdev;
364 led_set_trigger_data(led_cdev, data);
365 timer_setup(&data->timer, pattern_trig_timer_function, 0);
366 led_cdev->activated = true;
367
368 return 0;
369}
370
371static void pattern_trig_deactivate(struct led_classdev *led_cdev)
372{
373 struct pattern_trig_data *data = led_cdev->trigger_data;
374
375 if (!led_cdev->activated)
376 return;
377
378 if (led_cdev->pattern_clear)
379 led_cdev->pattern_clear(led_cdev);
380
381 del_timer_sync(&data->timer);
382
383 led_set_brightness(led_cdev, LED_OFF);
384 kfree(data);
385 led_cdev->activated = false;
386}
387
388static struct led_trigger pattern_led_trigger = {
389 .name = "pattern",
390 .activate = pattern_trig_activate,
391 .deactivate = pattern_trig_deactivate,
392 .groups = pattern_trig_groups,
393};
394
395static int __init pattern_trig_init(void)
396{
397 return led_trigger_register(&pattern_led_trigger);
398}
399
400static void __exit pattern_trig_exit(void)
401{
402 led_trigger_unregister(&pattern_led_trigger);
403}
404
405module_init(pattern_trig_init);
406module_exit(pattern_trig_exit);
407
408MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com");
409MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org");
410MODULE_DESCRIPTION("LED Pattern trigger");
411MODULE_LICENSE("GPL v2");