diff options
author | Florian Vaussard <florian.vaussard@epfl.ch> | 2013-01-28 09:00:59 -0500 |
---|---|---|
committer | Bryan Wu <cooloney@gmail.com> | 2013-04-01 14:04:50 -0400 |
commit | c971ff185f6443e834686f140ba6d6e341ced600 (patch) | |
tree | fef36e139acb29b2797c94861ee763817440b8dd | |
parent | 24d321284745cbc593fba8115585329d48703704 (diff) |
leds: leds-pwm: Defer led_pwm_set() if PWM can sleep
Call to led_pwm_set() can happen inside atomic context, like triggers.
If the PWM call can sleep, defer using a worker.
Signed-off-by: Florian Vaussard <florian.vaussard@epfl.ch>
Reviewed-by: Peter Ujfalusi <peter.ujfalusi@ti.com>
Acked-by: Thierry Reding <thierry.reding@avionic-design.de>
Signed-off-by: Bryan Wu <cooloney@gmail.com>
-rw-r--r-- | drivers/leds/leds-pwm.c | 50 |
1 files changed, 42 insertions, 8 deletions
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index a1ea5f6a8d39..faf52c005e8c 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c | |||
@@ -23,12 +23,16 @@ | |||
23 | #include <linux/pwm.h> | 23 | #include <linux/pwm.h> |
24 | #include <linux/leds_pwm.h> | 24 | #include <linux/leds_pwm.h> |
25 | #include <linux/slab.h> | 25 | #include <linux/slab.h> |
26 | #include <linux/workqueue.h> | ||
26 | 27 | ||
27 | struct led_pwm_data { | 28 | struct led_pwm_data { |
28 | struct led_classdev cdev; | 29 | struct led_classdev cdev; |
29 | struct pwm_device *pwm; | 30 | struct pwm_device *pwm; |
31 | struct work_struct work; | ||
30 | unsigned int active_low; | 32 | unsigned int active_low; |
31 | unsigned int period; | 33 | unsigned int period; |
34 | int duty; | ||
35 | bool can_sleep; | ||
32 | }; | 36 | }; |
33 | 37 | ||
34 | struct led_pwm_priv { | 38 | struct led_pwm_priv { |
@@ -36,6 +40,26 @@ struct led_pwm_priv { | |||
36 | struct led_pwm_data leds[0]; | 40 | struct led_pwm_data leds[0]; |
37 | }; | 41 | }; |
38 | 42 | ||
43 | static void __led_pwm_set(struct led_pwm_data *led_dat) | ||
44 | { | ||
45 | int new_duty = led_dat->duty; | ||
46 | |||
47 | pwm_config(led_dat->pwm, new_duty, led_dat->period); | ||
48 | |||
49 | if (new_duty == 0) | ||
50 | pwm_disable(led_dat->pwm); | ||
51 | else | ||
52 | pwm_enable(led_dat->pwm); | ||
53 | } | ||
54 | |||
55 | static void led_pwm_work(struct work_struct *work) | ||
56 | { | ||
57 | struct led_pwm_data *led_dat = | ||
58 | container_of(work, struct led_pwm_data, work); | ||
59 | |||
60 | __led_pwm_set(led_dat); | ||
61 | } | ||
62 | |||
39 | static void led_pwm_set(struct led_classdev *led_cdev, | 63 | static void led_pwm_set(struct led_classdev *led_cdev, |
40 | enum led_brightness brightness) | 64 | enum led_brightness brightness) |
41 | { | 65 | { |
@@ -44,13 +68,12 @@ static void led_pwm_set(struct led_classdev *led_cdev, | |||
44 | unsigned int max = led_dat->cdev.max_brightness; | 68 | unsigned int max = led_dat->cdev.max_brightness; |
45 | unsigned int period = led_dat->period; | 69 | unsigned int period = led_dat->period; |
46 | 70 | ||
47 | if (brightness == 0) { | 71 | led_dat->duty = brightness * period / max; |
48 | pwm_config(led_dat->pwm, 0, period); | 72 | |
49 | pwm_disable(led_dat->pwm); | 73 | if (led_dat->can_sleep) |
50 | } else { | 74 | schedule_work(&led_dat->work); |
51 | pwm_config(led_dat->pwm, brightness * period / max, period); | 75 | else |
52 | pwm_enable(led_dat->pwm); | 76 | __led_pwm_set(led_dat); |
53 | } | ||
54 | } | 77 | } |
55 | 78 | ||
56 | static inline size_t sizeof_pwm_leds_priv(int num_leds) | 79 | static inline size_t sizeof_pwm_leds_priv(int num_leds) |
@@ -100,6 +123,10 @@ static struct led_pwm_priv *led_pwm_create_of(struct platform_device *pdev) | |||
100 | led_dat->cdev.brightness = LED_OFF; | 123 | led_dat->cdev.brightness = LED_OFF; |
101 | led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; | 124 | led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; |
102 | 125 | ||
126 | led_dat->can_sleep = pwm_can_sleep(led_dat->pwm); | ||
127 | if (led_dat->can_sleep) | ||
128 | INIT_WORK(&led_dat->work, led_pwm_work); | ||
129 | |||
103 | ret = led_classdev_register(&pdev->dev, &led_dat->cdev); | 130 | ret = led_classdev_register(&pdev->dev, &led_dat->cdev); |
104 | if (ret < 0) { | 131 | if (ret < 0) { |
105 | dev_err(&pdev->dev, "failed to register for %s\n", | 132 | dev_err(&pdev->dev, "failed to register for %s\n", |
@@ -153,6 +180,10 @@ static int led_pwm_probe(struct platform_device *pdev) | |||
153 | led_dat->cdev.max_brightness = cur_led->max_brightness; | 180 | led_dat->cdev.max_brightness = cur_led->max_brightness; |
154 | led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; | 181 | led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; |
155 | 182 | ||
183 | led_dat->can_sleep = pwm_can_sleep(led_dat->pwm); | ||
184 | if (led_dat->can_sleep) | ||
185 | INIT_WORK(&led_dat->work, led_pwm_work); | ||
186 | |||
156 | ret = led_classdev_register(&pdev->dev, &led_dat->cdev); | 187 | ret = led_classdev_register(&pdev->dev, &led_dat->cdev); |
157 | if (ret < 0) | 188 | if (ret < 0) |
158 | goto err; | 189 | goto err; |
@@ -180,8 +211,11 @@ static int led_pwm_remove(struct platform_device *pdev) | |||
180 | struct led_pwm_priv *priv = platform_get_drvdata(pdev); | 211 | struct led_pwm_priv *priv = platform_get_drvdata(pdev); |
181 | int i; | 212 | int i; |
182 | 213 | ||
183 | for (i = 0; i < priv->num_leds; i++) | 214 | for (i = 0; i < priv->num_leds; i++) { |
184 | led_classdev_unregister(&priv->leds[i].cdev); | 215 | led_classdev_unregister(&priv->leds[i].cdev); |
216 | if (priv->leds[i].can_sleep) | ||
217 | cancel_work_sync(&priv->leds[i].work); | ||
218 | } | ||
185 | 219 | ||
186 | return 0; | 220 | return 0; |
187 | } | 221 | } |