Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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#include <linux/hrtimer.h>
 17
 18#define MAX_PATTERNS		1024
 19/*
 20 * When doing gradual dimming, the led brightness will be updated
 21 * every 50 milliseconds.
 22 */
 23#define UPDATE_INTERVAL		50
 24
 25enum pattern_type {
 26	PATTERN_TYPE_SW, /* Use standard timer for software pattern */
 27	PATTERN_TYPE_HR, /* Use hrtimer for software pattern */
 28	PATTERN_TYPE_HW, /* Hardware pattern */
 29};
 30
 31struct pattern_trig_data {
 32	struct led_classdev *led_cdev;
 33	struct led_pattern patterns[MAX_PATTERNS];
 34	struct led_pattern *curr;
 35	struct led_pattern *next;
 36	struct mutex lock;
 37	u32 npatterns;
 38	int repeat;
 39	int last_repeat;
 40	int delta_t;
 41	bool is_indefinite;
 42	enum pattern_type type;
 43	struct timer_list timer;
 44	struct hrtimer hrtimer;
 45};
 46
 47static void pattern_trig_update_patterns(struct pattern_trig_data *data)
 48{
 49	data->curr = data->next;
 50	if (!data->is_indefinite && data->curr == data->patterns)
 51		data->repeat--;
 52
 53	if (data->next == data->patterns + data->npatterns - 1)
 54		data->next = data->patterns;
 55	else
 56		data->next++;
 57
 58	data->delta_t = 0;
 59}
 60
 61static int pattern_trig_compute_brightness(struct pattern_trig_data *data)
 62{
 63	int step_brightness;
 64
 65	/*
 66	 * If current tuple's duration is less than the dimming interval,
 67	 * we should treat it as a step change of brightness instead of
 68	 * doing gradual dimming.
 69	 */
 70	if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL)
 71		return data->curr->brightness;
 72
 73	step_brightness = abs(data->next->brightness - data->curr->brightness);
 74	step_brightness = data->delta_t * step_brightness / data->curr->delta_t;
 75
 76	if (data->next->brightness > data->curr->brightness)
 77		return data->curr->brightness + step_brightness;
 78	else
 79		return data->curr->brightness - step_brightness;
 80}
 81
 82static void pattern_trig_timer_start(struct pattern_trig_data *data)
 83{
 84	if (data->type == PATTERN_TYPE_HR) {
 85		hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL);
 86	} else {
 87		data->timer.expires = jiffies;
 88		add_timer(&data->timer);
 89	}
 90}
 91
 92static void pattern_trig_timer_cancel(struct pattern_trig_data *data)
 93{
 94	if (data->type == PATTERN_TYPE_HR)
 95		hrtimer_cancel(&data->hrtimer);
 96	else
 97		del_timer_sync(&data->timer);
 98}
 99
100static void pattern_trig_timer_restart(struct pattern_trig_data *data,
101				       unsigned long interval)
102{
103	if (data->type == PATTERN_TYPE_HR)
104		hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval));
105	else
106		mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval));
107}
108
109static void pattern_trig_timer_common_function(struct pattern_trig_data *data)
110{
111	for (;;) {
112		if (!data->is_indefinite && !data->repeat)
113			break;
114
115		if (data->curr->brightness == data->next->brightness) {
116			/* Step change of brightness */
117			led_set_brightness(data->led_cdev,
118					   data->curr->brightness);
119			pattern_trig_timer_restart(data, data->curr->delta_t);
120			if (!data->next->delta_t) {
121				/* Skip the tuple with zero duration */
122				pattern_trig_update_patterns(data);
123			}
124			/* Select next tuple */
125			pattern_trig_update_patterns(data);
126		} else {
127			/* Gradual dimming */
128
129			/*
130			 * If the accumulation time is larger than current
131			 * tuple's duration, we should go next one and re-check
132			 * if we repeated done.
133			 */
134			if (data->delta_t > data->curr->delta_t) {
135				pattern_trig_update_patterns(data);
136				continue;
137			}
138
139			led_set_brightness(data->led_cdev,
140					   pattern_trig_compute_brightness(data));
141			pattern_trig_timer_restart(data, UPDATE_INTERVAL);
142
143			/* Accumulate the gradual dimming time */
144			data->delta_t += UPDATE_INTERVAL;
145		}
146
147		break;
148	}
149}
150
151static void pattern_trig_timer_function(struct timer_list *t)
152{
153	struct pattern_trig_data *data = from_timer(data, t, timer);
154
155	return pattern_trig_timer_common_function(data);
156}
157
158static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t)
159{
160	struct pattern_trig_data *data =
161		container_of(t, struct pattern_trig_data, hrtimer);
162
163	pattern_trig_timer_common_function(data);
164	if (!data->is_indefinite && !data->repeat)
165		return HRTIMER_NORESTART;
166
167	return HRTIMER_RESTART;
168}
169
170static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
171{
172	struct pattern_trig_data *data = led_cdev->trigger_data;
173
174	if (!data->npatterns)
175		return 0;
176
177	if (data->type == PATTERN_TYPE_HW) {
178		return led_cdev->pattern_set(led_cdev, data->patterns,
179					     data->npatterns, data->repeat);
180	}
181
182	/* At least 2 tuples for software pattern. */
183	if (data->npatterns < 2)
184		return -EINVAL;
185
186	data->delta_t = 0;
187	data->curr = data->patterns;
188	data->next = data->patterns + 1;
189	pattern_trig_timer_start(data);
190
191	return 0;
192}
193
194static ssize_t repeat_show(struct device *dev, struct device_attribute *attr,
195			   char *buf)
196{
197	struct led_classdev *led_cdev = dev_get_drvdata(dev);
198	struct pattern_trig_data *data = led_cdev->trigger_data;
199	int repeat;
200
201	mutex_lock(&data->lock);
202
203	repeat = data->last_repeat;
204
205	mutex_unlock(&data->lock);
206
207	return sysfs_emit(buf, "%d\n", repeat);
208}
209
210static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
211			    const char *buf, size_t count)
212{
213	struct led_classdev *led_cdev = dev_get_drvdata(dev);
214	struct pattern_trig_data *data = led_cdev->trigger_data;
215	int err, res;
216
217	err = kstrtos32(buf, 10, &res);
218	if (err)
219		return err;
220
221	/* Number 0 and negative numbers except -1 are invalid. */
222	if (res < -1 || res == 0)
223		return -EINVAL;
224
225	mutex_lock(&data->lock);
226
227	pattern_trig_timer_cancel(data);
228
229	if (data->type == PATTERN_TYPE_HW)
230		led_cdev->pattern_clear(led_cdev);
231
232	data->last_repeat = data->repeat = res;
233	/* -1 means repeat indefinitely */
234	if (data->repeat == -1)
235		data->is_indefinite = true;
236	else
237		data->is_indefinite = false;
238
239	err = pattern_trig_start_pattern(led_cdev);
240
241	mutex_unlock(&data->lock);
242	return err < 0 ? err : count;
243}
244
245static DEVICE_ATTR_RW(repeat);
246
247static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
248					  char *buf, enum pattern_type type)
249{
250	ssize_t count = 0;
251	int i;
252
253	mutex_lock(&data->lock);
254
255	if (!data->npatterns || data->type != type)
256		goto out;
257
258	for (i = 0; i < data->npatterns; i++) {
259		count += scnprintf(buf + count, PAGE_SIZE - count,
260				   "%d %u ",
261				   data->patterns[i].brightness,
262				   data->patterns[i].delta_t);
263	}
264
265	buf[count - 1] = '\n';
266
267out:
268	mutex_unlock(&data->lock);
269	return count;
270}
271
272static int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
273					      const char *buf, size_t count)
274{
275	int ccount, cr, offset = 0;
276
277	while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
278		cr = 0;
279		ccount = sscanf(buf + offset, "%u %u %n",
280				&data->patterns[data->npatterns].brightness,
281				&data->patterns[data->npatterns].delta_t, &cr);
282
283		if (ccount != 2 ||
284		    data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
285			data->npatterns = 0;
286			return -EINVAL;
287		}
288
289		offset += cr;
290		data->npatterns++;
291	}
292
293	return 0;
294}
295
296static int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
297					   const u32 *buf, size_t count)
298{
299	unsigned int i;
300
301	for (i = 0; i < count; i += 2) {
302		data->patterns[data->npatterns].brightness = buf[i];
303		data->patterns[data->npatterns].delta_t = buf[i + 1];
304		data->npatterns++;
305	}
306
307	return 0;
308}
309
310static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
311					   const char *buf, const u32 *buf_int,
312					   size_t count, enum pattern_type type)
313{
314	struct pattern_trig_data *data = led_cdev->trigger_data;
315	int err = 0;
316
317	mutex_lock(&data->lock);
318
319	pattern_trig_timer_cancel(data);
320
321	if (data->type == PATTERN_TYPE_HW)
322		led_cdev->pattern_clear(led_cdev);
323
324	data->type = type;
325	data->npatterns = 0;
326
327	if (buf)
328		err = pattern_trig_store_patterns_string(data, buf, count);
329	else
330		err = pattern_trig_store_patterns_int(data, buf_int, count);
331	if (err)
332		goto out;
333
334	err = pattern_trig_start_pattern(led_cdev);
335	if (err)
336		data->npatterns = 0;
337
338out:
339	mutex_unlock(&data->lock);
340	return err < 0 ? err : count;
341}
342
343static ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
344			    char *buf)
345{
346	struct led_classdev *led_cdev = dev_get_drvdata(dev);
347	struct pattern_trig_data *data = led_cdev->trigger_data;
348
349	return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW);
350}
351
352static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
353			     const char *buf, size_t count)
354{
355	struct led_classdev *led_cdev = dev_get_drvdata(dev);
356
357	return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
358					   PATTERN_TYPE_SW);
359}
360
361static DEVICE_ATTR_RW(pattern);
362
363static ssize_t hw_pattern_show(struct device *dev,
364			       struct device_attribute *attr, char *buf)
365{
366	struct led_classdev *led_cdev = dev_get_drvdata(dev);
367	struct pattern_trig_data *data = led_cdev->trigger_data;
368
369	return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW);
370}
371
372static ssize_t hw_pattern_store(struct device *dev,
373				struct device_attribute *attr,
374				const char *buf, size_t count)
375{
376	struct led_classdev *led_cdev = dev_get_drvdata(dev);
377
378	return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
379					   PATTERN_TYPE_HW);
380}
381
382static DEVICE_ATTR_RW(hw_pattern);
383
384static ssize_t hr_pattern_show(struct device *dev,
385			       struct device_attribute *attr, char *buf)
386{
387	struct led_classdev *led_cdev = dev_get_drvdata(dev);
388	struct pattern_trig_data *data = led_cdev->trigger_data;
389
390	return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR);
391}
392
393static ssize_t hr_pattern_store(struct device *dev,
394				struct device_attribute *attr,
395				const char *buf, size_t count)
396{
397	struct led_classdev *led_cdev = dev_get_drvdata(dev);
398
399	return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
400					   PATTERN_TYPE_HR);
401}
402
403static DEVICE_ATTR_RW(hr_pattern);
404
405static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
406				       struct attribute *attr, int index)
407{
408	struct device *dev = kobj_to_dev(kobj);
409	struct led_classdev *led_cdev = dev_get_drvdata(dev);
410
411	if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
412		return attr->mode;
413	else if (attr == &dev_attr_hr_pattern.attr)
414		return attr->mode;
415	else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
416		return attr->mode;
417
418	return 0;
419}
420
421static struct attribute *pattern_trig_attrs[] = {
422	&dev_attr_pattern.attr,
423	&dev_attr_hw_pattern.attr,
424	&dev_attr_hr_pattern.attr,
425	&dev_attr_repeat.attr,
426	NULL
427};
428
429static const struct attribute_group pattern_trig_group = {
430	.attrs = pattern_trig_attrs,
431	.is_visible = pattern_trig_attrs_mode,
432};
433
434static const struct attribute_group *pattern_trig_groups[] = {
435	&pattern_trig_group,
436	NULL,
437};
438
439static void pattern_init(struct led_classdev *led_cdev)
440{
441	unsigned int size = 0;
442	u32 *pattern;
443	int err;
444
445	pattern = led_get_default_pattern(led_cdev, &size);
446	if (!pattern)
447		return;
448
449	if (size % 2) {
450		dev_warn(led_cdev->dev, "Expected pattern of tuples\n");
451		goto out;
452	}
453
454	err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size,
455					  PATTERN_TYPE_SW);
456	if (err < 0)
457		dev_warn(led_cdev->dev,
458			 "Pattern initialization failed with error %d\n", err);
459
460out:
461	kfree(pattern);
462}
463
464static int pattern_trig_activate(struct led_classdev *led_cdev)
465{
466	struct pattern_trig_data *data;
467
468	data = kzalloc(sizeof(*data), GFP_KERNEL);
469	if (!data)
470		return -ENOMEM;
471
472	if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) {
473		dev_warn(led_cdev->dev,
474			 "Hardware pattern ops validation failed\n");
475		led_cdev->pattern_set = NULL;
476		led_cdev->pattern_clear = NULL;
477	}
478
479	data->type = PATTERN_TYPE_SW;
480	data->is_indefinite = true;
481	data->last_repeat = -1;
482	mutex_init(&data->lock);
483	data->led_cdev = led_cdev;
484	led_set_trigger_data(led_cdev, data);
485	timer_setup(&data->timer, pattern_trig_timer_function, 0);
486	hrtimer_init(&data->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
487	data->hrtimer.function = pattern_trig_hrtimer_function;
488	led_cdev->activated = true;
489
490	if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
491		pattern_init(led_cdev);
492		/*
493		 * Mark as initialized even on pattern_init() error because
494		 * any consecutive call to it would produce the same error.
495		 */
496		led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
497	}
498
499	return 0;
500}
501
502static void pattern_trig_deactivate(struct led_classdev *led_cdev)
503{
504	struct pattern_trig_data *data = led_cdev->trigger_data;
505
506	if (!led_cdev->activated)
507		return;
508
509	if (led_cdev->pattern_clear)
510		led_cdev->pattern_clear(led_cdev);
511
512	timer_shutdown_sync(&data->timer);
513	hrtimer_cancel(&data->hrtimer);
514
515	led_set_brightness(led_cdev, LED_OFF);
516	kfree(data);
517	led_cdev->activated = false;
518}
519
520static struct led_trigger pattern_led_trigger = {
521	.name = "pattern",
522	.activate = pattern_trig_activate,
523	.deactivate = pattern_trig_deactivate,
524	.groups = pattern_trig_groups,
525};
526
527static int __init pattern_trig_init(void)
528{
529	return led_trigger_register(&pattern_led_trigger);
530}
531
532static void __exit pattern_trig_exit(void)
533{
534	led_trigger_unregister(&pattern_led_trigger);
535}
536
537module_init(pattern_trig_init);
538module_exit(pattern_trig_exit);
539
540MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>");
541MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
542MODULE_DESCRIPTION("LED Pattern trigger");
543MODULE_LICENSE("GPL v2");