Message ID | 57480FF3.9060107@gmx.at (mailing list archive) |
---|---|
State | Accepted |
Headers | show |
On Fri, May 27, 2016 at 11:14:27AM +0200, Manfred Schlaegl wrote: > Pwm config may sleep so defer it using a worker. > > On a Freescale i.MX53 based board we ran into "BUG: scheduling while > atomic" because input_inject_event locks interrupts, but > imx_pwm_config_v2 sleeps. > > Tested on Freescale i.MX53 SoC with 4.6.0. > > Signed-off-by: Manfred Schlaegl <manfred.schlaegl@gmx.at> Applied, thank you. > --- > drivers/input/misc/pwm-beeper.c | 69 ++++++++++++++++++++++++++++------------- > 1 file changed, 48 insertions(+), 21 deletions(-) > > diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c > index 8d71332..5f9655d 100644 > --- a/drivers/input/misc/pwm-beeper.c > +++ b/drivers/input/misc/pwm-beeper.c > @@ -20,21 +20,40 @@ > #include <linux/platform_device.h> > #include <linux/pwm.h> > #include <linux/slab.h> > +#include <linux/workqueue.h> > > struct pwm_beeper { > struct input_dev *input; > struct pwm_device *pwm; > + struct work_struct work; > unsigned long period; > }; > > #define HZ_TO_NANOSECONDS(x) (1000000000UL/(x)) > > +static void __pwm_beeper_set(struct pwm_beeper *beeper) > +{ > + unsigned long period = beeper->period; > + > + if (period) { > + pwm_config(beeper->pwm, period / 2, period); > + pwm_enable(beeper->pwm); > + } else > + pwm_disable(beeper->pwm); > +} > + > +static void pwm_beeper_work(struct work_struct *work) > +{ > + struct pwm_beeper *beeper = > + container_of(work, struct pwm_beeper, work); > + > + __pwm_beeper_set(beeper); > +} > + > static int pwm_beeper_event(struct input_dev *input, > unsigned int type, unsigned int code, int value) > { > - int ret = 0; > struct pwm_beeper *beeper = input_get_drvdata(input); > - unsigned long period; > > if (type != EV_SND || value < 0) > return -EINVAL; > @@ -49,22 +68,31 @@ static int pwm_beeper_event(struct input_dev *input, > return -EINVAL; > } > > - if (value == 0) { > - pwm_disable(beeper->pwm); > - } else { > - period = HZ_TO_NANOSECONDS(value); > - ret = pwm_config(beeper->pwm, period / 2, period); > - if (ret) > - return ret; > - ret = pwm_enable(beeper->pwm); > - if (ret) > - return ret; > - beeper->period = period; > - } > + if (value == 0) > + beeper->period = 0; > + else > + beeper->period = HZ_TO_NANOSECONDS(value); > + > + schedule_work(&beeper->work); > > return 0; > } > > +static void pwm_beeper_stop(struct pwm_beeper *beeper) > +{ > + cancel_work_sync(&beeper->work); > + > + if (beeper->period) > + pwm_disable(beeper->pwm); > +} > + > +static void pwm_beeper_close(struct input_dev *input) > +{ > + struct pwm_beeper *beeper = input_get_drvdata(input); > + > + pwm_beeper_stop(beeper); > +} > + > static int pwm_beeper_probe(struct platform_device *pdev) > { > unsigned long pwm_id = (unsigned long)dev_get_platdata(&pdev->dev); > @@ -93,6 +121,8 @@ static int pwm_beeper_probe(struct platform_device *pdev) > */ > pwm_apply_args(beeper->pwm); > > + INIT_WORK(&beeper->work, pwm_beeper_work); > + > beeper->input = input_allocate_device(); > if (!beeper->input) { > dev_err(&pdev->dev, "Failed to allocate input device\n"); > @@ -112,6 +142,7 @@ static int pwm_beeper_probe(struct platform_device *pdev) > beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL); > > beeper->input->event = pwm_beeper_event; > + beeper->input->close = pwm_beeper_close; > > input_set_drvdata(beeper->input, beeper); > > @@ -141,7 +172,6 @@ static int pwm_beeper_remove(struct platform_device *pdev) > > input_unregister_device(beeper->input); > > - pwm_disable(beeper->pwm); > pwm_free(beeper->pwm); > > kfree(beeper); > @@ -153,8 +183,7 @@ static int __maybe_unused pwm_beeper_suspend(struct device *dev) > { > struct pwm_beeper *beeper = dev_get_drvdata(dev); > > - if (beeper->period) > - pwm_disable(beeper->pwm); > + pwm_beeper_stop(beeper); > > return 0; > } > @@ -163,10 +192,8 @@ static int __maybe_unused pwm_beeper_resume(struct device *dev) > { > struct pwm_beeper *beeper = dev_get_drvdata(dev); > > - if (beeper->period) { > - pwm_config(beeper->pwm, beeper->period / 2, beeper->period); > - pwm_enable(beeper->pwm); > - } > + if (beeper->period) > + __pwm_beeper_set(beeper); > > return 0; > } > -- > 2.1.4 >
diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c index 8d71332..5f9655d 100644 --- a/drivers/input/misc/pwm-beeper.c +++ b/drivers/input/misc/pwm-beeper.c @@ -20,21 +20,40 @@ #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/slab.h> +#include <linux/workqueue.h> struct pwm_beeper { struct input_dev *input; struct pwm_device *pwm; + struct work_struct work; unsigned long period; }; #define HZ_TO_NANOSECONDS(x) (1000000000UL/(x)) +static void __pwm_beeper_set(struct pwm_beeper *beeper) +{ + unsigned long period = beeper->period; + + if (period) { + pwm_config(beeper->pwm, period / 2, period); + pwm_enable(beeper->pwm); + } else + pwm_disable(beeper->pwm); +} + +static void pwm_beeper_work(struct work_struct *work) +{ + struct pwm_beeper *beeper = + container_of(work, struct pwm_beeper, work); + + __pwm_beeper_set(beeper); +} + static int pwm_beeper_event(struct input_dev *input, unsigned int type, unsigned int code, int value) { - int ret = 0; struct pwm_beeper *beeper = input_get_drvdata(input); - unsigned long period; if (type != EV_SND || value < 0) return -EINVAL; @@ -49,22 +68,31 @@ static int pwm_beeper_event(struct input_dev *input, return -EINVAL; } - if (value == 0) { - pwm_disable(beeper->pwm); - } else { - period = HZ_TO_NANOSECONDS(value); - ret = pwm_config(beeper->pwm, period / 2, period); - if (ret) - return ret; - ret = pwm_enable(beeper->pwm); - if (ret) - return ret; - beeper->period = period; - } + if (value == 0) + beeper->period = 0; + else + beeper->period = HZ_TO_NANOSECONDS(value); + + schedule_work(&beeper->work); return 0; } +static void pwm_beeper_stop(struct pwm_beeper *beeper) +{ + cancel_work_sync(&beeper->work); + + if (beeper->period) + pwm_disable(beeper->pwm); +} + +static void pwm_beeper_close(struct input_dev *input) +{ + struct pwm_beeper *beeper = input_get_drvdata(input); + + pwm_beeper_stop(beeper); +} + static int pwm_beeper_probe(struct platform_device *pdev) { unsigned long pwm_id = (unsigned long)dev_get_platdata(&pdev->dev); @@ -93,6 +121,8 @@ static int pwm_beeper_probe(struct platform_device *pdev) */ pwm_apply_args(beeper->pwm); + INIT_WORK(&beeper->work, pwm_beeper_work); + beeper->input = input_allocate_device(); if (!beeper->input) { dev_err(&pdev->dev, "Failed to allocate input device\n"); @@ -112,6 +142,7 @@ static int pwm_beeper_probe(struct platform_device *pdev) beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL); beeper->input->event = pwm_beeper_event; + beeper->input->close = pwm_beeper_close; input_set_drvdata(beeper->input, beeper); @@ -141,7 +172,6 @@ static int pwm_beeper_remove(struct platform_device *pdev) input_unregister_device(beeper->input); - pwm_disable(beeper->pwm); pwm_free(beeper->pwm); kfree(beeper); @@ -153,8 +183,7 @@ static int __maybe_unused pwm_beeper_suspend(struct device *dev) { struct pwm_beeper *beeper = dev_get_drvdata(dev); - if (beeper->period) - pwm_disable(beeper->pwm); + pwm_beeper_stop(beeper); return 0; } @@ -163,10 +192,8 @@ static int __maybe_unused pwm_beeper_resume(struct device *dev) { struct pwm_beeper *beeper = dev_get_drvdata(dev); - if (beeper->period) { - pwm_config(beeper->pwm, beeper->period / 2, beeper->period); - pwm_enable(beeper->pwm); - } + if (beeper->period) + __pwm_beeper_set(beeper); return 0; }
Pwm config may sleep so defer it using a worker. On a Freescale i.MX53 based board we ran into "BUG: scheduling while atomic" because input_inject_event locks interrupts, but imx_pwm_config_v2 sleeps. Tested on Freescale i.MX53 SoC with 4.6.0. Signed-off-by: Manfred Schlaegl <manfred.schlaegl@gmx.at> --- drivers/input/misc/pwm-beeper.c | 69 ++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 21 deletions(-)