aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2011-04-28 02:08:34 -0400
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2011-04-28 02:12:12 -0400
commitf5346668150c37094b42cc2d07ec5fd1451eb980 (patch)
tree23fb800e00acd2142ed8e4da2df9361de3397084
parentc36b58e8a9112017c2bcc322cc98e71241814303 (diff)
Input: wm831x-ts - fix races with IRQ management
If the WM831x pen down and data IRQs run in parallel it is possible for the data and pen down IRQs to deadlock themselves as one is part way through disabling its operation while the other is part way through enabling. Fix this by always disabling the pen down interrupt while data is active and vice versa. When a changeover is required we disable the IRQ that is to be stopped then schedule work that will enable the new IRQ. We need to handle the data flow in the data IRQ as the readback from the device needs to be ordered correctly with the IRQ for robust operation. This also fixes an issue when using the built in IRQs due to enable_irq() not being valid from interrupt context on an interrupt controller with bus operations like the built in IRQ controller - this issue may also have affected other interrupt controllers. We can't rely on having the data and pen down IRQs available via GPIOs on the CPU on every system. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
-rw-r--r--drivers/input/touchscreen/wm831x-ts.c54
1 files changed, 49 insertions, 5 deletions
diff --git a/drivers/input/touchscreen/wm831x-ts.c b/drivers/input/touchscreen/wm831x-ts.c
index 6ae054f8e0aa..b9373012b3e6 100644
--- a/drivers/input/touchscreen/wm831x-ts.c
+++ b/drivers/input/touchscreen/wm831x-ts.c
@@ -68,8 +68,23 @@ struct wm831x_ts {
68 unsigned int pd_irq; 68 unsigned int pd_irq;
69 bool pressure; 69 bool pressure;
70 bool pen_down; 70 bool pen_down;
71 struct work_struct pd_data_work;
71}; 72};
72 73
74static void wm831x_pd_data_work(struct work_struct *work)
75{
76 struct wm831x_ts *wm831x_ts =
77 container_of(work, struct wm831x_ts, pd_data_work);
78
79 if (wm831x_ts->pen_down) {
80 enable_irq(wm831x_ts->data_irq);
81 dev_dbg(wm831x_ts->wm831x->dev, "IRQ PD->DATA done\n");
82 } else {
83 enable_irq(wm831x_ts->pd_irq);
84 dev_dbg(wm831x_ts->wm831x->dev, "IRQ DATA->PD done\n");
85 }
86}
87
73static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data) 88static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
74{ 89{
75 struct wm831x_ts *wm831x_ts = irq_data; 90 struct wm831x_ts *wm831x_ts = irq_data;
@@ -110,6 +125,9 @@ static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
110 } 125 }
111 126
112 if (!wm831x_ts->pen_down) { 127 if (!wm831x_ts->pen_down) {
128 /* Switch from data to pen down */
129 dev_dbg(wm831x->dev, "IRQ DATA->PD\n");
130
113 disable_irq_nosync(wm831x_ts->data_irq); 131 disable_irq_nosync(wm831x_ts->data_irq);
114 132
115 /* Don't need data any more */ 133 /* Don't need data any more */
@@ -128,6 +146,8 @@ static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
128 ABS_PRESSURE, 0); 146 ABS_PRESSURE, 0);
129 147
130 input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 0); 148 input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 0);
149
150 schedule_work(&wm831x_ts->pd_data_work);
131 } 151 }
132 152
133 input_sync(wm831x_ts->input_dev); 153 input_sync(wm831x_ts->input_dev);
@@ -141,6 +161,11 @@ static irqreturn_t wm831x_ts_pen_down_irq(int irq, void *irq_data)
141 struct wm831x *wm831x = wm831x_ts->wm831x; 161 struct wm831x *wm831x = wm831x_ts->wm831x;
142 int ena = 0; 162 int ena = 0;
143 163
164 if (wm831x_ts->pen_down)
165 return IRQ_HANDLED;
166
167 disable_irq_nosync(wm831x_ts->pd_irq);
168
144 /* Start collecting data */ 169 /* Start collecting data */
145 if (wm831x_ts->pressure) 170 if (wm831x_ts->pressure)
146 ena |= WM831X_TCH_Z_ENA; 171 ena |= WM831X_TCH_Z_ENA;
@@ -156,7 +181,10 @@ static irqreturn_t wm831x_ts_pen_down_irq(int irq, void *irq_data)
156 WM831X_TCHPD_EINT, WM831X_TCHPD_EINT); 181 WM831X_TCHPD_EINT, WM831X_TCHPD_EINT);
157 182
158 wm831x_ts->pen_down = true; 183 wm831x_ts->pen_down = true;
159 enable_irq(wm831x_ts->data_irq); 184
185 /* Switch from pen down to data */
186 dev_dbg(wm831x->dev, "IRQ PD->DATA\n");
187 schedule_work(&wm831x_ts->pd_data_work);
160 188
161 return IRQ_HANDLED; 189 return IRQ_HANDLED;
162} 190}
@@ -182,13 +210,28 @@ static void wm831x_ts_input_close(struct input_dev *idev)
182 struct wm831x_ts *wm831x_ts = input_get_drvdata(idev); 210 struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
183 struct wm831x *wm831x = wm831x_ts->wm831x; 211 struct wm831x *wm831x = wm831x_ts->wm831x;
184 212
213 /* Shut the controller down, disabling all other functionality too */
185 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1, 214 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
186 WM831X_TCH_ENA | WM831X_TCH_CVT_ENA | 215 WM831X_TCH_ENA | WM831X_TCH_X_ENA |
187 WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | 216 WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA, 0);
188 WM831X_TCH_Z_ENA, 0);
189 217
190 if (wm831x_ts->pen_down) 218 /* Make sure any pending IRQs are done, the above will prevent
219 * new ones firing.
220 */
221 synchronize_irq(wm831x_ts->data_irq);
222 synchronize_irq(wm831x_ts->pd_irq);
223
224 /* Make sure the IRQ completion work is quiesced */
225 flush_work_sync(&wm831x_ts->pd_data_work);
226
227 /* If we ended up with the pen down then make sure we revert back
228 * to pen detection state for the next time we start up.
229 */
230 if (wm831x_ts->pen_down) {
191 disable_irq(wm831x_ts->data_irq); 231 disable_irq(wm831x_ts->data_irq);
232 enable_irq(wm831x_ts->pd_irq);
233 wm831x_ts->pen_down = false;
234 }
192} 235}
193 236
194static __devinit int wm831x_ts_probe(struct platform_device *pdev) 237static __devinit int wm831x_ts_probe(struct platform_device *pdev)
@@ -212,6 +255,7 @@ static __devinit int wm831x_ts_probe(struct platform_device *pdev)
212 255
213 wm831x_ts->wm831x = wm831x; 256 wm831x_ts->wm831x = wm831x;
214 wm831x_ts->input_dev = input_dev; 257 wm831x_ts->input_dev = input_dev;
258 INIT_WORK(&wm831x_ts->pd_data_work, wm831x_pd_data_work);
215 259
216 /* 260 /*
217 * If we have a direct IRQ use it, otherwise use the interrupt 261 * If we have a direct IRQ use it, otherwise use the interrupt