diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c index fadd87dbe993..92e69e7149ba 100644 --- a/drivers/leds/trigger/ledtrig-pattern.c +++ b/drivers/leds/trigger/ledtrig-pattern.c @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include #define MAX_PATTERNS 1024 /* @@ -34,8 +37,56 @@ struct pattern_trig_data { bool is_indefinite; bool is_hw_pattern; struct timer_list timer; +#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR) + struct led_classdev_mc *mcled; +#endif }; +#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR) +static void pattern_trig_apply_color(struct pattern_trig_data *data, + const struct led_pattern *pat) +{ + int i; + unsigned int max_intensity; + bool has_red = false, has_green = false, has_blue = false; + + if (!data->mcled || !pat->has_color) + return; + + max_intensity = data->mcled->led_cdev.max_brightness; + + for (i = 0; i < data->mcled->num_colors; i++) { + switch (data->mcled->subled_info[i].color_index) { + case LED_COLOR_ID_RED: + data->mcled->subled_info[i].intensity = + DIV_ROUND_CLOSEST(pat->red * max_intensity, 255); + has_red = true; + break; + case LED_COLOR_ID_GREEN: + data->mcled->subled_info[i].intensity = + DIV_ROUND_CLOSEST(pat->green * max_intensity, 255); + has_green = true; + break; + case LED_COLOR_ID_BLUE: + data->mcled->subled_info[i].intensity = + DIV_ROUND_CLOSEST(pat->blue * max_intensity, 255); + has_blue = true; + break; + default: + break; + } + } + + if ((!has_red && pat->red) || (!has_green && pat->green) || + (!has_blue && pat->blue)) + dev_warn_once(data->led_cdev->dev, + "pattern: color component set for absent channel\n"); +} +#else +static inline void pattern_trig_apply_color(struct pattern_trig_data *data, + const struct led_pattern *pat) { } +#endif + static void pattern_trig_update_patterns(struct pattern_trig_data *data) { data->curr = data->next; @@ -81,6 +132,7 @@ static void pattern_trig_timer_function(struct timer_list *t) if (data->curr->brightness == data->next->brightness) { /* Step change of brightness */ + pattern_trig_apply_color(data, data->curr); led_set_brightness(data->led_cdev, data->curr->brightness); mod_timer(&data->timer, @@ -104,6 +156,8 @@ static void pattern_trig_timer_function(struct timer_list *t) continue; } + if (data->delta_t == 0) + pattern_trig_apply_color(data, data->curr); led_set_brightness(data->led_cdev, pattern_trig_compute_brightness(data)); mod_timer(&data->timer, @@ -207,10 +261,18 @@ static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, goto out; for (i = 0; i < data->npatterns; i++) { - count += scnprintf(buf + count, PAGE_SIZE - count, - "%d %u ", - data->patterns[i].brightness, - data->patterns[i].delta_t); + const struct led_pattern *p = &data->patterns[i]; + + if (!hw_pattern && p->has_color) + count += scnprintf(buf + count, PAGE_SIZE - count, + "%d:%02x%02x%02x %u ", + p->brightness, p->red, p->green, + p->blue, p->delta_t); + else + count += scnprintf(buf + count, PAGE_SIZE - count, + "%d %u ", + p->brightness, + p->delta_t); } buf[count - 1] = '\n'; @@ -223,22 +285,52 @@ out: static int pattern_trig_store_patterns_string(struct pattern_trig_data *data, const char *buf, size_t count) { - int ccount, cr, offset = 0; + int consumed, ccount; + unsigned int brightness, delta_t; + size_t offset = 0; + u8 r, g, b; - while (offset < count - 1 && data->npatterns < MAX_PATTERNS) { - cr = 0; - ccount = sscanf(buf + offset, "%u %u %n", - &data->patterns[data->npatterns].brightness, - &data->patterns[data->npatterns].delta_t, &cr); + while (offset < count && data->npatterns < MAX_PATTERNS) { + while (offset < count && isspace(buf[offset])) + offset++; + if (offset >= count) + break; - if (ccount != 2 || - data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) { + if (!data->is_hw_pattern) { + consumed = 0; + ccount = sscanf(buf + offset, + "%u:%2hhx%2hhx%2hhx %u %n", + &brightness, &r, &g, &b, + &delta_t, &consumed); + if (ccount == 5) { + if (brightness > data->led_cdev->max_brightness) { + data->npatterns = 0; + return -EINVAL; + } + data->patterns[data->npatterns].brightness = brightness; + data->patterns[data->npatterns].delta_t = delta_t; + data->patterns[data->npatterns].has_color = true; + data->patterns[data->npatterns].red = r; + data->patterns[data->npatterns].green = g; + data->patterns[data->npatterns].blue = b; + offset += consumed; + data->npatterns++; + continue; + } + } + + consumed = 0; + ccount = sscanf(buf + offset, "%u %u %n", &brightness, &delta_t, &consumed); + if (ccount != 2 || brightness > data->led_cdev->max_brightness) { data->npatterns = 0; return -EINVAL; } - - offset += cr; + data->patterns[data->npatterns].brightness = brightness; + data->patterns[data->npatterns].delta_t = delta_t; data->npatterns++; + if (consumed == 0) + break; + offset += consumed; } return 0; @@ -274,6 +366,8 @@ static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, data->is_hw_pattern = hw_pattern; data->npatterns = 0; + /* Zero all pattern entries so per-tuple color field clearing is unnecessary */ + memset(data->patterns, 0, sizeof(data->patterns)); if (buf) err = pattern_trig_store_patterns_string(data, buf, count); @@ -404,6 +498,11 @@ static int pattern_trig_activate(struct led_classdev *led_cdev) data->last_repeat = -1; mutex_init(&data->lock); data->led_cdev = led_cdev; +#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR) + if (led_cdev->color == LED_COLOR_ID_MULTI || + led_cdev->color == LED_COLOR_ID_RGB) + data->mcled = lcdev_to_mccdev(led_cdev); +#endif led_set_trigger_data(led_cdev, data); timer_setup(&data->timer, pattern_trig_timer_function, 0); led_cdev->activated = true; @@ -459,5 +558,5 @@ module_exit(pattern_trig_exit); MODULE_AUTHOR("Raphael Teysseyre "); MODULE_AUTHOR("Baolin Wang "); -MODULE_DESCRIPTION("LED Pattern trigger"); +MODULE_DESCRIPTION("LED Pattern trigger (optional per-node RGB)"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/leds.h b/include/linux/leds.h index e91802cdc416..80febc2868db 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -706,13 +706,24 @@ static inline void led_classdev_notify_brightness_hw_changed( #endif /** - * struct led_pattern - pattern interval settings - * @delta_t: pattern interval delay, in milliseconds - * @brightness: pattern interval brightness + * struct led_pattern - pattern interval settings (optional RGB per node) + * @delta_t: pattern interval delay (ms) + * @brightness: pattern interval brightness (0..max_brightness) + * @has_color: true if this node specifies new RGB intensities + * @red: red component (0..255, valid if has_color) + * @green: green component (0..255, valid if has_color) + * @blue: blue component (0..255, valid if has_color) + * + * If has_color is false the previously applied color (if any) remains + * unchanged; only brightness/duration are processed. */ struct led_pattern { u32 delta_t; int brightness; + bool has_color; + u8 red; + u8 green; + u8 blue; }; enum led_audio {