I ran into this when working on a keyboard driver for the Razer family: the .blink_set() callback for setting hardware blinking on a LED only exist in a non-blocking (fastpath) variant, such as when blinking can be enabled by simply writing a memory-mapped register and protected by spinlocks. On USB keyboards with blinkable LEDs controlled with USB out-of-band commands this will of course not work: these calls need to come from process context. To support this: add a new .blink_set_blocking() callback in the same vein as .brightness_set_blocking() and add a flag and some code to the delayed work so that this will be able to fire the .blink_set_blocking() call. ALl of this will be handled transparently from the led_blink_set() call so all current users can keep using that. Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx> --- drivers/leds/led-core.c | 49 ++++++++++++++++++++++++++++++++++++++--- include/linux/leds.h | 9 ++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index ede4fa0ac2cc..94870a3d59af 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -108,6 +108,20 @@ static void set_brightness_delayed(struct work_struct *ws) container_of(ws, struct led_classdev, set_brightness_work); int ret = 0; + if (test_and_clear_bit(LED_BLINK_HW_SET, &led_cdev->work_flags)) { + if (led_cdev->blink_set) + ret = led_cdev->blink_set(led_cdev, + &led_cdev->blink_delay_on, + &led_cdev->blink_delay_off); + else if (led_cdev->blink_set_blocking) + ret = led_cdev->blink_set_blocking(led_cdev, + &led_cdev->blink_delay_on, + &led_cdev->blink_delay_off); + if (ret) + dev_err(led_cdev->dev, + "setting hardware blink failed (%d)\n", ret); + } + if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { led_cdev->delayed_set_value = LED_OFF; led_stop_software_blink(led_cdev); @@ -157,15 +171,44 @@ static void led_set_software_blink(struct led_classdev *led_cdev, mod_timer(&led_cdev->blink_timer, jiffies + 1); } +static int led_set_hardware_blink_nosleep(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + /* We have a non-blocking call, use it */ + if (led_cdev->blink_set) + return led_cdev->blink_set(led_cdev, delay_on, delay_off); + + /* Tell the worker to set up the blinking hardware */ + if (delay_on) + led_cdev->blink_delay_on = *delay_on; + else + led_cdev->blink_delay_on = 0; + if (delay_off) + led_cdev->blink_delay_off = *delay_off; + else + led_cdev->blink_delay_off = 0; + set_bit(LED_BLINK_HW_SET, &led_cdev->work_flags); + schedule_work(&led_cdev->set_brightness_work); + + return 0; +} static void led_blink_setup(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { + /* + * If not oneshot, and we have hardware support for blinking, + * relay the request to the driver. + */ if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) && - led_cdev->blink_set && - !led_cdev->blink_set(led_cdev, delay_on, delay_off)) - return; + (led_cdev->blink_set || led_cdev->blink_set_blocking)) { + /* If this fails we fall back to software blink */ + if (!led_set_hardware_blink_nosleep(led_cdev, + delay_on, delay_off)) + return; + } /* blink with 1 Hz as default if nothing specified */ if (!*delay_on && !*delay_off) diff --git a/include/linux/leds.h b/include/linux/leds.h index 7393a316d9fa..57f3df0774e7 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -61,6 +61,7 @@ struct led_classdev { #define LED_BLINK_INVERT 3 #define LED_BLINK_BRIGHTNESS_CHANGE 4 #define LED_BLINK_DISABLE 5 +#define LED_BLINK_HW_SET 6 /* Set LED brightness level * Must not sleep. Use brightness_set_blocking for drivers @@ -88,6 +89,14 @@ struct led_classdev { int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off); + /* + * Set LED blinking immediately but may sleep (block) the caller + * to access the LED device register. + */ + int (*blink_set_blocking)(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off); + int (*pattern_set)(struct led_classdev *led_cdev, struct led_pattern *pattern, u32 len, int repeat); -- 2.19.1