18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci/*
48c2ecf20Sopenharmony_ci * LED pattern trigger
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Idea discussed with Pavel Machek. Raphael Teysseyre implemented
78c2ecf20Sopenharmony_ci * the first version, Baolin Wang simplified and improved the approach.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/leds.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/mutex.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci#include <linux/timer.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define MAX_PATTERNS		1024
188c2ecf20Sopenharmony_ci/*
198c2ecf20Sopenharmony_ci * When doing gradual dimming, the led brightness will be updated
208c2ecf20Sopenharmony_ci * every 50 milliseconds.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci#define UPDATE_INTERVAL		50
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistruct pattern_trig_data {
258c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev;
268c2ecf20Sopenharmony_ci	struct led_pattern patterns[MAX_PATTERNS];
278c2ecf20Sopenharmony_ci	struct led_pattern *curr;
288c2ecf20Sopenharmony_ci	struct led_pattern *next;
298c2ecf20Sopenharmony_ci	struct mutex lock;
308c2ecf20Sopenharmony_ci	u32 npatterns;
318c2ecf20Sopenharmony_ci	int repeat;
328c2ecf20Sopenharmony_ci	int last_repeat;
338c2ecf20Sopenharmony_ci	int delta_t;
348c2ecf20Sopenharmony_ci	bool is_indefinite;
358c2ecf20Sopenharmony_ci	bool is_hw_pattern;
368c2ecf20Sopenharmony_ci	struct timer_list timer;
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic void pattern_trig_update_patterns(struct pattern_trig_data *data)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	data->curr = data->next;
428c2ecf20Sopenharmony_ci	if (!data->is_indefinite && data->curr == data->patterns)
438c2ecf20Sopenharmony_ci		data->repeat--;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	if (data->next == data->patterns + data->npatterns - 1)
468c2ecf20Sopenharmony_ci		data->next = data->patterns;
478c2ecf20Sopenharmony_ci	else
488c2ecf20Sopenharmony_ci		data->next++;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	data->delta_t = 0;
518c2ecf20Sopenharmony_ci}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int pattern_trig_compute_brightness(struct pattern_trig_data *data)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	int step_brightness;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/*
588c2ecf20Sopenharmony_ci	 * If current tuple's duration is less than the dimming interval,
598c2ecf20Sopenharmony_ci	 * we should treat it as a step change of brightness instead of
608c2ecf20Sopenharmony_ci	 * doing gradual dimming.
618c2ecf20Sopenharmony_ci	 */
628c2ecf20Sopenharmony_ci	if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL)
638c2ecf20Sopenharmony_ci		return data->curr->brightness;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	step_brightness = abs(data->next->brightness - data->curr->brightness);
668c2ecf20Sopenharmony_ci	step_brightness = data->delta_t * step_brightness / data->curr->delta_t;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (data->next->brightness > data->curr->brightness)
698c2ecf20Sopenharmony_ci		return data->curr->brightness + step_brightness;
708c2ecf20Sopenharmony_ci	else
718c2ecf20Sopenharmony_ci		return data->curr->brightness - step_brightness;
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic void pattern_trig_timer_function(struct timer_list *t)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = from_timer(data, t, timer);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	for (;;) {
798c2ecf20Sopenharmony_ci		if (!data->is_indefinite && !data->repeat)
808c2ecf20Sopenharmony_ci			break;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci		if (data->curr->brightness == data->next->brightness) {
838c2ecf20Sopenharmony_ci			/* Step change of brightness */
848c2ecf20Sopenharmony_ci			led_set_brightness(data->led_cdev,
858c2ecf20Sopenharmony_ci					   data->curr->brightness);
868c2ecf20Sopenharmony_ci			mod_timer(&data->timer,
878c2ecf20Sopenharmony_ci				  jiffies + msecs_to_jiffies(data->curr->delta_t));
888c2ecf20Sopenharmony_ci			if (!data->next->delta_t) {
898c2ecf20Sopenharmony_ci				/* Skip the tuple with zero duration */
908c2ecf20Sopenharmony_ci				pattern_trig_update_patterns(data);
918c2ecf20Sopenharmony_ci			}
928c2ecf20Sopenharmony_ci			/* Select next tuple */
938c2ecf20Sopenharmony_ci			pattern_trig_update_patterns(data);
948c2ecf20Sopenharmony_ci		} else {
958c2ecf20Sopenharmony_ci			/* Gradual dimming */
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci			/*
988c2ecf20Sopenharmony_ci			 * If the accumulation time is larger than current
998c2ecf20Sopenharmony_ci			 * tuple's duration, we should go next one and re-check
1008c2ecf20Sopenharmony_ci			 * if we repeated done.
1018c2ecf20Sopenharmony_ci			 */
1028c2ecf20Sopenharmony_ci			if (data->delta_t > data->curr->delta_t) {
1038c2ecf20Sopenharmony_ci				pattern_trig_update_patterns(data);
1048c2ecf20Sopenharmony_ci				continue;
1058c2ecf20Sopenharmony_ci			}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci			led_set_brightness(data->led_cdev,
1088c2ecf20Sopenharmony_ci					   pattern_trig_compute_brightness(data));
1098c2ecf20Sopenharmony_ci			mod_timer(&data->timer,
1108c2ecf20Sopenharmony_ci				  jiffies + msecs_to_jiffies(UPDATE_INTERVAL));
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci			/* Accumulate the gradual dimming time */
1138c2ecf20Sopenharmony_ci			data->delta_t += UPDATE_INTERVAL;
1148c2ecf20Sopenharmony_ci		}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci		break;
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int pattern_trig_start_pattern(struct led_classdev *led_cdev)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (!data->npatterns)
1258c2ecf20Sopenharmony_ci		return 0;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	if (data->is_hw_pattern) {
1288c2ecf20Sopenharmony_ci		return led_cdev->pattern_set(led_cdev, data->patterns,
1298c2ecf20Sopenharmony_ci					     data->npatterns, data->repeat);
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	/* At least 2 tuples for software pattern. */
1338c2ecf20Sopenharmony_ci	if (data->npatterns < 2)
1348c2ecf20Sopenharmony_ci		return -EINVAL;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	data->delta_t = 0;
1378c2ecf20Sopenharmony_ci	data->curr = data->patterns;
1388c2ecf20Sopenharmony_ci	data->next = data->patterns + 1;
1398c2ecf20Sopenharmony_ci	data->timer.expires = jiffies;
1408c2ecf20Sopenharmony_ci	add_timer(&data->timer);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return 0;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic ssize_t repeat_show(struct device *dev, struct device_attribute *attr,
1468c2ecf20Sopenharmony_ci			   char *buf)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
1498c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
1508c2ecf20Sopenharmony_ci	int repeat;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	mutex_lock(&data->lock);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	repeat = data->last_repeat;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	mutex_unlock(&data->lock);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	return scnprintf(buf, PAGE_SIZE, "%d\n", repeat);
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
1628c2ecf20Sopenharmony_ci			    const char *buf, size_t count)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
1658c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
1668c2ecf20Sopenharmony_ci	int err, res;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	err = kstrtos32(buf, 10, &res);
1698c2ecf20Sopenharmony_ci	if (err)
1708c2ecf20Sopenharmony_ci		return err;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	/* Number 0 and negative numbers except -1 are invalid. */
1738c2ecf20Sopenharmony_ci	if (res < -1 || res == 0)
1748c2ecf20Sopenharmony_ci		return -EINVAL;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	mutex_lock(&data->lock);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	del_timer_sync(&data->timer);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	if (data->is_hw_pattern)
1818c2ecf20Sopenharmony_ci		led_cdev->pattern_clear(led_cdev);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	data->last_repeat = data->repeat = res;
1848c2ecf20Sopenharmony_ci	/* -1 means repeat indefinitely */
1858c2ecf20Sopenharmony_ci	if (data->repeat == -1)
1868c2ecf20Sopenharmony_ci		data->is_indefinite = true;
1878c2ecf20Sopenharmony_ci	else
1888c2ecf20Sopenharmony_ci		data->is_indefinite = false;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	err = pattern_trig_start_pattern(led_cdev);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	mutex_unlock(&data->lock);
1938c2ecf20Sopenharmony_ci	return err < 0 ? err : count;
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(repeat);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
1998c2ecf20Sopenharmony_ci					  char *buf, bool hw_pattern)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	ssize_t count = 0;
2028c2ecf20Sopenharmony_ci	int i;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	mutex_lock(&data->lock);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern))
2078c2ecf20Sopenharmony_ci		goto out;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	for (i = 0; i < data->npatterns; i++) {
2108c2ecf20Sopenharmony_ci		count += scnprintf(buf + count, PAGE_SIZE - count,
2118c2ecf20Sopenharmony_ci				   "%d %u ",
2128c2ecf20Sopenharmony_ci				   data->patterns[i].brightness,
2138c2ecf20Sopenharmony_ci				   data->patterns[i].delta_t);
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	buf[count - 1] = '\n';
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ciout:
2198c2ecf20Sopenharmony_ci	mutex_unlock(&data->lock);
2208c2ecf20Sopenharmony_ci	return count;
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cistatic int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
2248c2ecf20Sopenharmony_ci					      const char *buf, size_t count)
2258c2ecf20Sopenharmony_ci{
2268c2ecf20Sopenharmony_ci	int ccount, cr, offset = 0;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
2298c2ecf20Sopenharmony_ci		cr = 0;
2308c2ecf20Sopenharmony_ci		ccount = sscanf(buf + offset, "%u %u %n",
2318c2ecf20Sopenharmony_ci				&data->patterns[data->npatterns].brightness,
2328c2ecf20Sopenharmony_ci				&data->patterns[data->npatterns].delta_t, &cr);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci		if (ccount != 2 ||
2358c2ecf20Sopenharmony_ci		    data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
2368c2ecf20Sopenharmony_ci			data->npatterns = 0;
2378c2ecf20Sopenharmony_ci			return -EINVAL;
2388c2ecf20Sopenharmony_ci		}
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci		offset += cr;
2418c2ecf20Sopenharmony_ci		data->npatterns++;
2428c2ecf20Sopenharmony_ci	}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	return 0;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
2488c2ecf20Sopenharmony_ci					   const u32 *buf, size_t count)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	unsigned int i;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	for (i = 0; i < count; i += 2) {
2538c2ecf20Sopenharmony_ci		data->patterns[data->npatterns].brightness = buf[i];
2548c2ecf20Sopenharmony_ci		data->patterns[data->npatterns].delta_t = buf[i + 1];
2558c2ecf20Sopenharmony_ci		data->npatterns++;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	return 0;
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
2628c2ecf20Sopenharmony_ci					   const char *buf, const u32 *buf_int,
2638c2ecf20Sopenharmony_ci					   size_t count, bool hw_pattern)
2648c2ecf20Sopenharmony_ci{
2658c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
2668c2ecf20Sopenharmony_ci	int err = 0;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	mutex_lock(&data->lock);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	del_timer_sync(&data->timer);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	if (data->is_hw_pattern)
2738c2ecf20Sopenharmony_ci		led_cdev->pattern_clear(led_cdev);
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	data->is_hw_pattern = hw_pattern;
2768c2ecf20Sopenharmony_ci	data->npatterns = 0;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	if (buf)
2798c2ecf20Sopenharmony_ci		err = pattern_trig_store_patterns_string(data, buf, count);
2808c2ecf20Sopenharmony_ci	else
2818c2ecf20Sopenharmony_ci		err = pattern_trig_store_patterns_int(data, buf_int, count);
2828c2ecf20Sopenharmony_ci	if (err)
2838c2ecf20Sopenharmony_ci		goto out;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	err = pattern_trig_start_pattern(led_cdev);
2868c2ecf20Sopenharmony_ci	if (err)
2878c2ecf20Sopenharmony_ci		data->npatterns = 0;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ciout:
2908c2ecf20Sopenharmony_ci	mutex_unlock(&data->lock);
2918c2ecf20Sopenharmony_ci	return err < 0 ? err : count;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistatic ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
2958c2ecf20Sopenharmony_ci			    char *buf)
2968c2ecf20Sopenharmony_ci{
2978c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
2988c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	return pattern_trig_show_patterns(data, buf, false);
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
3048c2ecf20Sopenharmony_ci			     const char *buf, size_t count)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false);
3098c2ecf20Sopenharmony_ci}
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(pattern);
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_cistatic ssize_t hw_pattern_show(struct device *dev,
3148c2ecf20Sopenharmony_ci			       struct device_attribute *attr, char *buf)
3158c2ecf20Sopenharmony_ci{
3168c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
3178c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	return pattern_trig_show_patterns(data, buf, true);
3208c2ecf20Sopenharmony_ci}
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_cistatic ssize_t hw_pattern_store(struct device *dev,
3238c2ecf20Sopenharmony_ci				struct device_attribute *attr,
3248c2ecf20Sopenharmony_ci				const char *buf, size_t count)
3258c2ecf20Sopenharmony_ci{
3268c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true);
3298c2ecf20Sopenharmony_ci}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(hw_pattern);
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_cistatic umode_t pattern_trig_attrs_mode(struct kobject *kobj,
3348c2ecf20Sopenharmony_ci				       struct attribute *attr, int index)
3358c2ecf20Sopenharmony_ci{
3368c2ecf20Sopenharmony_ci	struct device *dev = container_of(kobj, struct device, kobj);
3378c2ecf20Sopenharmony_ci	struct led_classdev *led_cdev = dev_get_drvdata(dev);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
3408c2ecf20Sopenharmony_ci		return attr->mode;
3418c2ecf20Sopenharmony_ci	else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
3428c2ecf20Sopenharmony_ci		return attr->mode;
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	return 0;
3458c2ecf20Sopenharmony_ci}
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_cistatic struct attribute *pattern_trig_attrs[] = {
3488c2ecf20Sopenharmony_ci	&dev_attr_pattern.attr,
3498c2ecf20Sopenharmony_ci	&dev_attr_hw_pattern.attr,
3508c2ecf20Sopenharmony_ci	&dev_attr_repeat.attr,
3518c2ecf20Sopenharmony_ci	NULL
3528c2ecf20Sopenharmony_ci};
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_cistatic const struct attribute_group pattern_trig_group = {
3558c2ecf20Sopenharmony_ci	.attrs = pattern_trig_attrs,
3568c2ecf20Sopenharmony_ci	.is_visible = pattern_trig_attrs_mode,
3578c2ecf20Sopenharmony_ci};
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_cistatic const struct attribute_group *pattern_trig_groups[] = {
3608c2ecf20Sopenharmony_ci	&pattern_trig_group,
3618c2ecf20Sopenharmony_ci	NULL,
3628c2ecf20Sopenharmony_ci};
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_cistatic void pattern_init(struct led_classdev *led_cdev)
3658c2ecf20Sopenharmony_ci{
3668c2ecf20Sopenharmony_ci	unsigned int size = 0;
3678c2ecf20Sopenharmony_ci	u32 *pattern;
3688c2ecf20Sopenharmony_ci	int err;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	pattern = led_get_default_pattern(led_cdev, &size);
3718c2ecf20Sopenharmony_ci	if (!pattern)
3728c2ecf20Sopenharmony_ci		return;
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	if (size % 2) {
3758c2ecf20Sopenharmony_ci		dev_warn(led_cdev->dev, "Expected pattern of tuples\n");
3768c2ecf20Sopenharmony_ci		goto out;
3778c2ecf20Sopenharmony_ci	}
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false);
3808c2ecf20Sopenharmony_ci	if (err < 0)
3818c2ecf20Sopenharmony_ci		dev_warn(led_cdev->dev,
3828c2ecf20Sopenharmony_ci			 "Pattern initialization failed with error %d\n", err);
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ciout:
3858c2ecf20Sopenharmony_ci	kfree(pattern);
3868c2ecf20Sopenharmony_ci}
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_cistatic int pattern_trig_activate(struct led_classdev *led_cdev)
3898c2ecf20Sopenharmony_ci{
3908c2ecf20Sopenharmony_ci	struct pattern_trig_data *data;
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	data = kzalloc(sizeof(*data), GFP_KERNEL);
3938c2ecf20Sopenharmony_ci	if (!data)
3948c2ecf20Sopenharmony_ci		return -ENOMEM;
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) {
3978c2ecf20Sopenharmony_ci		dev_warn(led_cdev->dev,
3988c2ecf20Sopenharmony_ci			 "Hardware pattern ops validation failed\n");
3998c2ecf20Sopenharmony_ci		led_cdev->pattern_set = NULL;
4008c2ecf20Sopenharmony_ci		led_cdev->pattern_clear = NULL;
4018c2ecf20Sopenharmony_ci	}
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	data->is_indefinite = true;
4048c2ecf20Sopenharmony_ci	data->last_repeat = -1;
4058c2ecf20Sopenharmony_ci	mutex_init(&data->lock);
4068c2ecf20Sopenharmony_ci	data->led_cdev = led_cdev;
4078c2ecf20Sopenharmony_ci	led_set_trigger_data(led_cdev, data);
4088c2ecf20Sopenharmony_ci	timer_setup(&data->timer, pattern_trig_timer_function, 0);
4098c2ecf20Sopenharmony_ci	led_cdev->activated = true;
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
4128c2ecf20Sopenharmony_ci		pattern_init(led_cdev);
4138c2ecf20Sopenharmony_ci		/*
4148c2ecf20Sopenharmony_ci		 * Mark as initialized even on pattern_init() error because
4158c2ecf20Sopenharmony_ci		 * any consecutive call to it would produce the same error.
4168c2ecf20Sopenharmony_ci		 */
4178c2ecf20Sopenharmony_ci		led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
4188c2ecf20Sopenharmony_ci	}
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	return 0;
4218c2ecf20Sopenharmony_ci}
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_cistatic void pattern_trig_deactivate(struct led_classdev *led_cdev)
4248c2ecf20Sopenharmony_ci{
4258c2ecf20Sopenharmony_ci	struct pattern_trig_data *data = led_cdev->trigger_data;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	if (!led_cdev->activated)
4288c2ecf20Sopenharmony_ci		return;
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci	if (led_cdev->pattern_clear)
4318c2ecf20Sopenharmony_ci		led_cdev->pattern_clear(led_cdev);
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	del_timer_sync(&data->timer);
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	led_set_brightness(led_cdev, LED_OFF);
4368c2ecf20Sopenharmony_ci	kfree(data);
4378c2ecf20Sopenharmony_ci	led_cdev->activated = false;
4388c2ecf20Sopenharmony_ci}
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_cistatic struct led_trigger pattern_led_trigger = {
4418c2ecf20Sopenharmony_ci	.name = "pattern",
4428c2ecf20Sopenharmony_ci	.activate = pattern_trig_activate,
4438c2ecf20Sopenharmony_ci	.deactivate = pattern_trig_deactivate,
4448c2ecf20Sopenharmony_ci	.groups = pattern_trig_groups,
4458c2ecf20Sopenharmony_ci};
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_cistatic int __init pattern_trig_init(void)
4488c2ecf20Sopenharmony_ci{
4498c2ecf20Sopenharmony_ci	return led_trigger_register(&pattern_led_trigger);
4508c2ecf20Sopenharmony_ci}
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_cistatic void __exit pattern_trig_exit(void)
4538c2ecf20Sopenharmony_ci{
4548c2ecf20Sopenharmony_ci	led_trigger_unregister(&pattern_led_trigger);
4558c2ecf20Sopenharmony_ci}
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_cimodule_init(pattern_trig_init);
4588c2ecf20Sopenharmony_cimodule_exit(pattern_trig_exit);
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ciMODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>");
4618c2ecf20Sopenharmony_ciMODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
4628c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LED Pattern trigger");
4638c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
464