| From f49cf3b8b4c841457244c461c66186a719e13bcc Mon Sep 17 00:00:00 2001 |
| From: Manfred Schlaegl <manfred.schlaegl@gmx.at> |
| Date: Fri, 27 May 2016 16:36:36 -0700 |
| Subject: Input: pwm-beeper - fix - scheduling while atomic |
| |
| From: Manfred Schlaegl <manfred.schlaegl@gmx.at> |
| |
| commit f49cf3b8b4c841457244c461c66186a719e13bcc upstream. |
| |
| 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> |
| Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/input/misc/pwm-beeper.c | 69 +++++++++++++++++++++++++++------------- |
| 1 file changed, 48 insertions(+), 21 deletions(-) |
| |
| --- 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 |
| 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); |
| @@ -87,6 +115,8 @@ static int pwm_beeper_probe(struct platf |
| goto err_free; |
| } |
| |
| + 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"); |
| @@ -106,6 +136,7 @@ static int pwm_beeper_probe(struct platf |
| 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); |
| |
| @@ -135,7 +166,6 @@ static int pwm_beeper_remove(struct plat |
| |
| input_unregister_device(beeper->input); |
| |
| - pwm_disable(beeper->pwm); |
| pwm_free(beeper->pwm); |
| |
| kfree(beeper); |
| @@ -147,8 +177,7 @@ static int __maybe_unused pwm_beeper_sus |
| { |
| struct pwm_beeper *beeper = dev_get_drvdata(dev); |
| |
| - if (beeper->period) |
| - pwm_disable(beeper->pwm); |
| + pwm_beeper_stop(beeper); |
| |
| return 0; |
| } |
| @@ -157,10 +186,8 @@ static int __maybe_unused pwm_beeper_res |
| { |
| 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; |
| } |