diff options
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/leds-gpio.c | 27 |
1 files changed, 26 insertions, 1 deletions
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 431dcb61902c..47d90db280ce 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c | |||
@@ -13,14 +13,26 @@ | |||
13 | #include <linux/init.h> | 13 | #include <linux/init.h> |
14 | #include <linux/platform_device.h> | 14 | #include <linux/platform_device.h> |
15 | #include <linux/leds.h> | 15 | #include <linux/leds.h> |
16 | #include <linux/workqueue.h> | ||
17 | |||
16 | #include <asm/gpio.h> | 18 | #include <asm/gpio.h> |
17 | 19 | ||
18 | struct gpio_led_data { | 20 | struct gpio_led_data { |
19 | struct led_classdev cdev; | 21 | struct led_classdev cdev; |
20 | unsigned gpio; | 22 | unsigned gpio; |
23 | struct work_struct work; | ||
24 | u8 new_level; | ||
25 | u8 can_sleep; | ||
21 | u8 active_low; | 26 | u8 active_low; |
22 | }; | 27 | }; |
23 | 28 | ||
29 | static void gpio_led_work(struct work_struct *work) | ||
30 | { | ||
31 | struct gpio_led_data *led_dat = | ||
32 | container_of(work, struct gpio_led_data, work); | ||
33 | |||
34 | gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level); | ||
35 | } | ||
24 | 36 | ||
25 | static void gpio_led_set(struct led_classdev *led_cdev, | 37 | static void gpio_led_set(struct led_classdev *led_cdev, |
26 | enum led_brightness value) | 38 | enum led_brightness value) |
@@ -37,7 +49,15 @@ static void gpio_led_set(struct led_classdev *led_cdev, | |||
37 | if (led_dat->active_low) | 49 | if (led_dat->active_low) |
38 | level = !level; | 50 | level = !level; |
39 | 51 | ||
40 | gpio_set_value(led_dat->gpio, level); | 52 | /* setting GPIOs with I2C/etc requires a preemptible task context */ |
53 | if (led_dat->can_sleep) { | ||
54 | if (preempt_count()) { | ||
55 | led_dat->new_level = level; | ||
56 | schedule_work(&led_dat->work); | ||
57 | } else | ||
58 | gpio_set_value_cansleep(led_dat->gpio, level); | ||
59 | } else | ||
60 | gpio_set_value(led_dat->gpio, level); | ||
41 | } | 61 | } |
42 | 62 | ||
43 | static int __init gpio_led_probe(struct platform_device *pdev) | 63 | static int __init gpio_led_probe(struct platform_device *pdev) |
@@ -62,6 +82,7 @@ static int __init gpio_led_probe(struct platform_device *pdev) | |||
62 | led_dat->cdev.name = cur_led->name; | 82 | led_dat->cdev.name = cur_led->name; |
63 | led_dat->cdev.default_trigger = cur_led->default_trigger; | 83 | led_dat->cdev.default_trigger = cur_led->default_trigger; |
64 | led_dat->gpio = cur_led->gpio; | 84 | led_dat->gpio = cur_led->gpio; |
85 | led_dat->can_sleep = gpio_cansleep(cur_led->gpio); | ||
65 | led_dat->active_low = cur_led->active_low; | 86 | led_dat->active_low = cur_led->active_low; |
66 | led_dat->cdev.brightness_set = gpio_led_set; | 87 | led_dat->cdev.brightness_set = gpio_led_set; |
67 | led_dat->cdev.brightness = cur_led->active_low ? LED_FULL : LED_OFF; | 88 | led_dat->cdev.brightness = cur_led->active_low ? LED_FULL : LED_OFF; |
@@ -77,6 +98,8 @@ static int __init gpio_led_probe(struct platform_device *pdev) | |||
77 | gpio_free(led_dat->gpio); | 98 | gpio_free(led_dat->gpio); |
78 | goto err; | 99 | goto err; |
79 | } | 100 | } |
101 | |||
102 | INIT_WORK(&led_dat->work, gpio_led_work); | ||
80 | } | 103 | } |
81 | 104 | ||
82 | platform_set_drvdata(pdev, leds_data); | 105 | platform_set_drvdata(pdev, leds_data); |
@@ -90,6 +113,8 @@ err: | |||
90 | gpio_free(leds_data[i].gpio); | 113 | gpio_free(leds_data[i].gpio); |
91 | } | 114 | } |
92 | } | 115 | } |
116 | |||
117 | flush_scheduled_work(); | ||
93 | kfree(leds_data); | 118 | kfree(leds_data); |
94 | 119 | ||
95 | return ret; | 120 | return ret; |