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