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