aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2011-01-30 15:31:30 -0500
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2011-01-30 15:32:29 -0500
commit00cfa730db0d8378685148e6365b9cec7384b275 (patch)
tree68d36895d6ac31eab880b68b336aa87c23d1c9ae
parent3c36e719033ddc09aded770472cbdb477fcb4479 (diff)
Input: wm831x - add driver for Wolfson WM831x PMIC touchscreen controllers
Some of the WM831x series of PMICs from Wolfson Microelectronics include a resistive touchscreen controller. Implement support for these controllers within the input API. Platform data is supported to allow configuration of system parameters such as selection between four and five wire touchscreens and for specification of optional direct to CPU IRQs for sample availability and for pen down. Use of this feature for at least the data IRQ is strongly recommended. Thanks to Julien Boibessot for extensive testing and detailed feedback. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Tested-by: Julien Boibessot <julien.boibessot@armadeus.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
-rw-r--r--drivers/input/touchscreen/Kconfig10
-rw-r--r--drivers/input/touchscreen/Makefile1
-rw-r--r--drivers/input/touchscreen/wm831x-ts.c355
-rw-r--r--include/linux/mfd/wm831x/pdata.h3
4 files changed, 368 insertions, 1 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 07ac77d393a4..ec5ee98fa6f6 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -423,6 +423,16 @@ config TOUCHSCREEN_UCB1400
423 To compile this driver as a module, choose M here: the 423 To compile this driver as a module, choose M here: the
424 module will be called ucb1400_ts. 424 module will be called ucb1400_ts.
425 425
426config TOUCHSCREEN_WM831X
427 tristate "Support for WM831x touchscreen controllers"
428 depends on MFD_WM831X
429 help
430 This enables support for the touchscreen controller on the WM831x
431 series of PMICs.
432
433 To compile this driver as a module, choose M here: the
434 module will be called wm831x-ts.
435
426config TOUCHSCREEN_WM97XX 436config TOUCHSCREEN_WM97XX
427 tristate "Support for WM97xx AC97 touchscreen controllers" 437 tristate "Support for WM97xx AC97 touchscreen controllers"
428 depends on AC97_BUS 438 depends on AC97_BUS
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 718bcc814952..e8dcfab7c2dc 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
48obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o 48obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
49obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o 49obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
50obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o 50obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
51obj-$(CONFIG_TOUCHSCREEN_WM831X) += wm831x-ts.o
51obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o 52obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o
52wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o 53wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
53wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o 54wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
diff --git a/drivers/input/touchscreen/wm831x-ts.c b/drivers/input/touchscreen/wm831x-ts.c
new file mode 100644
index 000000000000..a6121bdb635d
--- /dev/null
+++ b/drivers/input/touchscreen/wm831x-ts.c
@@ -0,0 +1,355 @@
1/*
2 * Touchscreen driver for WM831x PMICs
3 *
4 * Copyright 2011 Wolfson Microelectronics plc.
5 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version.
11 */
12
13#include <linux/module.h>
14#include <linux/moduleparam.h>
15#include <linux/kernel.h>
16#include <linux/init.h>
17#include <linux/string.h>
18#include <linux/pm.h>
19#include <linux/input.h>
20#include <linux/interrupt.h>
21#include <linux/io.h>
22#include <linux/mfd/wm831x/core.h>
23#include <linux/mfd/wm831x/irq.h>
24#include <linux/mfd/wm831x/pdata.h>
25#include <linux/platform_device.h>
26#include <linux/slab.h>
27#include <linux/types.h>
28
29/*
30 * R16424 (0x4028) - Touch Control 1
31 */
32#define WM831X_TCH_ENA 0x8000 /* TCH_ENA */
33#define WM831X_TCH_CVT_ENA 0x4000 /* TCH_CVT_ENA */
34#define WM831X_TCH_SLPENA 0x1000 /* TCH_SLPENA */
35#define WM831X_TCH_Z_ENA 0x0400 /* TCH_Z_ENA */
36#define WM831X_TCH_Y_ENA 0x0200 /* TCH_Y_ENA */
37#define WM831X_TCH_X_ENA 0x0100 /* TCH_X_ENA */
38#define WM831X_TCH_DELAY_MASK 0x00E0 /* TCH_DELAY - [7:5] */
39#define WM831X_TCH_DELAY_SHIFT 5 /* TCH_DELAY - [7:5] */
40#define WM831X_TCH_DELAY_WIDTH 3 /* TCH_DELAY - [7:5] */
41#define WM831X_TCH_RATE_MASK 0x001F /* TCH_RATE - [4:0] */
42#define WM831X_TCH_RATE_SHIFT 0 /* TCH_RATE - [4:0] */
43#define WM831X_TCH_RATE_WIDTH 5 /* TCH_RATE - [4:0] */
44
45/*
46 * R16425 (0x4029) - Touch Control 2
47 */
48#define WM831X_TCH_PD_WK 0x2000 /* TCH_PD_WK */
49#define WM831X_TCH_5WIRE 0x1000 /* TCH_5WIRE */
50#define WM831X_TCH_PDONLY 0x0800 /* TCH_PDONLY */
51#define WM831X_TCH_ISEL 0x0100 /* TCH_ISEL */
52#define WM831X_TCH_RPU_MASK 0x000F /* TCH_RPU - [3:0] */
53#define WM831X_TCH_RPU_SHIFT 0 /* TCH_RPU - [3:0] */
54#define WM831X_TCH_RPU_WIDTH 4 /* TCH_RPU - [3:0] */
55
56/*
57 * R16426-8 (0x402A-C) - Touch Data X/Y/X
58 */
59#define WM831X_TCH_PD 0x8000 /* TCH_PD1 */
60#define WM831X_TCH_DATA_MASK 0x0FFF /* TCH_DATA - [11:0] */
61#define WM831X_TCH_DATA_SHIFT 0 /* TCH_DATA - [11:0] */
62#define WM831X_TCH_DATA_WIDTH 12 /* TCH_DATA - [11:0] */
63
64struct wm831x_ts {
65 struct input_dev *input_dev;
66 struct wm831x *wm831x;
67 unsigned int data_irq;
68 unsigned int pd_irq;
69 bool pressure;
70 bool pen_down;
71};
72
73static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
74{
75 struct wm831x_ts *wm831x_ts = irq_data;
76 struct wm831x *wm831x = wm831x_ts->wm831x;
77 static int data_types[] = { ABS_X, ABS_Y, ABS_PRESSURE };
78 u16 data[3];
79 int count = wm831x_ts->pressure ? 3 : 2;
80 int i, ret;
81
82 wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
83 WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
84
85 ret = wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count,
86 data);
87 if (ret != 0) {
88 dev_err(wm831x->dev, "Failed to read touch data: %d\n",
89 ret);
90 return IRQ_NONE;
91 }
92
93 /*
94 * We get a pen down reading on every reading, report pen up if any
95 * individual reading does so.
96 */
97 wm831x_ts->pen_down = true;
98 for (i = 0; i < count; i++) {
99 if (!(data[i] & WM831X_TCH_PD)) {
100 wm831x_ts->pen_down = false;
101 continue;
102 }
103 input_report_abs(wm831x_ts->input_dev, data_types[i],
104 data[i] & WM831X_TCH_DATA_MASK);
105 }
106
107 if (!wm831x_ts->pen_down) {
108 disable_irq_nosync(wm831x_ts->data_irq);
109
110 /* Don't need data any more */
111 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
112 WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
113 WM831X_TCH_Z_ENA, 0);
114
115 /* Flush any final samples that arrived while reading */
116 wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
117 WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
118
119 wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count, data);
120
121 if (wm831x_ts->pressure)
122 input_report_abs(wm831x_ts->input_dev,
123 ABS_PRESSURE, 0);
124
125 input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 0);
126 }
127
128 input_sync(wm831x_ts->input_dev);
129
130 return IRQ_HANDLED;
131}
132
133static irqreturn_t wm831x_ts_pen_down_irq(int irq, void *irq_data)
134{
135 struct wm831x_ts *wm831x_ts = irq_data;
136 struct wm831x *wm831x = wm831x_ts->wm831x;
137 int ena;
138
139 /* Start collecting data */
140 ena = wm831x_ts->pressure ? WM831X_TCH_Z_ENA : 0;
141
142 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
143 WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA,
144 WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | ena);
145
146 input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 1);
147 input_sync(wm831x_ts->input_dev);
148
149 wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
150 WM831X_TCHPD_EINT, WM831X_TCHPD_EINT);
151
152 wm831x_ts->pen_down = true;
153 enable_irq(wm831x_ts->data_irq);
154
155 return IRQ_HANDLED;
156}
157
158static int wm831x_ts_input_open(struct input_dev *idev)
159{
160 struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
161 struct wm831x *wm831x = wm831x_ts->wm831x;
162
163 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
164 WM831X_TCH_ENA, WM831X_TCH_ENA);
165
166 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
167 WM831X_TCH_CVT_ENA, WM831X_TCH_CVT_ENA);
168
169 return 0;
170}
171
172static void wm831x_ts_input_close(struct input_dev *idev)
173{
174 struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
175 struct wm831x *wm831x = wm831x_ts->wm831x;
176
177 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
178 WM831X_TCH_ENA | WM831X_TCH_CVT_ENA |
179 WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
180 WM831X_TCH_Z_ENA, 0);
181
182 if (wm831x_ts->pen_down)
183 disable_irq(wm831x_ts->data_irq);
184}
185
186static __devinit int wm831x_ts_probe(struct platform_device *pdev)
187{
188 struct wm831x_ts *wm831x_ts;
189 struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
190 struct wm831x_pdata *core_pdata = dev_get_platdata(pdev->dev.parent);
191 struct wm831x_touch_pdata *pdata =
192 core_pdata ? core_pdata->touch : NULL;
193 struct input_dev *input_dev;
194 int error;
195
196 wm831x_ts = kzalloc(sizeof(struct wm831x_ts), GFP_KERNEL);
197 input_dev = input_allocate_device();
198 if (!wm831x_ts || !input_dev) {
199 error = -ENOMEM;
200 goto err_alloc;
201 }
202
203 wm831x_ts->wm831x = wm831x;
204 wm831x_ts->input_dev = input_dev;
205
206 /*
207 * If we have a direct IRQ use it, otherwise use the interrupt
208 * from the WM831x IRQ controller.
209 */
210 if (pdata && pdata->data_irq)
211 wm831x_ts->data_irq = pdata->data_irq;
212 else
213 wm831x_ts->data_irq = platform_get_irq_byname(pdev, "TCHDATA");
214
215 if (pdata && pdata->pd_irq)
216 wm831x_ts->pd_irq = pdata->pd_irq;
217 else
218 wm831x_ts->pd_irq = platform_get_irq_byname(pdev, "TCHPD");
219
220 wm831x_ts->pressure = pdata && pdata->pressure;
221
222 /* Five wire touchscreens can't report pressure */
223 if (pdata && pdata->fivewire) {
224 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
225 WM831X_TCH_5WIRE, WM831X_TCH_5WIRE);
226
227 /* Pressure measurements are not possible for five wire mode */
228 WARN_ON(pdata->pressure && pdata->fivewire);
229 wm831x_ts->pressure = false;
230 } else {
231 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
232 WM831X_TCH_5WIRE, 0);
233 }
234
235 if (pdata) {
236 switch (pdata->isel) {
237 default:
238 dev_err(&pdev->dev, "Unsupported ISEL setting: %d\n",
239 pdata->isel);
240 /* Fall through */
241 case 200:
242 case 0:
243 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
244 WM831X_TCH_ISEL, 0);
245 break;
246 case 400:
247 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
248 WM831X_TCH_ISEL, WM831X_TCH_ISEL);
249 break;
250 }
251 }
252
253 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
254 WM831X_TCH_PDONLY, 0);
255
256 /* Default to 96 samples/sec */
257 wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
258 WM831X_TCH_RATE_MASK, 6);
259
260 error = request_threaded_irq(wm831x_ts->data_irq,
261 NULL, wm831x_ts_data_irq,
262 IRQF_ONESHOT,
263 "Touchscreen data", wm831x_ts);
264 if (error) {
265 dev_err(&pdev->dev, "Failed to request data IRQ %d: %d\n",
266 wm831x_ts->data_irq, error);
267 goto err_alloc;
268 }
269 disable_irq(wm831x_ts->data_irq);
270
271 error = request_threaded_irq(wm831x_ts->pd_irq,
272 NULL, wm831x_ts_pen_down_irq,
273 IRQF_ONESHOT,
274 "Touchscreen pen down", wm831x_ts);
275 if (error) {
276 dev_err(&pdev->dev, "Failed to request pen down IRQ %d: %d\n",
277 wm831x_ts->pd_irq, error);
278 goto err_data_irq;
279 }
280
281 /* set up touch configuration */
282 input_dev->name = "WM831x touchscreen";
283 input_dev->phys = "wm831x";
284 input_dev->open = wm831x_ts_input_open;
285 input_dev->close = wm831x_ts_input_close;
286
287 __set_bit(EV_ABS, input_dev->evbit);
288 __set_bit(EV_KEY, input_dev->evbit);
289 __set_bit(BTN_TOUCH, input_dev->keybit);
290
291 input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
292 input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
293 if (wm831x_ts->pressure)
294 input_set_abs_params(input_dev, ABS_PRESSURE, 0, 4095, 5, 0);
295
296 input_set_drvdata(input_dev, wm831x_ts);
297 input_dev->dev.parent = &pdev->dev;
298
299 error = input_register_device(input_dev);
300 if (error)
301 goto err_pd_irq;
302
303 platform_set_drvdata(pdev, wm831x_ts);
304 return 0;
305
306err_pd_irq:
307 free_irq(wm831x_ts->pd_irq, wm831x_ts);
308err_data_irq:
309 free_irq(wm831x_ts->data_irq, wm831x_ts);
310err_alloc:
311 input_free_device(input_dev);
312 kfree(wm831x_ts);
313
314 return error;
315}
316
317static __devexit int wm831x_ts_remove(struct platform_device *pdev)
318{
319 struct wm831x_ts *wm831x_ts = platform_get_drvdata(pdev);
320
321 free_irq(wm831x_ts->pd_irq, wm831x_ts);
322 free_irq(wm831x_ts->data_irq, wm831x_ts);
323 input_unregister_device(wm831x_ts->input_dev);
324 kfree(wm831x_ts);
325
326 platform_set_drvdata(pdev, NULL);
327 return 0;
328}
329
330static struct platform_driver wm831x_ts_driver = {
331 .driver = {
332 .name = "wm831x-touch",
333 .owner = THIS_MODULE,
334 },
335 .probe = wm831x_ts_probe,
336 .remove = __devexit_p(wm831x_ts_remove),
337};
338
339static int __init wm831x_ts_init(void)
340{
341 return platform_driver_register(&wm831x_ts_driver);
342}
343module_init(wm831x_ts_init);
344
345static void __exit wm831x_ts_exit(void)
346{
347 platform_driver_unregister(&wm831x_ts_driver);
348}
349module_exit(wm831x_ts_exit);
350
351/* Module information */
352MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
353MODULE_DESCRIPTION("WM831x PMIC touchscreen driver");
354MODULE_LICENSE("GPL");
355MODULE_ALIAS("platform:wm831x-touch");
diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h
index fd322aca33ba..173086d42af4 100644
--- a/include/linux/mfd/wm831x/pdata.h
+++ b/include/linux/mfd/wm831x/pdata.h
@@ -80,7 +80,8 @@ struct wm831x_touch_pdata {
80 int isel; /** Current for pen down (uA) */ 80 int isel; /** Current for pen down (uA) */
81 int rpu; /** Pen down sensitivity resistor divider */ 81 int rpu; /** Pen down sensitivity resistor divider */
82 int pressure; /** Report pressure (boolean) */ 82 int pressure; /** Report pressure (boolean) */
83 int data_irq; /** Touch data ready IRQ */ 83 unsigned int data_irq; /** Touch data ready IRQ */
84 unsigned int pd_irq; /** Touch pendown detect IRQ */
84}; 85};
85 86
86enum wm831x_watchdog_action { 87enum wm831x_watchdog_action {