162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci/*
462306a36Sopenharmony_ci * LED pattern trigger
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Idea discussed with Pavel Machek. Raphael Teysseyre implemented
762306a36Sopenharmony_ci * the first version, Baolin Wang simplified and improved the approach.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/leds.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/mutex.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include <linux/timer.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define MAX_PATTERNS		1024
1862306a36Sopenharmony_ci/*
1962306a36Sopenharmony_ci * When doing gradual dimming, the led brightness will be updated
2062306a36Sopenharmony_ci * every 50 milliseconds.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci#define UPDATE_INTERVAL		50
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct pattern_trig_data {
2562306a36Sopenharmony_ci	struct led_classdev *led_cdev;
2662306a36Sopenharmony_ci	struct led_pattern patterns[MAX_PATTERNS];
2762306a36Sopenharmony_ci	struct led_pattern *curr;
2862306a36Sopenharmony_ci	struct led_pattern *next;
2962306a36Sopenharmony_ci	struct mutex lock;
3062306a36Sopenharmony_ci	u32 npatterns;
3162306a36Sopenharmony_ci	int repeat;
3262306a36Sopenharmony_ci	int last_repeat;
3362306a36Sopenharmony_ci	int delta_t;
3462306a36Sopenharmony_ci	bool is_indefinite;
3562306a36Sopenharmony_ci	bool is_hw_pattern;
3662306a36Sopenharmony_ci	struct timer_list timer;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic void pattern_trig_update_patterns(struct pattern_trig_data *data)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	data->curr = data->next;
4262306a36Sopenharmony_ci	if (!data->is_indefinite && data->curr == data->patterns)
4362306a36Sopenharmony_ci		data->repeat--;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	if (data->next == data->patterns + data->npatterns - 1)
4662306a36Sopenharmony_ci		data->next = data->patterns;
4762306a36Sopenharmony_ci	else
4862306a36Sopenharmony_ci		data->next++;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	data->delta_t = 0;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int pattern_trig_compute_brightness(struct pattern_trig_data *data)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	int step_brightness;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	/*
5862306a36Sopenharmony_ci	 * If current tuple's duration is less than the dimming interval,
5962306a36Sopenharmony_ci	 * we should treat it as a step change of brightness instead of
6062306a36Sopenharmony_ci	 * doing gradual dimming.
6162306a36Sopenharmony_ci	 */
6262306a36Sopenharmony_ci	if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL)
6362306a36Sopenharmony_ci		return data->curr->brightness;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	step_brightness = abs(data->next->brightness - data->curr->brightness);
6662306a36Sopenharmony_ci	step_brightness = data->delta_t * step_brightness / data->curr->delta_t;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (data->next->brightness > data->curr->brightness)
6962306a36Sopenharmony_ci		return data->curr->brightness + step_brightness;
7062306a36Sopenharmony_ci	else
7162306a36Sopenharmony_ci		return data->curr->brightness - step_brightness;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic void pattern_trig_timer_function(struct timer_list *t)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct pattern_trig_data *data = from_timer(data, t, timer);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	for (;;) {
7962306a36Sopenharmony_ci		if (!data->is_indefinite && !data->repeat)
8062306a36Sopenharmony_ci			break;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		if (data->curr->brightness == data->next->brightness) {
8362306a36Sopenharmony_ci			/* Step change of brightness */
8462306a36Sopenharmony_ci			led_set_brightness(data->led_cdev,
8562306a36Sopenharmony_ci					   data->curr->brightness);
8662306a36Sopenharmony_ci			mod_timer(&data->timer,
8762306a36Sopenharmony_ci				  jiffies + msecs_to_jiffies(data->curr->delta_t));
8862306a36Sopenharmony_ci			if (!data->next->delta_t) {
8962306a36Sopenharmony_ci				/* Skip the tuple with zero duration */
9062306a36Sopenharmony_ci				pattern_trig_update_patterns(data);
9162306a36Sopenharmony_ci			}
9262306a36Sopenharmony_ci			/* Select next tuple */
9362306a36Sopenharmony_ci			pattern_trig_update_patterns(data);
9462306a36Sopenharmony_ci		} else {
9562306a36Sopenharmony_ci			/* Gradual dimming */
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci			/*
9862306a36Sopenharmony_ci			 * If the accumulation time is larger than current
9962306a36Sopenharmony_ci			 * tuple's duration, we should go next one and re-check
10062306a36Sopenharmony_ci			 * if we repeated done.
10162306a36Sopenharmony_ci			 */
10262306a36Sopenharmony_ci			if (data->delta_t > data->curr->delta_t) {
10362306a36Sopenharmony_ci				pattern_trig_update_patterns(data);
10462306a36Sopenharmony_ci				continue;
10562306a36Sopenharmony_ci			}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci			led_set_brightness(data->led_cdev,
10862306a36Sopenharmony_ci					   pattern_trig_compute_brightness(data));
10962306a36Sopenharmony_ci			mod_timer(&data->timer,
11062306a36Sopenharmony_ci				  jiffies + msecs_to_jiffies(UPDATE_INTERVAL));
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci			/* Accumulate the gradual dimming time */
11362306a36Sopenharmony_ci			data->delta_t += UPDATE_INTERVAL;
11462306a36Sopenharmony_ci		}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		break;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic int pattern_trig_start_pattern(struct led_classdev *led_cdev)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	if (!data->npatterns)
12562306a36Sopenharmony_ci		return 0;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	if (data->is_hw_pattern) {
12862306a36Sopenharmony_ci		return led_cdev->pattern_set(led_cdev, data->patterns,
12962306a36Sopenharmony_ci					     data->npatterns, data->repeat);
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* At least 2 tuples for software pattern. */
13362306a36Sopenharmony_ci	if (data->npatterns < 2)
13462306a36Sopenharmony_ci		return -EINVAL;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	data->delta_t = 0;
13762306a36Sopenharmony_ci	data->curr = data->patterns;
13862306a36Sopenharmony_ci	data->next = data->patterns + 1;
13962306a36Sopenharmony_ci	data->timer.expires = jiffies;
14062306a36Sopenharmony_ci	add_timer(&data->timer);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return 0;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic ssize_t repeat_show(struct device *dev, struct device_attribute *attr,
14662306a36Sopenharmony_ci			   char *buf)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
14962306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
15062306a36Sopenharmony_ci	int repeat;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	mutex_lock(&data->lock);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	repeat = data->last_repeat;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	mutex_unlock(&data->lock);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	return sysfs_emit(buf, "%d\n", repeat);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
16262306a36Sopenharmony_ci			    const char *buf, size_t count)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
16562306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
16662306a36Sopenharmony_ci	int err, res;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	err = kstrtos32(buf, 10, &res);
16962306a36Sopenharmony_ci	if (err)
17062306a36Sopenharmony_ci		return err;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* Number 0 and negative numbers except -1 are invalid. */
17362306a36Sopenharmony_ci	if (res < -1 || res == 0)
17462306a36Sopenharmony_ci		return -EINVAL;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	mutex_lock(&data->lock);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	del_timer_sync(&data->timer);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (data->is_hw_pattern)
18162306a36Sopenharmony_ci		led_cdev->pattern_clear(led_cdev);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	data->last_repeat = data->repeat = res;
18462306a36Sopenharmony_ci	/* -1 means repeat indefinitely */
18562306a36Sopenharmony_ci	if (data->repeat == -1)
18662306a36Sopenharmony_ci		data->is_indefinite = true;
18762306a36Sopenharmony_ci	else
18862306a36Sopenharmony_ci		data->is_indefinite = false;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	err = pattern_trig_start_pattern(led_cdev);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	mutex_unlock(&data->lock);
19362306a36Sopenharmony_ci	return err < 0 ? err : count;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(repeat);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
19962306a36Sopenharmony_ci					  char *buf, bool hw_pattern)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	ssize_t count = 0;
20262306a36Sopenharmony_ci	int i;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	mutex_lock(&data->lock);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern))
20762306a36Sopenharmony_ci		goto out;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	for (i = 0; i < data->npatterns; i++) {
21062306a36Sopenharmony_ci		count += scnprintf(buf + count, PAGE_SIZE - count,
21162306a36Sopenharmony_ci				   "%d %u ",
21262306a36Sopenharmony_ci				   data->patterns[i].brightness,
21362306a36Sopenharmony_ci				   data->patterns[i].delta_t);
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	buf[count - 1] = '\n';
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ciout:
21962306a36Sopenharmony_ci	mutex_unlock(&data->lock);
22062306a36Sopenharmony_ci	return count;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
22462306a36Sopenharmony_ci					      const char *buf, size_t count)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	int ccount, cr, offset = 0;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
22962306a36Sopenharmony_ci		cr = 0;
23062306a36Sopenharmony_ci		ccount = sscanf(buf + offset, "%u %u %n",
23162306a36Sopenharmony_ci				&data->patterns[data->npatterns].brightness,
23262306a36Sopenharmony_ci				&data->patterns[data->npatterns].delta_t, &cr);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci		if (ccount != 2 ||
23562306a36Sopenharmony_ci		    data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
23662306a36Sopenharmony_ci			data->npatterns = 0;
23762306a36Sopenharmony_ci			return -EINVAL;
23862306a36Sopenharmony_ci		}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci		offset += cr;
24162306a36Sopenharmony_ci		data->npatterns++;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	return 0;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
24862306a36Sopenharmony_ci					   const u32 *buf, size_t count)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	unsigned int i;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	for (i = 0; i < count; i += 2) {
25362306a36Sopenharmony_ci		data->patterns[data->npatterns].brightness = buf[i];
25462306a36Sopenharmony_ci		data->patterns[data->npatterns].delta_t = buf[i + 1];
25562306a36Sopenharmony_ci		data->npatterns++;
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return 0;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
26262306a36Sopenharmony_ci					   const char *buf, const u32 *buf_int,
26362306a36Sopenharmony_ci					   size_t count, bool hw_pattern)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
26662306a36Sopenharmony_ci	int err = 0;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	mutex_lock(&data->lock);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	del_timer_sync(&data->timer);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (data->is_hw_pattern)
27362306a36Sopenharmony_ci		led_cdev->pattern_clear(led_cdev);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	data->is_hw_pattern = hw_pattern;
27662306a36Sopenharmony_ci	data->npatterns = 0;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	if (buf)
27962306a36Sopenharmony_ci		err = pattern_trig_store_patterns_string(data, buf, count);
28062306a36Sopenharmony_ci	else
28162306a36Sopenharmony_ci		err = pattern_trig_store_patterns_int(data, buf_int, count);
28262306a36Sopenharmony_ci	if (err)
28362306a36Sopenharmony_ci		goto out;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	err = pattern_trig_start_pattern(led_cdev);
28662306a36Sopenharmony_ci	if (err)
28762306a36Sopenharmony_ci		data->npatterns = 0;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ciout:
29062306a36Sopenharmony_ci	mutex_unlock(&data->lock);
29162306a36Sopenharmony_ci	return err < 0 ? err : count;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
29562306a36Sopenharmony_ci			    char *buf)
29662306a36Sopenharmony_ci{
29762306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
29862306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	return pattern_trig_show_patterns(data, buf, false);
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
30462306a36Sopenharmony_ci			     const char *buf, size_t count)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false);
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(pattern);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic ssize_t hw_pattern_show(struct device *dev,
31462306a36Sopenharmony_ci			       struct device_attribute *attr, char *buf)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
31762306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	return pattern_trig_show_patterns(data, buf, true);
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic ssize_t hw_pattern_store(struct device *dev,
32362306a36Sopenharmony_ci				struct device_attribute *attr,
32462306a36Sopenharmony_ci				const char *buf, size_t count)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true);
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(hw_pattern);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistatic umode_t pattern_trig_attrs_mode(struct kobject *kobj,
33462306a36Sopenharmony_ci				       struct attribute *attr, int index)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
33762306a36Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
34062306a36Sopenharmony_ci		return attr->mode;
34162306a36Sopenharmony_ci	else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
34262306a36Sopenharmony_ci		return attr->mode;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	return 0;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic struct attribute *pattern_trig_attrs[] = {
34862306a36Sopenharmony_ci	&dev_attr_pattern.attr,
34962306a36Sopenharmony_ci	&dev_attr_hw_pattern.attr,
35062306a36Sopenharmony_ci	&dev_attr_repeat.attr,
35162306a36Sopenharmony_ci	NULL
35262306a36Sopenharmony_ci};
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic const struct attribute_group pattern_trig_group = {
35562306a36Sopenharmony_ci	.attrs = pattern_trig_attrs,
35662306a36Sopenharmony_ci	.is_visible = pattern_trig_attrs_mode,
35762306a36Sopenharmony_ci};
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_cistatic const struct attribute_group *pattern_trig_groups[] = {
36062306a36Sopenharmony_ci	&pattern_trig_group,
36162306a36Sopenharmony_ci	NULL,
36262306a36Sopenharmony_ci};
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_cistatic void pattern_init(struct led_classdev *led_cdev)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	unsigned int size = 0;
36762306a36Sopenharmony_ci	u32 *pattern;
36862306a36Sopenharmony_ci	int err;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	pattern = led_get_default_pattern(led_cdev, &size);
37162306a36Sopenharmony_ci	if (!pattern)
37262306a36Sopenharmony_ci		return;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (size % 2) {
37562306a36Sopenharmony_ci		dev_warn(led_cdev->dev, "Expected pattern of tuples\n");
37662306a36Sopenharmony_ci		goto out;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false);
38062306a36Sopenharmony_ci	if (err < 0)
38162306a36Sopenharmony_ci		dev_warn(led_cdev->dev,
38262306a36Sopenharmony_ci			 "Pattern initialization failed with error %d\n", err);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ciout:
38562306a36Sopenharmony_ci	kfree(pattern);
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic int pattern_trig_activate(struct led_classdev *led_cdev)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	struct pattern_trig_data *data;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	data = kzalloc(sizeof(*data), GFP_KERNEL);
39362306a36Sopenharmony_ci	if (!data)
39462306a36Sopenharmony_ci		return -ENOMEM;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) {
39762306a36Sopenharmony_ci		dev_warn(led_cdev->dev,
39862306a36Sopenharmony_ci			 "Hardware pattern ops validation failed\n");
39962306a36Sopenharmony_ci		led_cdev->pattern_set = NULL;
40062306a36Sopenharmony_ci		led_cdev->pattern_clear = NULL;
40162306a36Sopenharmony_ci	}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	data->is_indefinite = true;
40462306a36Sopenharmony_ci	data->last_repeat = -1;
40562306a36Sopenharmony_ci	mutex_init(&data->lock);
40662306a36Sopenharmony_ci	data->led_cdev = led_cdev;
40762306a36Sopenharmony_ci	led_set_trigger_data(led_cdev, data);
40862306a36Sopenharmony_ci	timer_setup(&data->timer, pattern_trig_timer_function, 0);
40962306a36Sopenharmony_ci	led_cdev->activated = true;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
41262306a36Sopenharmony_ci		pattern_init(led_cdev);
41362306a36Sopenharmony_ci		/*
41462306a36Sopenharmony_ci		 * Mark as initialized even on pattern_init() error because
41562306a36Sopenharmony_ci		 * any consecutive call to it would produce the same error.
41662306a36Sopenharmony_ci		 */
41762306a36Sopenharmony_ci		led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
41862306a36Sopenharmony_ci	}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	return 0;
42162306a36Sopenharmony_ci}
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_cistatic void pattern_trig_deactivate(struct led_classdev *led_cdev)
42462306a36Sopenharmony_ci{
42562306a36Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	if (!led_cdev->activated)
42862306a36Sopenharmony_ci		return;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	if (led_cdev->pattern_clear)
43162306a36Sopenharmony_ci		led_cdev->pattern_clear(led_cdev);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	timer_shutdown_sync(&data->timer);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	led_set_brightness(led_cdev, LED_OFF);
43662306a36Sopenharmony_ci	kfree(data);
43762306a36Sopenharmony_ci	led_cdev->activated = false;
43862306a36Sopenharmony_ci}
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_cistatic struct led_trigger pattern_led_trigger = {
44162306a36Sopenharmony_ci	.name = "pattern",
44262306a36Sopenharmony_ci	.activate = pattern_trig_activate,
44362306a36Sopenharmony_ci	.deactivate = pattern_trig_deactivate,
44462306a36Sopenharmony_ci	.groups = pattern_trig_groups,
44562306a36Sopenharmony_ci};
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_cistatic int __init pattern_trig_init(void)
44862306a36Sopenharmony_ci{
44962306a36Sopenharmony_ci	return led_trigger_register(&pattern_led_trigger);
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic void __exit pattern_trig_exit(void)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	led_trigger_unregister(&pattern_led_trigger);
45562306a36Sopenharmony_ci}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_cimodule_init(pattern_trig_init);
45862306a36Sopenharmony_cimodule_exit(pattern_trig_exit);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ciMODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>");
46162306a36Sopenharmony_ciMODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
46262306a36Sopenharmony_ciMODULE_DESCRIPTION("LED Pattern trigger");
46362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
464