1 | // 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 | |
24 | struct 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 | |
39 | static 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 | |
53 | static 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 | |
74 | static void pattern_trig_timer_function(struct timer_list *t) |
75 | { |
76 | struct pattern_trig_data *data = from_timer(data, t, timer); |
77 | |
78 | for (;;) { |
79 | if (!data->is_indefinite && !data->repeat) |
80 | break; |
81 | |
82 | if (data->curr->brightness == data->next->brightness) { |
83 | /* Step change of brightness */ |
84 | led_set_brightness(led_cdev: data->led_cdev, |
85 | brightness: data->curr->brightness); |
86 | mod_timer(timer: &data->timer, |
87 | expires: jiffies + msecs_to_jiffies(m: data->curr->delta_t)); |
88 | if (!data->next->delta_t) { |
89 | /* Skip the tuple with zero duration */ |
90 | pattern_trig_update_patterns(data); |
91 | } |
92 | /* Select next tuple */ |
93 | pattern_trig_update_patterns(data); |
94 | } else { |
95 | /* Gradual dimming */ |
96 | |
97 | /* |
98 | * If the accumulation time is larger than current |
99 | * tuple's duration, we should go next one and re-check |
100 | * if we repeated done. |
101 | */ |
102 | if (data->delta_t > data->curr->delta_t) { |
103 | pattern_trig_update_patterns(data); |
104 | continue; |
105 | } |
106 | |
107 | led_set_brightness(led_cdev: data->led_cdev, |
108 | brightness: pattern_trig_compute_brightness(data)); |
109 | mod_timer(timer: &data->timer, |
110 | expires: jiffies + msecs_to_jiffies(UPDATE_INTERVAL)); |
111 | |
112 | /* Accumulate the gradual dimming time */ |
113 | data->delta_t += UPDATE_INTERVAL; |
114 | } |
115 | |
116 | break; |
117 | } |
118 | } |
119 | |
120 | static int pattern_trig_start_pattern(struct led_classdev *led_cdev) |
121 | { |
122 | struct pattern_trig_data *data = led_cdev->trigger_data; |
123 | |
124 | if (!data->npatterns) |
125 | return 0; |
126 | |
127 | if (data->is_hw_pattern) { |
128 | return led_cdev->pattern_set(led_cdev, data->patterns, |
129 | data->npatterns, data->repeat); |
130 | } |
131 | |
132 | /* At least 2 tuples for software pattern. */ |
133 | if (data->npatterns < 2) |
134 | return -EINVAL; |
135 | |
136 | data->delta_t = 0; |
137 | data->curr = data->patterns; |
138 | data->next = data->patterns + 1; |
139 | data->timer.expires = jiffies; |
140 | add_timer(timer: &data->timer); |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static ssize_t repeat_show(struct device *dev, struct device_attribute *attr, |
146 | char *buf) |
147 | { |
148 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
149 | struct pattern_trig_data *data = led_cdev->trigger_data; |
150 | int repeat; |
151 | |
152 | mutex_lock(&data->lock); |
153 | |
154 | repeat = data->last_repeat; |
155 | |
156 | mutex_unlock(lock: &data->lock); |
157 | |
158 | return sysfs_emit(buf, fmt: "%d\n" , repeat); |
159 | } |
160 | |
161 | static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, |
162 | const char *buf, size_t count) |
163 | { |
164 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
165 | struct pattern_trig_data *data = led_cdev->trigger_data; |
166 | int err, res; |
167 | |
168 | err = kstrtos32(s: buf, base: 10, res: &res); |
169 | if (err) |
170 | return err; |
171 | |
172 | /* Number 0 and negative numbers except -1 are invalid. */ |
173 | if (res < -1 || res == 0) |
174 | return -EINVAL; |
175 | |
176 | mutex_lock(&data->lock); |
177 | |
178 | del_timer_sync(timer: &data->timer); |
179 | |
180 | if (data->is_hw_pattern) |
181 | led_cdev->pattern_clear(led_cdev); |
182 | |
183 | data->last_repeat = data->repeat = res; |
184 | /* -1 means repeat indefinitely */ |
185 | if (data->repeat == -1) |
186 | data->is_indefinite = true; |
187 | else |
188 | data->is_indefinite = false; |
189 | |
190 | err = pattern_trig_start_pattern(led_cdev); |
191 | |
192 | mutex_unlock(lock: &data->lock); |
193 | return err < 0 ? err : count; |
194 | } |
195 | |
196 | static DEVICE_ATTR_RW(repeat); |
197 | |
198 | static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, |
199 | char *buf, bool hw_pattern) |
200 | { |
201 | ssize_t count = 0; |
202 | int i; |
203 | |
204 | mutex_lock(&data->lock); |
205 | |
206 | if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern)) |
207 | goto out; |
208 | |
209 | for (i = 0; i < data->npatterns; i++) { |
210 | count += scnprintf(buf: buf + count, PAGE_SIZE - count, |
211 | fmt: "%d %u " , |
212 | data->patterns[i].brightness, |
213 | data->patterns[i].delta_t); |
214 | } |
215 | |
216 | buf[count - 1] = '\n'; |
217 | |
218 | out: |
219 | mutex_unlock(lock: &data->lock); |
220 | return count; |
221 | } |
222 | |
223 | static int pattern_trig_store_patterns_string(struct pattern_trig_data *data, |
224 | const char *buf, size_t count) |
225 | { |
226 | int ccount, cr, offset = 0; |
227 | |
228 | while (offset < count - 1 && data->npatterns < MAX_PATTERNS) { |
229 | cr = 0; |
230 | ccount = sscanf(buf + offset, "%u %u %n" , |
231 | &data->patterns[data->npatterns].brightness, |
232 | &data->patterns[data->npatterns].delta_t, &cr); |
233 | |
234 | if (ccount != 2 || |
235 | data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) { |
236 | data->npatterns = 0; |
237 | return -EINVAL; |
238 | } |
239 | |
240 | offset += cr; |
241 | data->npatterns++; |
242 | } |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static int pattern_trig_store_patterns_int(struct pattern_trig_data *data, |
248 | const u32 *buf, size_t count) |
249 | { |
250 | unsigned int i; |
251 | |
252 | for (i = 0; i < count; i += 2) { |
253 | data->patterns[data->npatterns].brightness = buf[i]; |
254 | data->patterns[data->npatterns].delta_t = buf[i + 1]; |
255 | data->npatterns++; |
256 | } |
257 | |
258 | return 0; |
259 | } |
260 | |
261 | static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, |
262 | const char *buf, const u32 *buf_int, |
263 | size_t count, bool hw_pattern) |
264 | { |
265 | struct pattern_trig_data *data = led_cdev->trigger_data; |
266 | int err = 0; |
267 | |
268 | mutex_lock(&data->lock); |
269 | |
270 | del_timer_sync(timer: &data->timer); |
271 | |
272 | if (data->is_hw_pattern) |
273 | led_cdev->pattern_clear(led_cdev); |
274 | |
275 | data->is_hw_pattern = hw_pattern; |
276 | data->npatterns = 0; |
277 | |
278 | if (buf) |
279 | err = pattern_trig_store_patterns_string(data, buf, count); |
280 | else |
281 | err = pattern_trig_store_patterns_int(data, buf: buf_int, count); |
282 | if (err) |
283 | goto out; |
284 | |
285 | err = pattern_trig_start_pattern(led_cdev); |
286 | if (err) |
287 | data->npatterns = 0; |
288 | |
289 | out: |
290 | mutex_unlock(lock: &data->lock); |
291 | return err < 0 ? err : count; |
292 | } |
293 | |
294 | static ssize_t pattern_show(struct device *dev, struct device_attribute *attr, |
295 | 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, hw_pattern: false); |
301 | } |
302 | |
303 | static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, |
304 | const char *buf, size_t count) |
305 | { |
306 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
307 | |
308 | return pattern_trig_store_patterns(led_cdev, buf, NULL, count, hw_pattern: false); |
309 | } |
310 | |
311 | static DEVICE_ATTR_RW(pattern); |
312 | |
313 | static ssize_t hw_pattern_show(struct device *dev, |
314 | struct device_attribute *attr, char *buf) |
315 | { |
316 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
317 | struct pattern_trig_data *data = led_cdev->trigger_data; |
318 | |
319 | return pattern_trig_show_patterns(data, buf, hw_pattern: true); |
320 | } |
321 | |
322 | static ssize_t hw_pattern_store(struct device *dev, |
323 | struct device_attribute *attr, |
324 | const char *buf, size_t count) |
325 | { |
326 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
327 | |
328 | return pattern_trig_store_patterns(led_cdev, buf, NULL, count, hw_pattern: true); |
329 | } |
330 | |
331 | static DEVICE_ATTR_RW(hw_pattern); |
332 | |
333 | static umode_t pattern_trig_attrs_mode(struct kobject *kobj, |
334 | struct attribute *attr, int index) |
335 | { |
336 | struct device *dev = kobj_to_dev(kobj); |
337 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
338 | |
339 | if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr) |
340 | return attr->mode; |
341 | else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set) |
342 | return attr->mode; |
343 | |
344 | return 0; |
345 | } |
346 | |
347 | static struct attribute *pattern_trig_attrs[] = { |
348 | &dev_attr_pattern.attr, |
349 | &dev_attr_hw_pattern.attr, |
350 | &dev_attr_repeat.attr, |
351 | NULL |
352 | }; |
353 | |
354 | static const struct attribute_group pattern_trig_group = { |
355 | .attrs = pattern_trig_attrs, |
356 | .is_visible = pattern_trig_attrs_mode, |
357 | }; |
358 | |
359 | static const struct attribute_group *pattern_trig_groups[] = { |
360 | &pattern_trig_group, |
361 | NULL, |
362 | }; |
363 | |
364 | static void pattern_init(struct led_classdev *led_cdev) |
365 | { |
366 | unsigned int size = 0; |
367 | u32 *pattern; |
368 | int err; |
369 | |
370 | pattern = led_get_default_pattern(led_cdev, size: &size); |
371 | if (!pattern) |
372 | return; |
373 | |
374 | if (size % 2) { |
375 | dev_warn(led_cdev->dev, "Expected pattern of tuples\n" ); |
376 | goto out; |
377 | } |
378 | |
379 | err = pattern_trig_store_patterns(led_cdev, NULL, buf_int: pattern, count: size, hw_pattern: false); |
380 | if (err < 0) |
381 | dev_warn(led_cdev->dev, |
382 | "Pattern initialization failed with error %d\n" , err); |
383 | |
384 | out: |
385 | kfree(objp: pattern); |
386 | } |
387 | |
388 | static int pattern_trig_activate(struct led_classdev *led_cdev) |
389 | { |
390 | struct pattern_trig_data *data; |
391 | |
392 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
393 | if (!data) |
394 | return -ENOMEM; |
395 | |
396 | if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) { |
397 | dev_warn(led_cdev->dev, |
398 | "Hardware pattern ops validation failed\n" ); |
399 | led_cdev->pattern_set = NULL; |
400 | led_cdev->pattern_clear = NULL; |
401 | } |
402 | |
403 | data->is_indefinite = true; |
404 | data->last_repeat = -1; |
405 | mutex_init(&data->lock); |
406 | data->led_cdev = led_cdev; |
407 | led_set_trigger_data(led_cdev, trigger_data: data); |
408 | timer_setup(&data->timer, pattern_trig_timer_function, 0); |
409 | led_cdev->activated = true; |
410 | |
411 | if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { |
412 | pattern_init(led_cdev); |
413 | /* |
414 | * Mark as initialized even on pattern_init() error because |
415 | * any consecutive call to it would produce the same error. |
416 | */ |
417 | led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; |
418 | } |
419 | |
420 | return 0; |
421 | } |
422 | |
423 | static void pattern_trig_deactivate(struct led_classdev *led_cdev) |
424 | { |
425 | struct pattern_trig_data *data = led_cdev->trigger_data; |
426 | |
427 | if (!led_cdev->activated) |
428 | return; |
429 | |
430 | if (led_cdev->pattern_clear) |
431 | led_cdev->pattern_clear(led_cdev); |
432 | |
433 | timer_shutdown_sync(timer: &data->timer); |
434 | |
435 | led_set_brightness(led_cdev, brightness: LED_OFF); |
436 | kfree(objp: data); |
437 | led_cdev->activated = false; |
438 | } |
439 | |
440 | static struct led_trigger pattern_led_trigger = { |
441 | .name = "pattern" , |
442 | .activate = pattern_trig_activate, |
443 | .deactivate = pattern_trig_deactivate, |
444 | .groups = pattern_trig_groups, |
445 | }; |
446 | |
447 | static int __init pattern_trig_init(void) |
448 | { |
449 | return led_trigger_register(trigger: &pattern_led_trigger); |
450 | } |
451 | |
452 | static void __exit pattern_trig_exit(void) |
453 | { |
454 | led_trigger_unregister(trigger: &pattern_led_trigger); |
455 | } |
456 | |
457 | module_init(pattern_trig_init); |
458 | module_exit(pattern_trig_exit); |
459 | |
460 | MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>" ); |
461 | MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>" ); |
462 | MODULE_DESCRIPTION("LED Pattern trigger" ); |
463 | MODULE_LICENSE("GPL v2" ); |
464 | |