diff options
author | Manfred Schlaegl <manfred.schlaegl@gmx.at> | 2016-05-27 19:36:36 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2016-05-27 19:40:30 -0400 |
commit | f49cf3b8b4c841457244c461c66186a719e13bcc (patch) | |
tree | 11485c8b7383180ea9a68990dd58e79c5ffd88ab /drivers/input/misc/pwm-beeper.c | |
parent | 6f49a398b266d4895bd7e041db77a2b2ee1482a6 (diff) |
Input: pwm-beeper - fix - scheduling while atomic
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>
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers/input/misc/pwm-beeper.c')
-rw-r--r-- | drivers/input/misc/pwm-beeper.c | 69 |
1 files changed, 48 insertions, 21 deletions
diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c index f2261ab54701..18663d4edae5 100644 --- a/drivers/input/misc/pwm-beeper.c +++ b/drivers/input/misc/pwm-beeper.c | |||
@@ -20,21 +20,40 @@ | |||
20 | #include <linux/platform_device.h> | 20 | #include <linux/platform_device.h> |
21 | #include <linux/pwm.h> | 21 | #include <linux/pwm.h> |
22 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
23 | #include <linux/workqueue.h> | ||
23 | 24 | ||
24 | struct pwm_beeper { | 25 | struct pwm_beeper { |
25 | struct input_dev *input; | 26 | struct input_dev *input; |
26 | struct pwm_device *pwm; | 27 | struct pwm_device *pwm; |
28 | struct work_struct work; | ||
27 | unsigned long period; | 29 | unsigned long period; |
28 | }; | 30 | }; |
29 | 31 | ||
30 | #define HZ_TO_NANOSECONDS(x) (1000000000UL/(x)) | 32 | #define HZ_TO_NANOSECONDS(x) (1000000000UL/(x)) |
31 | 33 | ||
34 | static void __pwm_beeper_set(struct pwm_beeper *beeper) | ||
35 | { | ||
36 | unsigned long period = beeper->period; | ||
37 | |||
38 | if (period) { | ||
39 | pwm_config(beeper->pwm, period / 2, period); | ||
40 | pwm_enable(beeper->pwm); | ||
41 | } else | ||
42 | pwm_disable(beeper->pwm); | ||
43 | } | ||
44 | |||
45 | static void pwm_beeper_work(struct work_struct *work) | ||
46 | { | ||
47 | struct pwm_beeper *beeper = | ||
48 | container_of(work, struct pwm_beeper, work); | ||
49 | |||
50 | __pwm_beeper_set(beeper); | ||
51 | } | ||
52 | |||
32 | static int pwm_beeper_event(struct input_dev *input, | 53 | static int pwm_beeper_event(struct input_dev *input, |
33 | unsigned int type, unsigned int code, int value) | 54 | unsigned int type, unsigned int code, int value) |
34 | { | 55 | { |
35 | int ret = 0; | ||
36 | struct pwm_beeper *beeper = input_get_drvdata(input); | 56 | struct pwm_beeper *beeper = input_get_drvdata(input); |
37 | unsigned long period; | ||
38 | 57 | ||
39 | if (type != EV_SND || value < 0) | 58 | if (type != EV_SND || value < 0) |
40 | return -EINVAL; | 59 | return -EINVAL; |
@@ -49,22 +68,31 @@ static int pwm_beeper_event(struct input_dev *input, | |||
49 | return -EINVAL; | 68 | return -EINVAL; |
50 | } | 69 | } |
51 | 70 | ||
52 | if (value == 0) { | 71 | if (value == 0) |
53 | pwm_disable(beeper->pwm); | 72 | beeper->period = 0; |
54 | } else { | 73 | else |
55 | period = HZ_TO_NANOSECONDS(value); | 74 | beeper->period = HZ_TO_NANOSECONDS(value); |
56 | ret = pwm_config(beeper->pwm, period / 2, period); | 75 | |
57 | if (ret) | 76 | schedule_work(&beeper->work); |
58 | return ret; | ||
59 | ret = pwm_enable(beeper->pwm); | ||
60 | if (ret) | ||
61 | return ret; | ||
62 | beeper->period = period; | ||
63 | } | ||
64 | 77 | ||
65 | return 0; | 78 | return 0; |
66 | } | 79 | } |
67 | 80 | ||
81 | static void pwm_beeper_stop(struct pwm_beeper *beeper) | ||
82 | { | ||
83 | cancel_work_sync(&beeper->work); | ||
84 | |||
85 | if (beeper->period) | ||
86 | pwm_disable(beeper->pwm); | ||
87 | } | ||
88 | |||
89 | static void pwm_beeper_close(struct input_dev *input) | ||
90 | { | ||
91 | struct pwm_beeper *beeper = input_get_drvdata(input); | ||
92 | |||
93 | pwm_beeper_stop(beeper); | ||
94 | } | ||
95 | |||
68 | static int pwm_beeper_probe(struct platform_device *pdev) | 96 | static int pwm_beeper_probe(struct platform_device *pdev) |
69 | { | 97 | { |
70 | unsigned long pwm_id = (unsigned long)dev_get_platdata(&pdev->dev); | 98 | unsigned long pwm_id = (unsigned long)dev_get_platdata(&pdev->dev); |
@@ -87,6 +115,8 @@ static int pwm_beeper_probe(struct platform_device *pdev) | |||
87 | goto err_free; | 115 | goto err_free; |
88 | } | 116 | } |
89 | 117 | ||
118 | INIT_WORK(&beeper->work, pwm_beeper_work); | ||
119 | |||
90 | beeper->input = input_allocate_device(); | 120 | beeper->input = input_allocate_device(); |
91 | if (!beeper->input) { | 121 | if (!beeper->input) { |
92 | dev_err(&pdev->dev, "Failed to allocate input device\n"); | 122 | dev_err(&pdev->dev, "Failed to allocate input device\n"); |
@@ -106,6 +136,7 @@ static int pwm_beeper_probe(struct platform_device *pdev) | |||
106 | beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL); | 136 | beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL); |
107 | 137 | ||
108 | beeper->input->event = pwm_beeper_event; | 138 | beeper->input->event = pwm_beeper_event; |
139 | beeper->input->close = pwm_beeper_close; | ||
109 | 140 | ||
110 | input_set_drvdata(beeper->input, beeper); | 141 | input_set_drvdata(beeper->input, beeper); |
111 | 142 | ||
@@ -135,7 +166,6 @@ static int pwm_beeper_remove(struct platform_device *pdev) | |||
135 | 166 | ||
136 | input_unregister_device(beeper->input); | 167 | input_unregister_device(beeper->input); |
137 | 168 | ||
138 | pwm_disable(beeper->pwm); | ||
139 | pwm_free(beeper->pwm); | 169 | pwm_free(beeper->pwm); |
140 | 170 | ||
141 | kfree(beeper); | 171 | kfree(beeper); |
@@ -147,8 +177,7 @@ static int __maybe_unused pwm_beeper_suspend(struct device *dev) | |||
147 | { | 177 | { |
148 | struct pwm_beeper *beeper = dev_get_drvdata(dev); | 178 | struct pwm_beeper *beeper = dev_get_drvdata(dev); |
149 | 179 | ||
150 | if (beeper->period) | 180 | pwm_beeper_stop(beeper); |
151 | pwm_disable(beeper->pwm); | ||
152 | 181 | ||
153 | return 0; | 182 | return 0; |
154 | } | 183 | } |
@@ -157,10 +186,8 @@ static int __maybe_unused pwm_beeper_resume(struct device *dev) | |||
157 | { | 186 | { |
158 | struct pwm_beeper *beeper = dev_get_drvdata(dev); | 187 | struct pwm_beeper *beeper = dev_get_drvdata(dev); |
159 | 188 | ||
160 | if (beeper->period) { | 189 | if (beeper->period) |
161 | pwm_config(beeper->pwm, beeper->period / 2, beeper->period); | 190 | __pwm_beeper_set(beeper); |
162 | pwm_enable(beeper->pwm); | ||
163 | } | ||
164 | 191 | ||
165 | return 0; | 192 | return 0; |
166 | } | 193 | } |