[PATCH] leds: core: Support blocking HW blink operations

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux