aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/touchscreen/atmel-wm97xx.c
diff options
context:
space:
mode:
authorHans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>2009-04-18 21:45:06 -0400
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2009-04-18 22:13:34 -0400
commit864fe73c312ca8e177da01207ce86fb1b80b3e54 (patch)
treef55e566d457124095a7235bce56159dfda8582bd /drivers/input/touchscreen/atmel-wm97xx.c
parent64e8563ca86167b4a991724b416d61c129138359 (diff)
Input: add wm97xx accelerated driver for Atmel microprocessors
This patch adds an accelerated driver for Atmel AVR32 AT32AP700X microprocessors. It uses interrupts on the channel B in the AC97 controller. Thus, offloading the work queue in the wm97xx-ts driver. The driver has been tested with Atmel AVR32 AT32AP7000 and Wolfson WM9712 codec. The driver can also be easily modified to support Atmel AT91 devices, as AT91 and AVR32 share the same AC97C module. [Fixed leak of atmel_wm97xx when probe fails. -- broonie] [dtor@mail.ru: do not report ABS_PRESSURE events when not measuring pressure] Signed-off-by: Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers/input/touchscreen/atmel-wm97xx.c')
-rw-r--r--drivers/input/touchscreen/atmel-wm97xx.c446
1 files changed, 446 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/atmel-wm97xx.c b/drivers/input/touchscreen/atmel-wm97xx.c
new file mode 100644
index 000000000000..35377f583e28
--- /dev/null
+++ b/drivers/input/touchscreen/atmel-wm97xx.c
@@ -0,0 +1,446 @@
1/*
2 * Atmel AT91 and AVR32 continuous touch screen driver for Wolfson WM97xx AC97
3 * codecs.
4 *
5 * Copyright (C) 2008 - 2009 Atmel Corporation
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 version 2 as published by
9 * the Free Software Foundation.
10 */
11#include <linux/module.h>
12#include <linux/moduleparam.h>
13#include <linux/kernel.h>
14#include <linux/init.h>
15#include <linux/delay.h>
16#include <linux/irq.h>
17#include <linux/interrupt.h>
18#include <linux/wm97xx.h>
19#include <linux/timer.h>
20#include <linux/gpio.h>
21#include <linux/io.h>
22
23#define AC97C_ICA 0x10
24#define AC97C_CBRHR 0x30
25#define AC97C_CBSR 0x38
26#define AC97C_CBMR 0x3c
27#define AC97C_IER 0x54
28#define AC97C_IDR 0x58
29
30#define AC97C_RXRDY (1 << 4)
31#define AC97C_OVRUN (1 << 5)
32
33#define AC97C_CMR_SIZE_20 (0 << 16)
34#define AC97C_CMR_SIZE_18 (1 << 16)
35#define AC97C_CMR_SIZE_16 (2 << 16)
36#define AC97C_CMR_SIZE_10 (3 << 16)
37#define AC97C_CMR_CEM_LITTLE (1 << 18)
38#define AC97C_CMR_CEM_BIG (0 << 18)
39#define AC97C_CMR_CENA (1 << 21)
40
41#define AC97C_INT_CBEVT (1 << 4)
42
43#define AC97C_SR_CAEVT (1 << 3)
44
45#define AC97C_CH_MASK(slot) \
46 (0x7 << (3 * (slot - 3)))
47#define AC97C_CH_ASSIGN(slot, channel) \
48 (AC97C_CHANNEL_##channel << (3 * (slot - 3)))
49#define AC97C_CHANNEL_NONE 0x0
50#define AC97C_CHANNEL_B 0x2
51
52#define ac97c_writel(chip, reg, val) \
53 __raw_writel((val), (chip)->regs + AC97C_##reg)
54#define ac97c_readl(chip, reg) \
55 __raw_readl((chip)->regs + AC97C_##reg)
56
57#ifdef CONFIG_CPU_AT32AP700X
58#define ATMEL_WM97XX_AC97C_IOMEM (0xfff02800)
59#define ATMEL_WM97XX_AC97C_IRQ (29)
60#define ATMEL_WM97XX_GPIO_DEFAULT (32+16) /* Pin 16 on port B. */
61#else
62#error Unkown CPU, this driver only supports AT32AP700X CPUs.
63#endif
64
65struct continuous {
66 u16 id; /* codec id */
67 u8 code; /* continuous code */
68 u8 reads; /* number of coord reads per read cycle */
69 u32 speed; /* number of coords per second */
70};
71
72#define WM_READS(sp) ((sp / HZ) + 1)
73
74static const struct continuous cinfo[] = {
75 {WM9705_ID2, 0, WM_READS(94), 94},
76 {WM9705_ID2, 1, WM_READS(188), 188},
77 {WM9705_ID2, 2, WM_READS(375), 375},
78 {WM9705_ID2, 3, WM_READS(750), 750},
79 {WM9712_ID2, 0, WM_READS(94), 94},
80 {WM9712_ID2, 1, WM_READS(188), 188},
81 {WM9712_ID2, 2, WM_READS(375), 375},
82 {WM9712_ID2, 3, WM_READS(750), 750},
83 {WM9713_ID2, 0, WM_READS(94), 94},
84 {WM9713_ID2, 1, WM_READS(120), 120},
85 {WM9713_ID2, 2, WM_READS(154), 154},
86 {WM9713_ID2, 3, WM_READS(188), 188},
87};
88
89/* Continuous speed index. */
90static int sp_idx;
91
92/*
93 * Pen sampling frequency (Hz) in continuous mode.
94 */
95static int cont_rate = 188;
96module_param(cont_rate, int, 0);
97MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)");
98
99/*
100 * Pen down detection.
101 *
102 * This driver can either poll or use an interrupt to indicate a pen down
103 * event. If the irq request fails then it will fall back to polling mode.
104 */
105static int pen_int = 1;
106module_param(pen_int, int, 0);
107MODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)");
108
109/*
110 * Pressure readback.
111 *
112 * Set to 1 to read back pen down pressure.
113 */
114static int pressure;
115module_param(pressure, int, 0);
116MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)");
117
118/*
119 * AC97 touch data slot.
120 *
121 * Touch screen readback data ac97 slot.
122 */
123static int ac97_touch_slot = 5;
124module_param(ac97_touch_slot, int, 0);
125MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number");
126
127/*
128 * GPIO line number.
129 *
130 * Set to GPIO number where the signal from the WM97xx device is hooked up.
131 */
132static int atmel_gpio_line = ATMEL_WM97XX_GPIO_DEFAULT;
133module_param(atmel_gpio_line, int, 0);
134MODULE_PARM_DESC(atmel_gpio_line, "GPIO line number connected to WM97xx");
135
136struct atmel_wm97xx {
137 struct wm97xx *wm;
138 struct timer_list pen_timer;
139 void __iomem *regs;
140 unsigned long ac97c_irq;
141 unsigned long gpio_pen;
142 unsigned long gpio_irq;
143 unsigned short x;
144 unsigned short y;
145};
146
147static irqreturn_t atmel_wm97xx_channel_b_interrupt(int irq, void *dev_id)
148{
149 struct atmel_wm97xx *atmel_wm97xx = dev_id;
150 struct wm97xx *wm = atmel_wm97xx->wm;
151 int status = ac97c_readl(atmel_wm97xx, CBSR);
152 irqreturn_t retval = IRQ_NONE;
153
154 if (status & AC97C_OVRUN) {
155 dev_dbg(&wm->touch_dev->dev, "AC97C overrun\n");
156 ac97c_readl(atmel_wm97xx, CBRHR);
157 retval = IRQ_HANDLED;
158 } else if (status & AC97C_RXRDY) {
159 u16 data;
160 u16 value;
161 u16 source;
162 u16 pen_down;
163
164 data = ac97c_readl(atmel_wm97xx, CBRHR);
165 value = data & 0x0fff;
166 source = data & WM97XX_ADCSRC_MASK;
167 pen_down = (data & WM97XX_PEN_DOWN) >> 8;
168
169 if (source == WM97XX_ADCSEL_X)
170 atmel_wm97xx->x = value;
171 if (source == WM97XX_ADCSEL_Y)
172 atmel_wm97xx->y = value;
173
174 if (!pressure && source == WM97XX_ADCSEL_Y) {
175 input_report_abs(wm->input_dev, ABS_X, atmel_wm97xx->x);
176 input_report_abs(wm->input_dev, ABS_Y, atmel_wm97xx->y);
177 input_report_key(wm->input_dev, BTN_TOUCH, pen_down);
178 input_sync(wm->input_dev);
179 } else if (pressure && source == WM97XX_ADCSEL_PRES) {
180 input_report_abs(wm->input_dev, ABS_X, atmel_wm97xx->x);
181 input_report_abs(wm->input_dev, ABS_Y, atmel_wm97xx->y);
182 input_report_abs(wm->input_dev, ABS_PRESSURE, value);
183 input_report_key(wm->input_dev, BTN_TOUCH, value);
184 input_sync(wm->input_dev);
185 }
186
187 retval = IRQ_HANDLED;
188 }
189
190 return retval;
191}
192
193static void atmel_wm97xx_acc_pen_up(struct wm97xx *wm)
194{
195 struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(wm->touch_dev);
196 struct input_dev *input_dev = wm->input_dev;
197 int pen_down = gpio_get_value(atmel_wm97xx->gpio_pen);
198
199 if (pen_down != 0) {
200 mod_timer(&atmel_wm97xx->pen_timer,
201 jiffies + msecs_to_jiffies(1));
202 } else {
203 if (pressure)
204 input_report_abs(input_dev, ABS_PRESSURE, 0);
205 input_report_key(input_dev, BTN_TOUCH, 0);
206 input_sync(input_dev);
207 }
208}
209
210static void atmel_wm97xx_pen_timer(unsigned long data)
211{
212 atmel_wm97xx_acc_pen_up((struct wm97xx *)data);
213}
214
215static int atmel_wm97xx_acc_startup(struct wm97xx *wm)
216{
217 struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(wm->touch_dev);
218 int idx = 0;
219
220 if (wm->ac97 == NULL)
221 return -ENODEV;
222
223 for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) {
224 if (wm->id != cinfo[idx].id)
225 continue;
226
227 sp_idx = idx;
228
229 if (cont_rate <= cinfo[idx].speed)
230 break;
231 }
232
233 wm->acc_rate = cinfo[sp_idx].code;
234 wm->acc_slot = ac97_touch_slot;
235 dev_info(&wm->touch_dev->dev, "atmel accelerated touchscreen driver, "
236 "%d samples/sec\n", cinfo[sp_idx].speed);
237
238 if (pen_int) {
239 unsigned long reg;
240
241 wm->pen_irq = atmel_wm97xx->gpio_irq;
242
243 switch (wm->id) {
244 case WM9712_ID2: /* Fall through. */
245 case WM9713_ID2:
246 /*
247 * Use GPIO 13 (PEN_DOWN) to assert GPIO line 3
248 * (PENDOWN).
249 */
250 wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN,
251 WM97XX_GPIO_POL_HIGH,
252 WM97XX_GPIO_STICKY,
253 WM97XX_GPIO_WAKE);
254 wm97xx_config_gpio(wm, WM97XX_GPIO_3, WM97XX_GPIO_OUT,
255 WM97XX_GPIO_POL_HIGH,
256 WM97XX_GPIO_NOTSTICKY,
257 WM97XX_GPIO_NOWAKE);
258 case WM9705_ID2: /* Fall through. */
259 /*
260 * Enable touch data slot in AC97 controller channel B.
261 */
262 reg = ac97c_readl(atmel_wm97xx, ICA);
263 reg &= ~AC97C_CH_MASK(wm->acc_slot);
264 reg |= AC97C_CH_ASSIGN(wm->acc_slot, B);
265 ac97c_writel(atmel_wm97xx, ICA, reg);
266
267 /*
268 * Enable channel and interrupt for RXRDY and OVERRUN.
269 */
270 ac97c_writel(atmel_wm97xx, CBMR, AC97C_CMR_CENA
271 | AC97C_CMR_CEM_BIG
272 | AC97C_CMR_SIZE_16
273 | AC97C_OVRUN
274 | AC97C_RXRDY);
275 /* Dummy read to empty RXRHR. */
276 ac97c_readl(atmel_wm97xx, CBRHR);
277 /*
278 * Enable interrupt for channel B in the AC97
279 * controller.
280 */
281 ac97c_writel(atmel_wm97xx, IER, AC97C_INT_CBEVT);
282 break;
283 default:
284 dev_err(&wm->touch_dev->dev, "pen down irq not "
285 "supported on this device\n");
286 pen_int = 0;
287 break;
288 }
289 }
290
291 return 0;
292}
293
294static void atmel_wm97xx_acc_shutdown(struct wm97xx *wm)
295{
296 if (pen_int) {
297 struct atmel_wm97xx *atmel_wm97xx =
298 platform_get_drvdata(wm->touch_dev);
299 unsigned long ica;
300
301 switch (wm->id & 0xffff) {
302 case WM9705_ID2: /* Fall through. */
303 case WM9712_ID2: /* Fall through. */
304 case WM9713_ID2:
305 /* Disable slot and turn off channel B interrupts. */
306 ica = ac97c_readl(atmel_wm97xx, ICA);
307 ica &= ~AC97C_CH_MASK(wm->acc_slot);
308 ac97c_writel(atmel_wm97xx, ICA, ica);
309 ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT);
310 ac97c_writel(atmel_wm97xx, CBMR, 0);
311 wm->pen_irq = 0;
312 break;
313 default:
314 dev_err(&wm->touch_dev->dev, "unknown codec\n");
315 break;
316 }
317 }
318}
319
320static void atmel_wm97xx_irq_enable(struct wm97xx *wm, int enable)
321{
322 /* Intentionally left empty. */
323}
324
325static struct wm97xx_mach_ops atmel_mach_ops = {
326 .acc_enabled = 1,
327 .acc_pen_up = atmel_wm97xx_acc_pen_up,
328 .acc_startup = atmel_wm97xx_acc_startup,
329 .acc_shutdown = atmel_wm97xx_acc_shutdown,
330 .irq_enable = atmel_wm97xx_irq_enable,
331 .irq_gpio = WM97XX_GPIO_3,
332};
333
334static int __init atmel_wm97xx_probe(struct platform_device *pdev)
335{
336 struct wm97xx *wm = platform_get_drvdata(pdev);
337 struct atmel_wm97xx *atmel_wm97xx;
338 int ret;
339
340 atmel_wm97xx = kzalloc(sizeof(struct atmel_wm97xx), GFP_KERNEL);
341 if (!atmel_wm97xx) {
342 dev_dbg(&pdev->dev, "out of memory\n");
343 return -ENOMEM;
344 }
345
346 atmel_wm97xx->wm = wm;
347 atmel_wm97xx->regs = (void *)ATMEL_WM97XX_AC97C_IOMEM;
348 atmel_wm97xx->ac97c_irq = ATMEL_WM97XX_AC97C_IRQ;
349 atmel_wm97xx->gpio_pen = atmel_gpio_line;
350 atmel_wm97xx->gpio_irq = gpio_to_irq(atmel_wm97xx->gpio_pen);
351
352 setup_timer(&atmel_wm97xx->pen_timer, atmel_wm97xx_pen_timer,
353 (unsigned long)wm);
354
355 ret = request_irq(atmel_wm97xx->ac97c_irq,
356 atmel_wm97xx_channel_b_interrupt,
357 IRQF_SHARED, "atmel-wm97xx-ch-b", atmel_wm97xx);
358 if (ret) {
359 dev_dbg(&pdev->dev, "could not request ac97c irq\n");
360 goto err;
361 }
362
363 platform_set_drvdata(pdev, atmel_wm97xx);
364
365 ret = wm97xx_register_mach_ops(wm, &atmel_mach_ops);
366 if (ret)
367 goto err_irq;
368
369 return ret;
370
371err_irq:
372 free_irq(atmel_wm97xx->ac97c_irq, atmel_wm97xx);
373err:
374 platform_set_drvdata(pdev, NULL);
375 kfree(atmel_wm97xx);
376 return ret;
377}
378
379static int __exit atmel_wm97xx_remove(struct platform_device *pdev)
380{
381 struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev);
382 struct wm97xx *wm = atmel_wm97xx->wm;
383
384 ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT);
385 free_irq(atmel_wm97xx->ac97c_irq, atmel_wm97xx);
386 del_timer_sync(&atmel_wm97xx->pen_timer);
387 wm97xx_unregister_mach_ops(wm);
388 platform_set_drvdata(pdev, NULL);
389 kfree(atmel_wm97xx);
390
391 return 0;
392}
393
394#ifdef CONFIG_PM
395static int atmel_wm97xx_suspend(struct platform_device *pdev, pm_message_t msg)
396{
397 struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev);
398
399 ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT);
400 disable_irq(atmel_wm97xx->gpio_irq);
401 del_timer_sync(&atmel_wm97xx->pen_timer);
402
403 return 0;
404}
405
406static int atmel_wm97xx_resume(struct platform_device *pdev)
407{
408 struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev);
409 struct wm97xx *wm = atmel_wm97xx->wm;
410
411 if (wm->input_dev->users) {
412 enable_irq(atmel_wm97xx->gpio_irq);
413 ac97c_writel(atmel_wm97xx, IER, AC97C_INT_CBEVT);
414 }
415
416 return 0;
417}
418#else
419#define atmel_wm97xx_suspend NULL
420#define atmel_wm97xx_resume NULL
421#endif
422
423static struct platform_driver atmel_wm97xx_driver = {
424 .remove = __exit_p(atmel_wm97xx_remove),
425 .driver = {
426 .name = "wm97xx-touch",
427 },
428 .suspend = atmel_wm97xx_suspend,
429 .resume = atmel_wm97xx_resume,
430};
431
432static int __init atmel_wm97xx_init(void)
433{
434 return platform_driver_probe(&atmel_wm97xx_driver, atmel_wm97xx_probe);
435}
436module_init(atmel_wm97xx_init);
437
438static void __exit atmel_wm97xx_exit(void)
439{
440 platform_driver_unregister(&atmel_wm97xx_driver);
441}
442module_exit(atmel_wm97xx_exit);
443
444MODULE_AUTHOR("Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>");
445MODULE_DESCRIPTION("wm97xx continuous touch driver for Atmel AT91 and AVR32");
446MODULE_LICENSE("GPL");