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
);
79 if (!data
->is_indefinite
&& !data
->repeat
)
82 if (data
->curr
->brightness
== data
->next
->brightness
) {
83 /* Step change of brightness */
84 led_set_brightness(data
->led_cdev
,
85 data
->curr
->brightness
);
86 mod_timer(&data
->timer
,
87 jiffies
+ msecs_to_jiffies(data
->curr
->delta_t
));
88 if (!data
->next
->delta_t
) {
89 /* Skip the tuple with zero duration */
90 pattern_trig_update_patterns(data
);
92 /* Select next tuple */
93 pattern_trig_update_patterns(data
);
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.
102 if (data
->delta_t
> data
->curr
->delta_t
) {
103 pattern_trig_update_patterns(data
);
107 led_set_brightness(data
->led_cdev
,
108 pattern_trig_compute_brightness(data
));
109 mod_timer(&data
->timer
,
110 jiffies
+ msecs_to_jiffies(UPDATE_INTERVAL
));
112 /* Accumulate the gradual dimming time */
113 data
->delta_t
+= UPDATE_INTERVAL
;
120 static int pattern_trig_start_pattern(struct led_classdev
*led_cdev
)
122 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
124 if (!data
->npatterns
)
127 if (data
->is_hw_pattern
) {
128 return led_cdev
->pattern_set(led_cdev
, data
->patterns
,
129 data
->npatterns
, data
->repeat
);
132 /* At least 2 tuples for software pattern. */
133 if (data
->npatterns
< 2)
137 data
->curr
= data
->patterns
;
138 data
->next
= data
->patterns
+ 1;
139 data
->timer
.expires
= jiffies
;
140 add_timer(&data
->timer
);
145 static ssize_t
repeat_show(struct device
*dev
, struct device_attribute
*attr
,
148 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
149 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
152 mutex_lock(&data
->lock
);
154 repeat
= data
->last_repeat
;
156 mutex_unlock(&data
->lock
);
158 return scnprintf(buf
, PAGE_SIZE
, "%d\n", repeat
);
161 static ssize_t
repeat_store(struct device
*dev
, struct device_attribute
*attr
,
162 const char *buf
, size_t count
)
164 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
165 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
168 err
= kstrtos32(buf
, 10, &res
);
172 /* Number 0 and negative numbers except -1 are invalid. */
173 if (res
< -1 || res
== 0)
176 mutex_lock(&data
->lock
);
178 del_timer_sync(&data
->timer
);
180 if (data
->is_hw_pattern
)
181 led_cdev
->pattern_clear(led_cdev
);
183 data
->last_repeat
= data
->repeat
= res
;
184 /* -1 means repeat indefinitely */
185 if (data
->repeat
== -1)
186 data
->is_indefinite
= true;
188 data
->is_indefinite
= false;
190 err
= pattern_trig_start_pattern(led_cdev
);
192 mutex_unlock(&data
->lock
);
193 return err
< 0 ? err
: count
;
196 static DEVICE_ATTR_RW(repeat
);
198 static ssize_t
pattern_trig_show_patterns(struct pattern_trig_data
*data
,
199 char *buf
, bool hw_pattern
)
204 mutex_lock(&data
->lock
);
206 if (!data
->npatterns
|| (data
->is_hw_pattern
^ hw_pattern
))
209 for (i
= 0; i
< data
->npatterns
; i
++) {
210 count
+= scnprintf(buf
+ count
, PAGE_SIZE
- count
,
212 data
->patterns
[i
].brightness
,
213 data
->patterns
[i
].delta_t
);
216 buf
[count
- 1] = '\n';
219 mutex_unlock(&data
->lock
);
223 static int pattern_trig_store_patterns_string(struct pattern_trig_data
*data
,
224 const char *buf
, size_t count
)
226 int ccount
, cr
, offset
= 0;
228 while (offset
< count
- 1 && data
->npatterns
< MAX_PATTERNS
) {
230 ccount
= sscanf(buf
+ offset
, "%u %u %n",
231 &data
->patterns
[data
->npatterns
].brightness
,
232 &data
->patterns
[data
->npatterns
].delta_t
, &cr
);
235 data
->patterns
[data
->npatterns
].brightness
> data
->led_cdev
->max_brightness
) {
247 static int pattern_trig_store_patterns_int(struct pattern_trig_data
*data
,
248 const u32
*buf
, size_t count
)
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];
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
)
265 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
268 mutex_lock(&data
->lock
);
270 del_timer_sync(&data
->timer
);
272 if (data
->is_hw_pattern
)
273 led_cdev
->pattern_clear(led_cdev
);
275 data
->is_hw_pattern
= hw_pattern
;
279 err
= pattern_trig_store_patterns_string(data
, buf
, count
);
281 err
= pattern_trig_store_patterns_int(data
, buf_int
, count
);
285 err
= pattern_trig_start_pattern(led_cdev
);
290 mutex_unlock(&data
->lock
);
291 return err
< 0 ? err
: count
;
294 static ssize_t
pattern_show(struct device
*dev
, struct device_attribute
*attr
,
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
, false);
303 static ssize_t
pattern_store(struct device
*dev
, struct device_attribute
*attr
,
304 const char *buf
, size_t count
)
306 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
308 return pattern_trig_store_patterns(led_cdev
, buf
, NULL
, count
, false);
311 static DEVICE_ATTR_RW(pattern
);
313 static ssize_t
hw_pattern_show(struct device
*dev
,
314 struct device_attribute
*attr
, char *buf
)
316 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
317 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
319 return pattern_trig_show_patterns(data
, buf
, true);
322 static ssize_t
hw_pattern_store(struct device
*dev
,
323 struct device_attribute
*attr
,
324 const char *buf
, size_t count
)
326 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
328 return pattern_trig_store_patterns(led_cdev
, buf
, NULL
, count
, true);
331 static DEVICE_ATTR_RW(hw_pattern
);
333 static umode_t
pattern_trig_attrs_mode(struct kobject
*kobj
,
334 struct attribute
*attr
, int index
)
336 struct device
*dev
= container_of(kobj
, struct device
, kobj
);
337 struct led_classdev
*led_cdev
= dev_get_drvdata(dev
);
339 if (attr
== &dev_attr_repeat
.attr
|| attr
== &dev_attr_pattern
.attr
)
341 else if (attr
== &dev_attr_hw_pattern
.attr
&& led_cdev
->pattern_set
)
347 static struct attribute
*pattern_trig_attrs
[] = {
348 &dev_attr_pattern
.attr
,
349 &dev_attr_hw_pattern
.attr
,
350 &dev_attr_repeat
.attr
,
354 static const struct attribute_group pattern_trig_group
= {
355 .attrs
= pattern_trig_attrs
,
356 .is_visible
= pattern_trig_attrs_mode
,
359 static const struct attribute_group
*pattern_trig_groups
[] = {
364 static void pattern_init(struct led_classdev
*led_cdev
)
366 unsigned int size
= 0;
370 pattern
= led_get_default_pattern(led_cdev
, &size
);
375 dev_warn(led_cdev
->dev
, "Expected pattern of tuples\n");
379 err
= pattern_trig_store_patterns(led_cdev
, NULL
, pattern
, size
, false);
381 dev_warn(led_cdev
->dev
,
382 "Pattern initialization failed with error %d\n", err
);
388 static int pattern_trig_activate(struct led_classdev
*led_cdev
)
390 struct pattern_trig_data
*data
;
392 data
= kzalloc(sizeof(*data
), GFP_KERNEL
);
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
;
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
, data
);
408 timer_setup(&data
->timer
, pattern_trig_timer_function
, 0);
409 led_cdev
->activated
= true;
411 if (led_cdev
->flags
& LED_INIT_DEFAULT_TRIGGER
) {
412 pattern_init(led_cdev
);
414 * Mark as initialized even on pattern_init() error because
415 * any consecutive call to it would produce the same error.
417 led_cdev
->flags
&= ~LED_INIT_DEFAULT_TRIGGER
;
423 static void pattern_trig_deactivate(struct led_classdev
*led_cdev
)
425 struct pattern_trig_data
*data
= led_cdev
->trigger_data
;
427 if (!led_cdev
->activated
)
430 if (led_cdev
->pattern_clear
)
431 led_cdev
->pattern_clear(led_cdev
);
433 del_timer_sync(&data
->timer
);
435 led_set_brightness(led_cdev
, LED_OFF
);
437 led_cdev
->activated
= false;
440 static struct led_trigger pattern_led_trigger
= {
442 .activate
= pattern_trig_activate
,
443 .deactivate
= pattern_trig_deactivate
,
444 .groups
= pattern_trig_groups
,
447 static int __init
pattern_trig_init(void)
449 return led_trigger_register(&pattern_led_trigger
);
452 static void __exit
pattern_trig_exit(void)
454 led_trigger_unregister(&pattern_led_trigger
);
457 module_init(pattern_trig_init
);
458 module_exit(pattern_trig_exit
);
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");