1 // SPDX-License-Identifier: GPL-2.0
6 * Idea discussed with Pavel Machek. Raphael Teysseyre implemented
7 * the first version, Baolin Wang simplified and improved the approach.
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>
17 #define MAX_PATTERNS 1024
19 * When doing gradual dimming, the led brightness will be updated
20 * every 50 milliseconds.
22 #define UPDATE_INTERVAL 50
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
;
36 struct timer_list timer
;
39 static void pattern_trig_update_patterns(struct pattern_trig_data
*data
)
41 data
->curr
= data
->next
;
42 if (!data
->is_indefinite
&& data
->curr
== data
->patterns
)
45 if (data
->next
== data
->patterns
+ data
->npatterns
- 1)
46 data
->next
= data
->patterns
;
53 static int pattern_trig_compute_brightness(struct pattern_trig_data
*data
)
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.
62 if (data
->delta_t
== 0 || data
->curr
->delta_t
< UPDATE_INTERVAL
)
63 return data
->curr
->brightness
;
65 step_brightness
= abs(data
->next
->brightness
- data
->curr
->brightness
);
66 step_brightness
= data
->delta_t
* step_brightness
/ data
->curr
->delta_t
;
68 if (data
->next
->brightness
> data
->curr
->brightness
)
69 return data
->curr
->brightness
+ step_brightness
;
71 return data
->curr
->brightness
- step_brightness
;
74 static void pattern_trig_timer_function(struct timer_list
*t
)
76 struct pattern_trig_data
*data
= from_timer(data
, t
, timer
);
78 mutex_lock(&data
->lock
);
81 if (!data
->is_indefinite
&& !data
->repeat
)
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
));
91 /* Skip the tuple with zero duration */
92 pattern_trig_update_patterns(data
);
93 /* Select next tuple */
94 pattern_trig_update_patterns(data
);
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.
103 if (data
->delta_t
> data
->curr
->delta_t
) {
104 pattern_trig_update_patterns(data
);
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
));
113 /* Accumulate the gradual dimming time */
114 data
->delta_t
+= UPDATE_INTERVAL
;
120 mutex_unlock(&data
->lock
);
123 static int pattern_trig_start_pattern(struct led_classdev
*led_cdev
)
125 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
127 if (!data
->npatterns
)
130 if (data
->is_hw_pattern
) {
131 return led_cdev
->pattern_set(led_cdev
, data
->patterns
,
132 data
->npatterns
, data
->repeat
);
135 /* At least 2 tuples for software pattern. */
136 if (data
->npatterns
< 2)
140 data
->curr
= data
->patterns
;
141 data
->next
= data
->patterns
+ 1;
142 data
->timer
.expires
= jiffies
;
143 add_timer(&data
->timer
);
148 static ssize_t
repeat_show(struct device
*dev
, struct device_attribute
*attr
,
151 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
152 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
155 mutex_lock(&data
->lock
);
157 repeat
= data
->last_repeat
;
159 mutex_unlock(&data
->lock
);
161 return scnprintf(buf
, PAGE_SIZE
, "%d\n", repeat
);
164 static ssize_t
repeat_store(struct device
*dev
, struct device_attribute
*attr
,
165 const char *buf
, size_t count
)
167 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
168 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
171 err
= kstrtos32(buf
, 10, &res
);
175 /* Number 0 and negative numbers except -1 are invalid. */
176 if (res
< -1 || res
== 0)
180 * Clear previous patterns' performence firstly, and remove the timer
181 * without mutex lock to avoid dead lock.
183 del_timer_sync(&data
->timer
);
185 mutex_lock(&data
->lock
);
187 if (data
->is_hw_pattern
)
188 led_cdev
->pattern_clear(led_cdev
);
190 data
->last_repeat
= data
->repeat
= res
;
191 /* -1 means repeat indefinitely */
192 if (data
->repeat
== -1)
193 data
->is_indefinite
= true;
195 data
->is_indefinite
= false;
197 err
= pattern_trig_start_pattern(led_cdev
);
199 mutex_unlock(&data
->lock
);
200 return err
< 0 ? err
: count
;
203 static DEVICE_ATTR_RW(repeat
);
205 static ssize_t
pattern_trig_show_patterns(struct pattern_trig_data
*data
,
206 char *buf
, bool hw_pattern
)
211 mutex_lock(&data
->lock
);
213 if (!data
->npatterns
|| (data
->is_hw_pattern
^ hw_pattern
))
216 for (i
= 0; i
< data
->npatterns
; i
++) {
217 count
+= scnprintf(buf
+ count
, PAGE_SIZE
- count
,
219 data
->patterns
[i
].brightness
,
220 data
->patterns
[i
].delta_t
);
223 buf
[count
- 1] = '\n';
226 mutex_unlock(&data
->lock
);
230 static ssize_t
pattern_trig_store_patterns(struct led_classdev
*led_cdev
,
231 const char *buf
, size_t count
,
234 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
235 int ccount
, cr
, offset
= 0, err
= 0;
238 * Clear previous patterns' performence firstly, and remove the timer
239 * without mutex lock to avoid dead lock.
241 del_timer_sync(&data
->timer
);
243 mutex_lock(&data
->lock
);
245 if (data
->is_hw_pattern
)
246 led_cdev
->pattern_clear(led_cdev
);
248 data
->is_hw_pattern
= hw_pattern
;
251 while (offset
< count
- 1 && data
->npatterns
< MAX_PATTERNS
) {
253 ccount
= sscanf(buf
+ offset
, "%d %u %n",
254 &data
->patterns
[data
->npatterns
].brightness
,
255 &data
->patterns
[data
->npatterns
].delta_t
, &cr
);
266 err
= pattern_trig_start_pattern(led_cdev
);
271 mutex_unlock(&data
->lock
);
272 return err
< 0 ? err
: count
;
275 static ssize_t
pattern_show(struct device
*dev
, struct device_attribute
*attr
,
278 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
279 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
281 return pattern_trig_show_patterns(data
, buf
, false);
284 static ssize_t
pattern_store(struct device
*dev
, struct device_attribute
*attr
,
285 const char *buf
, size_t count
)
287 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
289 return pattern_trig_store_patterns(led_cdev
, buf
, count
, false);
292 static DEVICE_ATTR_RW(pattern
);
294 static ssize_t
hw_pattern_show(struct device
*dev
,
295 struct device_attribute
*attr
, char *buf
)
297 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
298 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
300 return pattern_trig_show_patterns(data
, buf
, true);
303 static ssize_t
hw_pattern_store(struct device
*dev
,
304 struct device_attribute
*attr
,
305 const char *buf
, size_t count
)
307 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
309 return pattern_trig_store_patterns(led_cdev
, buf
, count
, true);
312 static DEVICE_ATTR_RW(hw_pattern
);
314 static umode_t
pattern_trig_attrs_mode(struct kobject
*kobj
,
315 struct attribute
*attr
, int index
)
317 struct device
*dev
= container_of(kobj
, struct device
, kobj
);
318 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
320 if (attr
== &dev_attr_repeat
.attr
|| attr
== &dev_attr_pattern
.attr
)
322 else if (attr
== &dev_attr_hw_pattern
.attr
&& led_cdev
->pattern_set
)
328 static struct attribute
*pattern_trig_attrs
[] = {
329 &dev_attr_pattern
.attr
,
330 &dev_attr_hw_pattern
.attr
,
331 &dev_attr_repeat
.attr
,
335 static const struct attribute_group pattern_trig_group
= {
336 .attrs
= pattern_trig_attrs
,
337 .is_visible
= pattern_trig_attrs_mode
,
340 static const struct attribute_group
*pattern_trig_groups
[] = {
345 static int pattern_trig_activate(struct led_classdev
*led_cdev
)
347 struct pattern_trig_data
*data
;
349 data
= kzalloc(sizeof(*data
), GFP_KERNEL
);
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
;
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;
371 static void pattern_trig_deactivate(struct led_classdev
*led_cdev
)
373 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
375 if (!led_cdev
->activated
)
378 if (led_cdev
->pattern_clear
)
379 led_cdev
->pattern_clear(led_cdev
);
381 del_timer_sync(&data
->timer
);
383 led_set_brightness(led_cdev
, LED_OFF
);
385 led_cdev
->activated
= false;
388 static struct led_trigger pattern_led_trigger
= {
390 .activate
= pattern_trig_activate
,
391 .deactivate
= pattern_trig_deactivate
,
392 .groups
= pattern_trig_groups
,
395 static int __init
pattern_trig_init(void)
397 return led_trigger_register(&pattern_led_trigger
);
400 static void __exit
pattern_trig_exit(void)
402 led_trigger_unregister(&pattern_led_trigger
);
405 module_init(pattern_trig_init
);
406 module_exit(pattern_trig_exit
);
408 MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com");
409 MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org");
410 MODULE_DESCRIPTION("LED Pattern trigger");
411 MODULE_LICENSE("GPL v2");