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

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>

authored by

Florian Vaussard and committed by
Bryan Wu
c971ff18 24d32128

+42 -8
+42 -8
drivers/leds/leds-pwm.c
··· 23 23 #include <linux/pwm.h> 24 24 #include <linux/leds_pwm.h> 25 25 #include <linux/slab.h> 26 + #include <linux/workqueue.h> 26 27 27 28 struct led_pwm_data { 28 29 struct led_classdev cdev; 29 30 struct pwm_device *pwm; 31 + struct work_struct work; 30 32 unsigned int active_low; 31 33 unsigned int period; 34 + int duty; 35 + bool can_sleep; 32 36 }; 33 37 34 38 struct led_pwm_priv { 35 39 int num_leds; 36 40 struct led_pwm_data leds[0]; 37 41 }; 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 + } 38 62 39 63 static void led_pwm_set(struct led_classdev *led_cdev, 40 64 enum led_brightness brightness) ··· 68 44 unsigned int max = led_dat->cdev.max_brightness; 69 45 unsigned int period = led_dat->period; 70 46 71 - if (brightness == 0) { 72 - pwm_config(led_dat->pwm, 0, period); 73 - pwm_disable(led_dat->pwm); 74 - } else { 75 - pwm_config(led_dat->pwm, brightness * period / max, period); 76 - pwm_enable(led_dat->pwm); 77 - } 47 + led_dat->duty = brightness * period / max; 48 + 49 + if (led_dat->can_sleep) 50 + schedule_work(&led_dat->work); 51 + else 52 + __led_pwm_set(led_dat); 78 53 } 79 54 80 55 static inline size_t sizeof_pwm_leds_priv(int num_leds) ··· 122 99 led_dat->cdev.brightness_set = led_pwm_set; 123 100 led_dat->cdev.brightness = LED_OFF; 124 101 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 102 + 103 + led_dat->can_sleep = pwm_can_sleep(led_dat->pwm); 104 + if (led_dat->can_sleep) 105 + INIT_WORK(&led_dat->work, led_pwm_work); 125 106 126 107 ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 127 108 if (ret < 0) { ··· 180 153 led_dat->cdev.max_brightness = cur_led->max_brightness; 181 154 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 182 155 156 + led_dat->can_sleep = pwm_can_sleep(led_dat->pwm); 157 + if (led_dat->can_sleep) 158 + INIT_WORK(&led_dat->work, led_pwm_work); 159 + 183 160 ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 184 161 if (ret < 0) 185 162 goto err; ··· 211 180 struct led_pwm_priv *priv = platform_get_drvdata(pdev); 212 181 int i; 213 182 214 - for (i = 0; i < priv->num_leds; i++) 183 + for (i = 0; i < priv->num_leds; i++) { 215 184 led_classdev_unregister(&priv->leds[i].cdev); 185 + if (priv->leds[i].can_sleep) 186 + cancel_work_sync(&priv->leds[i].work); 187 + } 216 188 217 189 return 0; 218 190 }