Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: trigger: pattern: Add support for hrtimer

Currently, led pattern trigger uses timer_list to schedule brightness
changing. As we know from timer_list API [1], it's not accurate to
milliseconds and depends on HZ granularity.

Example:
"0 10 0 0 50 10 50 0 100 10 100 0 150 10 150 0 200 10 200 0 250 10 250 0",
we expect it to be 60ms long, but it can actually be up to ~120ms
(add ~10ms for each pattern when HZ == 100).

But sometimes, userspace needs time accurate led patterns to make sure
that pattern will be executed during expected time slot.
To achieve this goal the patch introduces optional hrtimer usage for
led trigger pattern, because hrtimer is microseconds accurate timer.

[1]: kernel/time/timer.c#L104

Signed-off-by: Martin Kurbanov <mmkurbanov@salutedevices.com>
Link: https://lore.kernel.org/r/20240416201847.357099-1-mmkurbanov@salutedevices.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Martin Kurbanov and committed by
Lee Jones
aa172ba7 fd05e369

+113 -23
+10
Documentation/ABI/testing/sysfs-class-led-trigger-pattern
··· 12 12 The exact format is described in: 13 13 Documentation/devicetree/bindings/leds/leds-trigger-pattern.txt 14 14 15 + What: /sys/class/leds/<led>/hr_pattern 16 + Date: April 2024 17 + Description: 18 + Specify a software pattern for the LED, that supports altering 19 + the brightness for the specified duration with one software 20 + timer. It can do gradual dimming and step change of brightness. 21 + 22 + Unlike the /sys/class/leds/<led>/pattern, this attribute runs 23 + a pattern on high-resolution timer (hrtimer). 24 + 15 25 What: /sys/class/leds/<led>/hw_pattern 16 26 Date: September 2018 17 27 KernelVersion: 4.20
+103 -23
drivers/leds/trigger/ledtrig-pattern.c
··· 13 13 #include <linux/mutex.h> 14 14 #include <linux/slab.h> 15 15 #include <linux/timer.h> 16 + #include <linux/hrtimer.h> 16 17 17 18 #define MAX_PATTERNS 1024 18 19 /* ··· 21 20 * every 50 milliseconds. 22 21 */ 23 22 #define UPDATE_INTERVAL 50 23 + 24 + enum pattern_type { 25 + PATTERN_TYPE_SW, /* Use standard timer for software pattern */ 26 + PATTERN_TYPE_HR, /* Use hrtimer for software pattern */ 27 + PATTERN_TYPE_HW, /* Hardware pattern */ 28 + }; 24 29 25 30 struct pattern_trig_data { 26 31 struct led_classdev *led_cdev; ··· 39 32 int last_repeat; 40 33 int delta_t; 41 34 bool is_indefinite; 42 - bool is_hw_pattern; 35 + enum pattern_type type; 43 36 struct timer_list timer; 37 + struct hrtimer hrtimer; 44 38 }; 45 39 46 40 static void pattern_trig_update_patterns(struct pattern_trig_data *data) ··· 79 71 return data->curr->brightness - step_brightness; 80 72 } 81 73 82 - static void pattern_trig_timer_function(struct timer_list *t) 74 + static void pattern_trig_timer_start(struct pattern_trig_data *data) 83 75 { 84 - struct pattern_trig_data *data = from_timer(data, t, timer); 76 + if (data->type == PATTERN_TYPE_HR) { 77 + hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL); 78 + } else { 79 + data->timer.expires = jiffies; 80 + add_timer(&data->timer); 81 + } 82 + } 85 83 84 + static void pattern_trig_timer_cancel(struct pattern_trig_data *data) 85 + { 86 + if (data->type == PATTERN_TYPE_HR) 87 + hrtimer_cancel(&data->hrtimer); 88 + else 89 + del_timer_sync(&data->timer); 90 + } 91 + 92 + static void pattern_trig_timer_restart(struct pattern_trig_data *data, 93 + unsigned long interval) 94 + { 95 + if (data->type == PATTERN_TYPE_HR) 96 + hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval)); 97 + else 98 + mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval)); 99 + } 100 + 101 + static void pattern_trig_timer_common_function(struct pattern_trig_data *data) 102 + { 86 103 for (;;) { 87 104 if (!data->is_indefinite && !data->repeat) 88 105 break; ··· 116 83 /* Step change of brightness */ 117 84 led_set_brightness(data->led_cdev, 118 85 data->curr->brightness); 119 - mod_timer(&data->timer, 120 - jiffies + msecs_to_jiffies(data->curr->delta_t)); 86 + pattern_trig_timer_restart(data, data->curr->delta_t); 121 87 if (!data->next->delta_t) { 122 88 /* Skip the tuple with zero duration */ 123 89 pattern_trig_update_patterns(data); ··· 138 106 139 107 led_set_brightness(data->led_cdev, 140 108 pattern_trig_compute_brightness(data)); 141 - mod_timer(&data->timer, 142 - jiffies + msecs_to_jiffies(UPDATE_INTERVAL)); 109 + pattern_trig_timer_restart(data, UPDATE_INTERVAL); 143 110 144 111 /* Accumulate the gradual dimming time */ 145 112 data->delta_t += UPDATE_INTERVAL; ··· 148 117 } 149 118 } 150 119 120 + static void pattern_trig_timer_function(struct timer_list *t) 121 + { 122 + struct pattern_trig_data *data = from_timer(data, t, timer); 123 + 124 + return pattern_trig_timer_common_function(data); 125 + } 126 + 127 + static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t) 128 + { 129 + struct pattern_trig_data *data = 130 + container_of(t, struct pattern_trig_data, hrtimer); 131 + 132 + pattern_trig_timer_common_function(data); 133 + if (!data->is_indefinite && !data->repeat) 134 + return HRTIMER_NORESTART; 135 + 136 + return HRTIMER_RESTART; 137 + } 138 + 151 139 static int pattern_trig_start_pattern(struct led_classdev *led_cdev) 152 140 { 153 141 struct pattern_trig_data *data = led_cdev->trigger_data; ··· 174 124 if (!data->npatterns) 175 125 return 0; 176 126 177 - if (data->is_hw_pattern) { 127 + if (data->type == PATTERN_TYPE_HW) { 178 128 return led_cdev->pattern_set(led_cdev, data->patterns, 179 129 data->npatterns, data->repeat); 180 130 } ··· 186 136 data->delta_t = 0; 187 137 data->curr = data->patterns; 188 138 data->next = data->patterns + 1; 189 - data->timer.expires = jiffies; 190 - add_timer(&data->timer); 139 + pattern_trig_timer_start(data); 191 140 192 141 return 0; 193 142 } ··· 224 175 225 176 mutex_lock(&data->lock); 226 177 227 - del_timer_sync(&data->timer); 178 + pattern_trig_timer_cancel(data); 228 179 229 - if (data->is_hw_pattern) 180 + if (data->type == PATTERN_TYPE_HW) 230 181 led_cdev->pattern_clear(led_cdev); 231 182 232 183 data->last_repeat = data->repeat = res; ··· 245 196 static DEVICE_ATTR_RW(repeat); 246 197 247 198 static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, 248 - char *buf, bool hw_pattern) 199 + char *buf, enum pattern_type type) 249 200 { 250 201 ssize_t count = 0; 251 202 int i; 252 203 253 204 mutex_lock(&data->lock); 254 205 255 - if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern)) 206 + if (!data->npatterns || data->type != type) 256 207 goto out; 257 208 258 209 for (i = 0; i < data->npatterns; i++) { ··· 309 260 310 261 static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, 311 262 const char *buf, const u32 *buf_int, 312 - size_t count, bool hw_pattern) 263 + size_t count, enum pattern_type type) 313 264 { 314 265 struct pattern_trig_data *data = led_cdev->trigger_data; 315 266 int err = 0; 316 267 317 268 mutex_lock(&data->lock); 318 269 319 - del_timer_sync(&data->timer); 270 + pattern_trig_timer_cancel(data); 320 271 321 - if (data->is_hw_pattern) 272 + if (data->type == PATTERN_TYPE_HW) 322 273 led_cdev->pattern_clear(led_cdev); 323 274 324 - data->is_hw_pattern = hw_pattern; 275 + data->type = type; 325 276 data->npatterns = 0; 326 277 327 278 if (buf) ··· 346 297 struct led_classdev *led_cdev = dev_get_drvdata(dev); 347 298 struct pattern_trig_data *data = led_cdev->trigger_data; 348 299 349 - return pattern_trig_show_patterns(data, buf, false); 300 + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW); 350 301 } 351 302 352 303 static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, ··· 354 305 { 355 306 struct led_classdev *led_cdev = dev_get_drvdata(dev); 356 307 357 - return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false); 308 + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, 309 + PATTERN_TYPE_SW); 358 310 } 359 311 360 312 static DEVICE_ATTR_RW(pattern); ··· 366 316 struct led_classdev *led_cdev = dev_get_drvdata(dev); 367 317 struct pattern_trig_data *data = led_cdev->trigger_data; 368 318 369 - return pattern_trig_show_patterns(data, buf, true); 319 + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW); 370 320 } 371 321 372 322 static ssize_t hw_pattern_store(struct device *dev, ··· 375 325 { 376 326 struct led_classdev *led_cdev = dev_get_drvdata(dev); 377 327 378 - return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true); 328 + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, 329 + PATTERN_TYPE_HW); 379 330 } 380 331 381 332 static DEVICE_ATTR_RW(hw_pattern); 333 + 334 + static ssize_t hr_pattern_show(struct device *dev, 335 + struct device_attribute *attr, char *buf) 336 + { 337 + struct led_classdev *led_cdev = dev_get_drvdata(dev); 338 + struct pattern_trig_data *data = led_cdev->trigger_data; 339 + 340 + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR); 341 + } 342 + 343 + static ssize_t hr_pattern_store(struct device *dev, 344 + struct device_attribute *attr, 345 + const char *buf, size_t count) 346 + { 347 + struct led_classdev *led_cdev = dev_get_drvdata(dev); 348 + 349 + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, 350 + PATTERN_TYPE_HR); 351 + } 352 + 353 + static DEVICE_ATTR_RW(hr_pattern); 382 354 383 355 static umode_t pattern_trig_attrs_mode(struct kobject *kobj, 384 356 struct attribute *attr, int index) ··· 409 337 struct led_classdev *led_cdev = dev_get_drvdata(dev); 410 338 411 339 if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr) 340 + return attr->mode; 341 + else if (attr == &dev_attr_hr_pattern.attr) 412 342 return attr->mode; 413 343 else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set) 414 344 return attr->mode; ··· 421 347 static struct attribute *pattern_trig_attrs[] = { 422 348 &dev_attr_pattern.attr, 423 349 &dev_attr_hw_pattern.attr, 350 + &dev_attr_hr_pattern.attr, 424 351 &dev_attr_repeat.attr, 425 352 NULL 426 353 }; ··· 451 376 goto out; 452 377 } 453 378 454 - err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false); 379 + err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, 380 + PATTERN_TYPE_SW); 455 381 if (err < 0) 456 382 dev_warn(led_cdev->dev, 457 383 "Pattern initialization failed with error %d\n", err); ··· 476 400 led_cdev->pattern_clear = NULL; 477 401 } 478 402 403 + data->type = PATTERN_TYPE_SW; 479 404 data->is_indefinite = true; 480 405 data->last_repeat = -1; 481 406 mutex_init(&data->lock); 482 407 data->led_cdev = led_cdev; 483 408 led_set_trigger_data(led_cdev, data); 484 409 timer_setup(&data->timer, pattern_trig_timer_function, 0); 410 + hrtimer_init(&data->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 411 + data->hrtimer.function = pattern_trig_hrtimer_function; 485 412 led_cdev->activated = true; 486 413 487 414 if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { ··· 510 431 led_cdev->pattern_clear(led_cdev); 511 432 512 433 timer_shutdown_sync(&data->timer); 434 + hrtimer_cancel(&data->hrtimer); 513 435 514 436 led_set_brightness(led_cdev, LED_OFF); 515 437 kfree(data);