15fd752b6SBaolin Wang // SPDX-License-Identifier: GPL-2.0 25fd752b6SBaolin Wang 35fd752b6SBaolin Wang /* 45fd752b6SBaolin Wang * LED pattern trigger 55fd752b6SBaolin Wang * 65fd752b6SBaolin Wang * Idea discussed with Pavel Machek. Raphael Teysseyre implemented 75fd752b6SBaolin Wang * the first version, Baolin Wang simplified and improved the approach. 85fd752b6SBaolin Wang */ 95fd752b6SBaolin Wang 105fd752b6SBaolin Wang #include <linux/kernel.h> 115fd752b6SBaolin Wang #include <linux/leds.h> 125fd752b6SBaolin Wang #include <linux/module.h> 135fd752b6SBaolin Wang #include <linux/mutex.h> 145fd752b6SBaolin Wang #include <linux/slab.h> 155fd752b6SBaolin Wang #include <linux/timer.h> 16aa172ba7SMartin Kurbanov #include <linux/hrtimer.h> 175fd752b6SBaolin Wang 185fd752b6SBaolin Wang #define MAX_PATTERNS 1024 195fd752b6SBaolin Wang /* 205fd752b6SBaolin Wang * When doing gradual dimming, the led brightness will be updated 215fd752b6SBaolin Wang * every 50 milliseconds. 225fd752b6SBaolin Wang */ 235fd752b6SBaolin Wang #define UPDATE_INTERVAL 50 245fd752b6SBaolin Wang 25aa172ba7SMartin Kurbanov enum pattern_type { 26aa172ba7SMartin Kurbanov PATTERN_TYPE_SW, /* Use standard timer for software pattern */ 27aa172ba7SMartin Kurbanov PATTERN_TYPE_HR, /* Use hrtimer for software pattern */ 28aa172ba7SMartin Kurbanov PATTERN_TYPE_HW, /* Hardware pattern */ 29aa172ba7SMartin Kurbanov }; 30aa172ba7SMartin Kurbanov 315fd752b6SBaolin Wang struct pattern_trig_data { 325fd752b6SBaolin Wang struct led_classdev *led_cdev; 335fd752b6SBaolin Wang struct led_pattern patterns[MAX_PATTERNS]; 345fd752b6SBaolin Wang struct led_pattern *curr; 355fd752b6SBaolin Wang struct led_pattern *next; 365fd752b6SBaolin Wang struct mutex lock; 375fd752b6SBaolin Wang u32 npatterns; 385fd752b6SBaolin Wang int repeat; 395fd752b6SBaolin Wang int last_repeat; 405fd752b6SBaolin Wang int delta_t; 415fd752b6SBaolin Wang bool is_indefinite; 42aa172ba7SMartin Kurbanov enum pattern_type type; 435fd752b6SBaolin Wang struct timer_list timer; 44aa172ba7SMartin Kurbanov struct hrtimer hrtimer; 455fd752b6SBaolin Wang }; 465fd752b6SBaolin Wang 475fd752b6SBaolin Wang static void pattern_trig_update_patterns(struct pattern_trig_data *data) 485fd752b6SBaolin Wang { 495fd752b6SBaolin Wang data->curr = data->next; 505fd752b6SBaolin Wang if (!data->is_indefinite && data->curr == data->patterns) 515fd752b6SBaolin Wang data->repeat--; 525fd752b6SBaolin Wang 535fd752b6SBaolin Wang if (data->next == data->patterns + data->npatterns - 1) 545fd752b6SBaolin Wang data->next = data->patterns; 555fd752b6SBaolin Wang else 565fd752b6SBaolin Wang data->next++; 575fd752b6SBaolin Wang 585fd752b6SBaolin Wang data->delta_t = 0; 595fd752b6SBaolin Wang } 605fd752b6SBaolin Wang 615fd752b6SBaolin Wang static int pattern_trig_compute_brightness(struct pattern_trig_data *data) 625fd752b6SBaolin Wang { 635fd752b6SBaolin Wang int step_brightness; 645fd752b6SBaolin Wang 655fd752b6SBaolin Wang /* 665fd752b6SBaolin Wang * If current tuple's duration is less than the dimming interval, 675fd752b6SBaolin Wang * we should treat it as a step change of brightness instead of 685fd752b6SBaolin Wang * doing gradual dimming. 695fd752b6SBaolin Wang */ 705fd752b6SBaolin Wang if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL) 715fd752b6SBaolin Wang return data->curr->brightness; 725fd752b6SBaolin Wang 735fd752b6SBaolin Wang step_brightness = abs(data->next->brightness - data->curr->brightness); 745fd752b6SBaolin Wang step_brightness = data->delta_t * step_brightness / data->curr->delta_t; 755fd752b6SBaolin Wang 765fd752b6SBaolin Wang if (data->next->brightness > data->curr->brightness) 775fd752b6SBaolin Wang return data->curr->brightness + step_brightness; 785fd752b6SBaolin Wang else 795fd752b6SBaolin Wang return data->curr->brightness - step_brightness; 805fd752b6SBaolin Wang } 815fd752b6SBaolin Wang 82aa172ba7SMartin Kurbanov static void pattern_trig_timer_start(struct pattern_trig_data *data) 835fd752b6SBaolin Wang { 84aa172ba7SMartin Kurbanov if (data->type == PATTERN_TYPE_HR) { 85aa172ba7SMartin Kurbanov hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL); 86aa172ba7SMartin Kurbanov } else { 87aa172ba7SMartin Kurbanov data->timer.expires = jiffies; 88aa172ba7SMartin Kurbanov add_timer(&data->timer); 89aa172ba7SMartin Kurbanov } 90aa172ba7SMartin Kurbanov } 915fd752b6SBaolin Wang 92aa172ba7SMartin Kurbanov static void pattern_trig_timer_cancel(struct pattern_trig_data *data) 93aa172ba7SMartin Kurbanov { 94aa172ba7SMartin Kurbanov if (data->type == PATTERN_TYPE_HR) 95aa172ba7SMartin Kurbanov hrtimer_cancel(&data->hrtimer); 96aa172ba7SMartin Kurbanov else 97aa172ba7SMartin Kurbanov del_timer_sync(&data->timer); 98aa172ba7SMartin Kurbanov } 99aa172ba7SMartin Kurbanov 100aa172ba7SMartin Kurbanov static void pattern_trig_timer_restart(struct pattern_trig_data *data, 101aa172ba7SMartin Kurbanov unsigned long interval) 102aa172ba7SMartin Kurbanov { 103aa172ba7SMartin Kurbanov if (data->type == PATTERN_TYPE_HR) 104aa172ba7SMartin Kurbanov hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval)); 105aa172ba7SMartin Kurbanov else 106aa172ba7SMartin Kurbanov mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval)); 107aa172ba7SMartin Kurbanov } 108aa172ba7SMartin Kurbanov 109aa172ba7SMartin Kurbanov static void pattern_trig_timer_common_function(struct pattern_trig_data *data) 110aa172ba7SMartin Kurbanov { 1115fd752b6SBaolin Wang for (;;) { 1125fd752b6SBaolin Wang if (!data->is_indefinite && !data->repeat) 1135fd752b6SBaolin Wang break; 1145fd752b6SBaolin Wang 1155fd752b6SBaolin Wang if (data->curr->brightness == data->next->brightness) { 1165fd752b6SBaolin Wang /* Step change of brightness */ 1175fd752b6SBaolin Wang led_set_brightness(data->led_cdev, 1185fd752b6SBaolin Wang data->curr->brightness); 119aa172ba7SMartin Kurbanov pattern_trig_timer_restart(data, data->curr->delta_t); 1201b50bb4dSPavel Machek if (!data->next->delta_t) { 1215fd752b6SBaolin Wang /* Skip the tuple with zero duration */ 1225fd752b6SBaolin Wang pattern_trig_update_patterns(data); 1231b50bb4dSPavel Machek } 1245fd752b6SBaolin Wang /* Select next tuple */ 1255fd752b6SBaolin Wang pattern_trig_update_patterns(data); 1265fd752b6SBaolin Wang } else { 1275fd752b6SBaolin Wang /* Gradual dimming */ 1285fd752b6SBaolin Wang 1295fd752b6SBaolin Wang /* 1305fd752b6SBaolin Wang * If the accumulation time is larger than current 1315fd752b6SBaolin Wang * tuple's duration, we should go next one and re-check 1325fd752b6SBaolin Wang * if we repeated done. 1335fd752b6SBaolin Wang */ 1345fd752b6SBaolin Wang if (data->delta_t > data->curr->delta_t) { 1355fd752b6SBaolin Wang pattern_trig_update_patterns(data); 1365fd752b6SBaolin Wang continue; 1375fd752b6SBaolin Wang } 1385fd752b6SBaolin Wang 1395fd752b6SBaolin Wang led_set_brightness(data->led_cdev, 1405fd752b6SBaolin Wang pattern_trig_compute_brightness(data)); 141aa172ba7SMartin Kurbanov pattern_trig_timer_restart(data, UPDATE_INTERVAL); 1425fd752b6SBaolin Wang 1435fd752b6SBaolin Wang /* Accumulate the gradual dimming time */ 1445fd752b6SBaolin Wang data->delta_t += UPDATE_INTERVAL; 1455fd752b6SBaolin Wang } 1465fd752b6SBaolin Wang 1475fd752b6SBaolin Wang break; 1485fd752b6SBaolin Wang } 1495fd752b6SBaolin Wang } 1505fd752b6SBaolin Wang 151aa172ba7SMartin Kurbanov static void pattern_trig_timer_function(struct timer_list *t) 152aa172ba7SMartin Kurbanov { 153aa172ba7SMartin Kurbanov struct pattern_trig_data *data = from_timer(data, t, timer); 154aa172ba7SMartin Kurbanov 155aa172ba7SMartin Kurbanov return pattern_trig_timer_common_function(data); 156aa172ba7SMartin Kurbanov } 157aa172ba7SMartin Kurbanov 158aa172ba7SMartin Kurbanov static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t) 159aa172ba7SMartin Kurbanov { 160aa172ba7SMartin Kurbanov struct pattern_trig_data *data = 161aa172ba7SMartin Kurbanov container_of(t, struct pattern_trig_data, hrtimer); 162aa172ba7SMartin Kurbanov 163aa172ba7SMartin Kurbanov pattern_trig_timer_common_function(data); 164aa172ba7SMartin Kurbanov if (!data->is_indefinite && !data->repeat) 165aa172ba7SMartin Kurbanov return HRTIMER_NORESTART; 166aa172ba7SMartin Kurbanov 167aa172ba7SMartin Kurbanov return HRTIMER_RESTART; 168aa172ba7SMartin Kurbanov } 169aa172ba7SMartin Kurbanov 1705fd752b6SBaolin Wang static int pattern_trig_start_pattern(struct led_classdev *led_cdev) 1715fd752b6SBaolin Wang { 1725fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 1735fd752b6SBaolin Wang 1745fd752b6SBaolin Wang if (!data->npatterns) 1755fd752b6SBaolin Wang return 0; 1765fd752b6SBaolin Wang 177aa172ba7SMartin Kurbanov if (data->type == PATTERN_TYPE_HW) { 1785fd752b6SBaolin Wang return led_cdev->pattern_set(led_cdev, data->patterns, 1795fd752b6SBaolin Wang data->npatterns, data->repeat); 1805fd752b6SBaolin Wang } 1815fd752b6SBaolin Wang 1825fd752b6SBaolin Wang /* At least 2 tuples for software pattern. */ 1835fd752b6SBaolin Wang if (data->npatterns < 2) 1845fd752b6SBaolin Wang return -EINVAL; 1855fd752b6SBaolin Wang 1865fd752b6SBaolin Wang data->delta_t = 0; 1875fd752b6SBaolin Wang data->curr = data->patterns; 1885fd752b6SBaolin Wang data->next = data->patterns + 1; 189aa172ba7SMartin Kurbanov pattern_trig_timer_start(data); 1905fd752b6SBaolin Wang 1915fd752b6SBaolin Wang return 0; 1925fd752b6SBaolin Wang } 1935fd752b6SBaolin Wang 1945fd752b6SBaolin Wang static ssize_t repeat_show(struct device *dev, struct device_attribute *attr, 1955fd752b6SBaolin Wang char *buf) 1965fd752b6SBaolin Wang { 1975fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 1985fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 1995fd752b6SBaolin Wang int repeat; 2005fd752b6SBaolin Wang 2015fd752b6SBaolin Wang mutex_lock(&data->lock); 2025fd752b6SBaolin Wang 2035fd752b6SBaolin Wang repeat = data->last_repeat; 2045fd752b6SBaolin Wang 2055fd752b6SBaolin Wang mutex_unlock(&data->lock); 2065fd752b6SBaolin Wang 2073f6fb1cfSye xingchen return sysfs_emit(buf, "%d\n", repeat); 2085fd752b6SBaolin Wang } 2095fd752b6SBaolin Wang 2105fd752b6SBaolin Wang static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, 2115fd752b6SBaolin Wang const char *buf, size_t count) 2125fd752b6SBaolin Wang { 2135fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 2145fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 2155fd752b6SBaolin Wang int err, res; 2165fd752b6SBaolin Wang 2175fd752b6SBaolin Wang err = kstrtos32(buf, 10, &res); 2185fd752b6SBaolin Wang if (err) 2195fd752b6SBaolin Wang return err; 2205fd752b6SBaolin Wang 2215fd752b6SBaolin Wang /* Number 0 and negative numbers except -1 are invalid. */ 2225fd752b6SBaolin Wang if (res < -1 || res == 0) 2235fd752b6SBaolin Wang return -EINVAL; 2245fd752b6SBaolin Wang 2255fd752b6SBaolin Wang mutex_lock(&data->lock); 2265fd752b6SBaolin Wang 227aa172ba7SMartin Kurbanov pattern_trig_timer_cancel(data); 2283a40cfe8SBaolin Wang 229aa172ba7SMartin Kurbanov if (data->type == PATTERN_TYPE_HW) 2305fd752b6SBaolin Wang led_cdev->pattern_clear(led_cdev); 2315fd752b6SBaolin Wang 2325fd752b6SBaolin Wang data->last_repeat = data->repeat = res; 2335fd752b6SBaolin Wang /* -1 means repeat indefinitely */ 2345fd752b6SBaolin Wang if (data->repeat == -1) 2355fd752b6SBaolin Wang data->is_indefinite = true; 2365fd752b6SBaolin Wang else 2375fd752b6SBaolin Wang data->is_indefinite = false; 2385fd752b6SBaolin Wang 2395fd752b6SBaolin Wang err = pattern_trig_start_pattern(led_cdev); 2405fd752b6SBaolin Wang 2415fd752b6SBaolin Wang mutex_unlock(&data->lock); 2425fd752b6SBaolin Wang return err < 0 ? err : count; 2435fd752b6SBaolin Wang } 2445fd752b6SBaolin Wang 2455fd752b6SBaolin Wang static DEVICE_ATTR_RW(repeat); 2465fd752b6SBaolin Wang 2475fd752b6SBaolin Wang static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, 248aa172ba7SMartin Kurbanov char *buf, enum pattern_type type) 2495fd752b6SBaolin Wang { 2505fd752b6SBaolin Wang ssize_t count = 0; 2515fd752b6SBaolin Wang int i; 2525fd752b6SBaolin Wang 2535fd752b6SBaolin Wang mutex_lock(&data->lock); 2545fd752b6SBaolin Wang 255aa172ba7SMartin Kurbanov if (!data->npatterns || data->type != type) 2565fd752b6SBaolin Wang goto out; 2575fd752b6SBaolin Wang 2585fd752b6SBaolin Wang for (i = 0; i < data->npatterns; i++) { 2595fd752b6SBaolin Wang count += scnprintf(buf + count, PAGE_SIZE - count, 2605fd752b6SBaolin Wang "%d %u ", 2615fd752b6SBaolin Wang data->patterns[i].brightness, 2625fd752b6SBaolin Wang data->patterns[i].delta_t); 2635fd752b6SBaolin Wang } 2645fd752b6SBaolin Wang 2655fd752b6SBaolin Wang buf[count - 1] = '\n'; 2665fd752b6SBaolin Wang 2675fd752b6SBaolin Wang out: 2685fd752b6SBaolin Wang mutex_unlock(&data->lock); 2695fd752b6SBaolin Wang return count; 2705fd752b6SBaolin Wang } 2715fd752b6SBaolin Wang 272aa6fd104SKrzysztof Kozlowski static int pattern_trig_store_patterns_string(struct pattern_trig_data *data, 273aa6fd104SKrzysztof Kozlowski const char *buf, size_t count) 274aa6fd104SKrzysztof Kozlowski { 275aa6fd104SKrzysztof Kozlowski int ccount, cr, offset = 0; 276aa6fd104SKrzysztof Kozlowski 277aa6fd104SKrzysztof Kozlowski while (offset < count - 1 && data->npatterns < MAX_PATTERNS) { 278aa6fd104SKrzysztof Kozlowski cr = 0; 279feff7273SPavel Machek ccount = sscanf(buf + offset, "%u %u %n", 280aa6fd104SKrzysztof Kozlowski &data->patterns[data->npatterns].brightness, 281aa6fd104SKrzysztof Kozlowski &data->patterns[data->npatterns].delta_t, &cr); 282feff7273SPavel Machek 283feff7273SPavel Machek if (ccount != 2 || 284feff7273SPavel Machek data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) { 285aa6fd104SKrzysztof Kozlowski data->npatterns = 0; 286aa6fd104SKrzysztof Kozlowski return -EINVAL; 287aa6fd104SKrzysztof Kozlowski } 288aa6fd104SKrzysztof Kozlowski 289aa6fd104SKrzysztof Kozlowski offset += cr; 290aa6fd104SKrzysztof Kozlowski data->npatterns++; 291aa6fd104SKrzysztof Kozlowski } 292aa6fd104SKrzysztof Kozlowski 293aa6fd104SKrzysztof Kozlowski return 0; 294aa6fd104SKrzysztof Kozlowski } 295aa6fd104SKrzysztof Kozlowski 296aa6fd104SKrzysztof Kozlowski static int pattern_trig_store_patterns_int(struct pattern_trig_data *data, 297aa6fd104SKrzysztof Kozlowski const u32 *buf, size_t count) 298aa6fd104SKrzysztof Kozlowski { 299aa6fd104SKrzysztof Kozlowski unsigned int i; 300aa6fd104SKrzysztof Kozlowski 301aa6fd104SKrzysztof Kozlowski for (i = 0; i < count; i += 2) { 302aa6fd104SKrzysztof Kozlowski data->patterns[data->npatterns].brightness = buf[i]; 303aa6fd104SKrzysztof Kozlowski data->patterns[data->npatterns].delta_t = buf[i + 1]; 304aa6fd104SKrzysztof Kozlowski data->npatterns++; 305aa6fd104SKrzysztof Kozlowski } 306aa6fd104SKrzysztof Kozlowski 307aa6fd104SKrzysztof Kozlowski return 0; 308aa6fd104SKrzysztof Kozlowski } 309aa6fd104SKrzysztof Kozlowski 3105fd752b6SBaolin Wang static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, 311aa6fd104SKrzysztof Kozlowski const char *buf, const u32 *buf_int, 312aa172ba7SMartin Kurbanov size_t count, enum pattern_type type) 3135fd752b6SBaolin Wang { 3145fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 315aa6fd104SKrzysztof Kozlowski int err = 0; 3165fd752b6SBaolin Wang 3175fd752b6SBaolin Wang mutex_lock(&data->lock); 3185fd752b6SBaolin Wang 319aa172ba7SMartin Kurbanov pattern_trig_timer_cancel(data); 3203a40cfe8SBaolin Wang 321aa172ba7SMartin Kurbanov if (data->type == PATTERN_TYPE_HW) 3225fd752b6SBaolin Wang led_cdev->pattern_clear(led_cdev); 3235fd752b6SBaolin Wang 324aa172ba7SMartin Kurbanov data->type = type; 3255fd752b6SBaolin Wang data->npatterns = 0; 3265fd752b6SBaolin Wang 327aa6fd104SKrzysztof Kozlowski if (buf) 328aa6fd104SKrzysztof Kozlowski err = pattern_trig_store_patterns_string(data, buf, count); 329aa6fd104SKrzysztof Kozlowski else 330aa6fd104SKrzysztof Kozlowski err = pattern_trig_store_patterns_int(data, buf_int, count); 331aa6fd104SKrzysztof Kozlowski if (err) 3325fd752b6SBaolin Wang goto out; 3335fd752b6SBaolin Wang 3345fd752b6SBaolin Wang err = pattern_trig_start_pattern(led_cdev); 3355fd752b6SBaolin Wang if (err) 3365fd752b6SBaolin Wang data->npatterns = 0; 3375fd752b6SBaolin Wang 3385fd752b6SBaolin Wang out: 3395fd752b6SBaolin Wang mutex_unlock(&data->lock); 3405fd752b6SBaolin Wang return err < 0 ? err : count; 3415fd752b6SBaolin Wang } 3425fd752b6SBaolin Wang 3435fd752b6SBaolin Wang static ssize_t pattern_show(struct device *dev, struct device_attribute *attr, 3445fd752b6SBaolin Wang char *buf) 3455fd752b6SBaolin Wang { 3465fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 3475fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 3485fd752b6SBaolin Wang 349aa172ba7SMartin Kurbanov return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW); 3505fd752b6SBaolin Wang } 3515fd752b6SBaolin Wang 3525fd752b6SBaolin Wang static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, 3535fd752b6SBaolin Wang const char *buf, size_t count) 3545fd752b6SBaolin Wang { 3555fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 3565fd752b6SBaolin Wang 357aa172ba7SMartin Kurbanov return pattern_trig_store_patterns(led_cdev, buf, NULL, count, 358aa172ba7SMartin Kurbanov PATTERN_TYPE_SW); 3595fd752b6SBaolin Wang } 3605fd752b6SBaolin Wang 3615fd752b6SBaolin Wang static DEVICE_ATTR_RW(pattern); 3625fd752b6SBaolin Wang 3635fd752b6SBaolin Wang static ssize_t hw_pattern_show(struct device *dev, 3645fd752b6SBaolin Wang struct device_attribute *attr, char *buf) 3655fd752b6SBaolin Wang { 3665fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 3675fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 3685fd752b6SBaolin Wang 369aa172ba7SMartin Kurbanov return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW); 3705fd752b6SBaolin Wang } 3715fd752b6SBaolin Wang 3725fd752b6SBaolin Wang static ssize_t hw_pattern_store(struct device *dev, 3735fd752b6SBaolin Wang struct device_attribute *attr, 3745fd752b6SBaolin Wang const char *buf, size_t count) 3755fd752b6SBaolin Wang { 3765fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 3775fd752b6SBaolin Wang 378aa172ba7SMartin Kurbanov return pattern_trig_store_patterns(led_cdev, buf, NULL, count, 379aa172ba7SMartin Kurbanov PATTERN_TYPE_HW); 3805fd752b6SBaolin Wang } 3815fd752b6SBaolin Wang 3825fd752b6SBaolin Wang static DEVICE_ATTR_RW(hw_pattern); 3835fd752b6SBaolin Wang 384aa172ba7SMartin Kurbanov static ssize_t hr_pattern_show(struct device *dev, 385aa172ba7SMartin Kurbanov struct device_attribute *attr, char *buf) 386aa172ba7SMartin Kurbanov { 387aa172ba7SMartin Kurbanov struct led_classdev *led_cdev = dev_get_drvdata(dev); 388aa172ba7SMartin Kurbanov struct pattern_trig_data *data = led_cdev->trigger_data; 389aa172ba7SMartin Kurbanov 390aa172ba7SMartin Kurbanov return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR); 391aa172ba7SMartin Kurbanov } 392aa172ba7SMartin Kurbanov 393aa172ba7SMartin Kurbanov static ssize_t hr_pattern_store(struct device *dev, 394aa172ba7SMartin Kurbanov struct device_attribute *attr, 395aa172ba7SMartin Kurbanov const char *buf, size_t count) 396aa172ba7SMartin Kurbanov { 397aa172ba7SMartin Kurbanov struct led_classdev *led_cdev = dev_get_drvdata(dev); 398aa172ba7SMartin Kurbanov 399aa172ba7SMartin Kurbanov return pattern_trig_store_patterns(led_cdev, buf, NULL, count, 400aa172ba7SMartin Kurbanov PATTERN_TYPE_HR); 401aa172ba7SMartin Kurbanov } 402aa172ba7SMartin Kurbanov 403aa172ba7SMartin Kurbanov static DEVICE_ATTR_RW(hr_pattern); 404aa172ba7SMartin Kurbanov 4055fd752b6SBaolin Wang static umode_t pattern_trig_attrs_mode(struct kobject *kobj, 4065fd752b6SBaolin Wang struct attribute *attr, int index) 4075fd752b6SBaolin Wang { 4085fe09e16STian Tao struct device *dev = kobj_to_dev(kobj); 4095fd752b6SBaolin Wang struct led_classdev *led_cdev = dev_get_drvdata(dev); 4105fd752b6SBaolin Wang 4115fd752b6SBaolin Wang if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr) 4125fd752b6SBaolin Wang return attr->mode; 413aa172ba7SMartin Kurbanov else if (attr == &dev_attr_hr_pattern.attr) 414aa172ba7SMartin Kurbanov return attr->mode; 4155fd752b6SBaolin Wang else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set) 4165fd752b6SBaolin Wang return attr->mode; 4175fd752b6SBaolin Wang 4185fd752b6SBaolin Wang return 0; 4195fd752b6SBaolin Wang } 4205fd752b6SBaolin Wang 4215fd752b6SBaolin Wang static struct attribute *pattern_trig_attrs[] = { 4225fd752b6SBaolin Wang &dev_attr_pattern.attr, 4235fd752b6SBaolin Wang &dev_attr_hw_pattern.attr, 424aa172ba7SMartin Kurbanov &dev_attr_hr_pattern.attr, 4255fd752b6SBaolin Wang &dev_attr_repeat.attr, 4265fd752b6SBaolin Wang NULL 4275fd752b6SBaolin Wang }; 4285fd752b6SBaolin Wang 4295fd752b6SBaolin Wang static const struct attribute_group pattern_trig_group = { 4305fd752b6SBaolin Wang .attrs = pattern_trig_attrs, 4315fd752b6SBaolin Wang .is_visible = pattern_trig_attrs_mode, 4325fd752b6SBaolin Wang }; 4335fd752b6SBaolin Wang 4345fd752b6SBaolin Wang static const struct attribute_group *pattern_trig_groups[] = { 4355fd752b6SBaolin Wang &pattern_trig_group, 4365fd752b6SBaolin Wang NULL, 4375fd752b6SBaolin Wang }; 4385fd752b6SBaolin Wang 439aa6fd104SKrzysztof Kozlowski static void pattern_init(struct led_classdev *led_cdev) 440aa6fd104SKrzysztof Kozlowski { 441aa6fd104SKrzysztof Kozlowski unsigned int size = 0; 442aa6fd104SKrzysztof Kozlowski u32 *pattern; 443aa6fd104SKrzysztof Kozlowski int err; 444aa6fd104SKrzysztof Kozlowski 445aa6fd104SKrzysztof Kozlowski pattern = led_get_default_pattern(led_cdev, &size); 446aa6fd104SKrzysztof Kozlowski if (!pattern) 447aa6fd104SKrzysztof Kozlowski return; 448aa6fd104SKrzysztof Kozlowski 449aa6fd104SKrzysztof Kozlowski if (size % 2) { 450aa6fd104SKrzysztof Kozlowski dev_warn(led_cdev->dev, "Expected pattern of tuples\n"); 451aa6fd104SKrzysztof Kozlowski goto out; 452aa6fd104SKrzysztof Kozlowski } 453aa6fd104SKrzysztof Kozlowski 454aa172ba7SMartin Kurbanov err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, 455aa172ba7SMartin Kurbanov PATTERN_TYPE_SW); 456aa6fd104SKrzysztof Kozlowski if (err < 0) 457aa6fd104SKrzysztof Kozlowski dev_warn(led_cdev->dev, 458aa6fd104SKrzysztof Kozlowski "Pattern initialization failed with error %d\n", err); 459aa6fd104SKrzysztof Kozlowski 460aa6fd104SKrzysztof Kozlowski out: 461aa6fd104SKrzysztof Kozlowski kfree(pattern); 462aa6fd104SKrzysztof Kozlowski } 463aa6fd104SKrzysztof Kozlowski 4645fd752b6SBaolin Wang static int pattern_trig_activate(struct led_classdev *led_cdev) 4655fd752b6SBaolin Wang { 4665fd752b6SBaolin Wang struct pattern_trig_data *data; 4675fd752b6SBaolin Wang 4685fd752b6SBaolin Wang data = kzalloc(sizeof(*data), GFP_KERNEL); 4695fd752b6SBaolin Wang if (!data) 4705fd752b6SBaolin Wang return -ENOMEM; 4715fd752b6SBaolin Wang 4725fd752b6SBaolin Wang if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) { 4735fd752b6SBaolin Wang dev_warn(led_cdev->dev, 4745fd752b6SBaolin Wang "Hardware pattern ops validation failed\n"); 4755fd752b6SBaolin Wang led_cdev->pattern_set = NULL; 4765fd752b6SBaolin Wang led_cdev->pattern_clear = NULL; 4775fd752b6SBaolin Wang } 4785fd752b6SBaolin Wang 479aa172ba7SMartin Kurbanov data->type = PATTERN_TYPE_SW; 4805fd752b6SBaolin Wang data->is_indefinite = true; 4815fd752b6SBaolin Wang data->last_repeat = -1; 4825fd752b6SBaolin Wang mutex_init(&data->lock); 4835fd752b6SBaolin Wang data->led_cdev = led_cdev; 4845fd752b6SBaolin Wang led_set_trigger_data(led_cdev, data); 4855fd752b6SBaolin Wang timer_setup(&data->timer, pattern_trig_timer_function, 0); 486*a9d0ac73SNam Cao hrtimer_setup(&data->hrtimer, pattern_trig_hrtimer_function, CLOCK_MONOTONIC, 487*a9d0ac73SNam Cao HRTIMER_MODE_REL); 4885fd752b6SBaolin Wang led_cdev->activated = true; 4895fd752b6SBaolin Wang 490aa6fd104SKrzysztof Kozlowski if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { 491aa6fd104SKrzysztof Kozlowski pattern_init(led_cdev); 492aa6fd104SKrzysztof Kozlowski /* 493aa6fd104SKrzysztof Kozlowski * Mark as initialized even on pattern_init() error because 494aa6fd104SKrzysztof Kozlowski * any consecutive call to it would produce the same error. 495aa6fd104SKrzysztof Kozlowski */ 496aa6fd104SKrzysztof Kozlowski led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; 497aa6fd104SKrzysztof Kozlowski } 498aa6fd104SKrzysztof Kozlowski 4995fd752b6SBaolin Wang return 0; 5005fd752b6SBaolin Wang } 5015fd752b6SBaolin Wang 5025fd752b6SBaolin Wang static void pattern_trig_deactivate(struct led_classdev *led_cdev) 5035fd752b6SBaolin Wang { 5045fd752b6SBaolin Wang struct pattern_trig_data *data = led_cdev->trigger_data; 5055fd752b6SBaolin Wang 5065fd752b6SBaolin Wang if (!led_cdev->activated) 5075fd752b6SBaolin Wang return; 5085fd752b6SBaolin Wang 5095fd752b6SBaolin Wang if (led_cdev->pattern_clear) 5105fd752b6SBaolin Wang led_cdev->pattern_clear(led_cdev); 5115fd752b6SBaolin Wang 512292a089dSSteven Rostedt (Google) timer_shutdown_sync(&data->timer); 513aa172ba7SMartin Kurbanov hrtimer_cancel(&data->hrtimer); 5145fd752b6SBaolin Wang 5155fd752b6SBaolin Wang led_set_brightness(led_cdev, LED_OFF); 5165fd752b6SBaolin Wang kfree(data); 5175fd752b6SBaolin Wang led_cdev->activated = false; 5185fd752b6SBaolin Wang } 5195fd752b6SBaolin Wang 5205fd752b6SBaolin Wang static struct led_trigger pattern_led_trigger = { 5215fd752b6SBaolin Wang .name = "pattern", 5225fd752b6SBaolin Wang .activate = pattern_trig_activate, 5235fd752b6SBaolin Wang .deactivate = pattern_trig_deactivate, 5245fd752b6SBaolin Wang .groups = pattern_trig_groups, 5255fd752b6SBaolin Wang }; 5265fd752b6SBaolin Wang 5275fd752b6SBaolin Wang static int __init pattern_trig_init(void) 5285fd752b6SBaolin Wang { 5295fd752b6SBaolin Wang return led_trigger_register(&pattern_led_trigger); 5305fd752b6SBaolin Wang } 5315fd752b6SBaolin Wang 5325fd752b6SBaolin Wang static void __exit pattern_trig_exit(void) 5335fd752b6SBaolin Wang { 5345fd752b6SBaolin Wang led_trigger_unregister(&pattern_led_trigger); 5355fd752b6SBaolin Wang } 5365fd752b6SBaolin Wang 5375fd752b6SBaolin Wang module_init(pattern_trig_init); 5385fd752b6SBaolin Wang module_exit(pattern_trig_exit); 5395fd752b6SBaolin Wang 54030d57d55SPavel Machek MODULE_AUTHOR("Raphael Teysseyre <[email protected]>"); 54130d57d55SPavel Machek MODULE_AUTHOR("Baolin Wang <[email protected]>"); 5425fd752b6SBaolin Wang MODULE_DESCRIPTION("LED Pattern trigger"); 5435fd752b6SBaolin Wang MODULE_LICENSE("GPL v2"); 544