1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Touchscreen driver for UCB1x00-based touchscreens |
4 | * |
5 | * Copyright (C) 2001 Russell King, All Rights Reserved. |
6 | * Copyright (C) 2005 Pavel Machek |
7 | * |
8 | * 21-Jan-2002 <jco@ict.es> : |
9 | * |
10 | * Added support for synchronous A/D mode. This mode is useful to |
11 | * avoid noise induced in the touchpanel by the LCD, provided that |
12 | * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin. |
13 | * It is important to note that the signal connected to the ADCSYNC |
14 | * pin should provide pulses even when the LCD is blanked, otherwise |
15 | * a pen touch needed to unblank the LCD will never be read. |
16 | */ |
17 | #include <linux/module.h> |
18 | #include <linux/moduleparam.h> |
19 | #include <linux/init.h> |
20 | #include <linux/interrupt.h> |
21 | #include <linux/sched.h> |
22 | #include <linux/spinlock.h> |
23 | #include <linux/completion.h> |
24 | #include <linux/delay.h> |
25 | #include <linux/string.h> |
26 | #include <linux/input.h> |
27 | #include <linux/device.h> |
28 | #include <linux/freezer.h> |
29 | #include <linux/slab.h> |
30 | #include <linux/kthread.h> |
31 | #include <linux/mfd/ucb1x00.h> |
32 | |
33 | #include <mach/collie.h> |
34 | #include <asm/mach-types.h> |
35 | |
36 | |
37 | |
38 | struct ucb1x00_ts { |
39 | struct input_dev *idev; |
40 | struct ucb1x00 *ucb; |
41 | |
42 | spinlock_t irq_lock; |
43 | unsigned irq_disabled; |
44 | wait_queue_head_t irq_wait; |
45 | struct task_struct *rtask; |
46 | u16 x_res; |
47 | u16 y_res; |
48 | |
49 | unsigned int adcsync:1; |
50 | }; |
51 | |
52 | static int adcsync; |
53 | |
54 | static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y) |
55 | { |
56 | struct input_dev *idev = ts->idev; |
57 | |
58 | input_report_abs(dev: idev, ABS_X, value: x); |
59 | input_report_abs(dev: idev, ABS_Y, value: y); |
60 | input_report_abs(dev: idev, ABS_PRESSURE, value: pressure); |
61 | input_report_key(dev: idev, BTN_TOUCH, value: 1); |
62 | input_sync(dev: idev); |
63 | } |
64 | |
65 | static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts) |
66 | { |
67 | struct input_dev *idev = ts->idev; |
68 | |
69 | input_report_abs(dev: idev, ABS_PRESSURE, value: 0); |
70 | input_report_key(dev: idev, BTN_TOUCH, value: 0); |
71 | input_sync(dev: idev); |
72 | } |
73 | |
74 | /* |
75 | * Switch to interrupt mode. |
76 | */ |
77 | static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts) |
78 | { |
79 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
80 | UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | |
81 | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | |
82 | UCB_TS_CR_MODE_INT); |
83 | } |
84 | |
85 | /* |
86 | * Switch to pressure mode, and read pressure. We don't need to wait |
87 | * here, since both plates are being driven. |
88 | */ |
89 | static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) |
90 | { |
91 | if (machine_is_collie()) { |
92 | ucb1x00_io_write(ucb: ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0); |
93 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
94 | UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW | |
95 | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); |
96 | |
97 | udelay(55); |
98 | |
99 | return ucb1x00_adc_read(ucb: ts->ucb, UCB_ADC_INP_AD2, sync: ts->adcsync); |
100 | } else { |
101 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
102 | UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | |
103 | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | |
104 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
105 | |
106 | return ucb1x00_adc_read(ucb: ts->ucb, UCB_ADC_INP_TSPY, sync: ts->adcsync); |
107 | } |
108 | } |
109 | |
110 | /* |
111 | * Switch to X position mode and measure Y plate. We switch the plate |
112 | * configuration in pressure mode, then switch to position mode. This |
113 | * gives a faster response time. Even so, we need to wait about 55us |
114 | * for things to stabilise. |
115 | */ |
116 | static inline unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) |
117 | { |
118 | if (machine_is_collie()) |
119 | ucb1x00_io_write(ucb: ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); |
120 | else { |
121 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
122 | UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | |
123 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
124 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
125 | UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | |
126 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
127 | } |
128 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
129 | UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | |
130 | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); |
131 | |
132 | udelay(55); |
133 | |
134 | return ucb1x00_adc_read(ucb: ts->ucb, UCB_ADC_INP_TSPY, sync: ts->adcsync); |
135 | } |
136 | |
137 | /* |
138 | * Switch to Y position mode and measure X plate. We switch the plate |
139 | * configuration in pressure mode, then switch to position mode. This |
140 | * gives a faster response time. Even so, we need to wait about 55us |
141 | * for things to stabilise. |
142 | */ |
143 | static inline unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) |
144 | { |
145 | if (machine_is_collie()) |
146 | ucb1x00_io_write(ucb: ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); |
147 | else { |
148 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
149 | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | |
150 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
151 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
152 | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | |
153 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
154 | } |
155 | |
156 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
157 | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | |
158 | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); |
159 | |
160 | udelay(55); |
161 | |
162 | return ucb1x00_adc_read(ucb: ts->ucb, UCB_ADC_INP_TSPX, sync: ts->adcsync); |
163 | } |
164 | |
165 | /* |
166 | * Switch to X plate resistance mode. Set MX to ground, PX to |
167 | * supply. Measure current. |
168 | */ |
169 | static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts) |
170 | { |
171 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
172 | UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | |
173 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
174 | return ucb1x00_adc_read(ucb: ts->ucb, adc_channel: 0, sync: ts->adcsync); |
175 | } |
176 | |
177 | /* |
178 | * Switch to Y plate resistance mode. Set MY to ground, PY to |
179 | * supply. Measure current. |
180 | */ |
181 | static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts) |
182 | { |
183 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, |
184 | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | |
185 | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); |
186 | return ucb1x00_adc_read(ucb: ts->ucb, adc_channel: 0, sync: ts->adcsync); |
187 | } |
188 | |
189 | static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts) |
190 | { |
191 | unsigned int val = ucb1x00_reg_read(ucb: ts->ucb, UCB_TS_CR); |
192 | |
193 | if (machine_is_collie()) |
194 | return (!(val & (UCB_TS_CR_TSPX_LOW))); |
195 | else |
196 | return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)); |
197 | } |
198 | |
199 | /* |
200 | * This is a RT kernel thread that handles the ADC accesses |
201 | * (mainly so we can use semaphores in the UCB1200 core code |
202 | * to serialise accesses to the ADC). |
203 | */ |
204 | static int ucb1x00_thread(void *_ts) |
205 | { |
206 | struct ucb1x00_ts *ts = _ts; |
207 | DECLARE_WAITQUEUE(wait, current); |
208 | bool frozen, ignore = false; |
209 | int valid = 0; |
210 | |
211 | set_freezable(); |
212 | add_wait_queue(wq_head: &ts->irq_wait, wq_entry: &wait); |
213 | while (!kthread_freezable_should_stop(was_frozen: &frozen)) { |
214 | unsigned int x, y, p; |
215 | signed long timeout; |
216 | |
217 | if (frozen) |
218 | ignore = true; |
219 | |
220 | ucb1x00_adc_enable(ucb: ts->ucb); |
221 | |
222 | x = ucb1x00_ts_read_xpos(ts); |
223 | y = ucb1x00_ts_read_ypos(ts); |
224 | p = ucb1x00_ts_read_pressure(ts); |
225 | |
226 | /* |
227 | * Switch back to interrupt mode. |
228 | */ |
229 | ucb1x00_ts_mode_int(ts); |
230 | ucb1x00_adc_disable(ucb: ts->ucb); |
231 | |
232 | msleep(msecs: 10); |
233 | |
234 | ucb1x00_enable(ucb: ts->ucb); |
235 | |
236 | |
237 | if (ucb1x00_ts_pen_down(ts)) { |
238 | set_current_state(TASK_INTERRUPTIBLE); |
239 | |
240 | spin_lock_irq(lock: &ts->irq_lock); |
241 | if (ts->irq_disabled) { |
242 | ts->irq_disabled = 0; |
243 | enable_irq(irq: ts->ucb->irq_base + UCB_IRQ_TSPX); |
244 | } |
245 | spin_unlock_irq(lock: &ts->irq_lock); |
246 | ucb1x00_disable(ucb: ts->ucb); |
247 | |
248 | /* |
249 | * If we spat out a valid sample set last time, |
250 | * spit out a "pen off" sample here. |
251 | */ |
252 | if (valid) { |
253 | ucb1x00_ts_event_release(ts); |
254 | valid = 0; |
255 | } |
256 | |
257 | timeout = MAX_SCHEDULE_TIMEOUT; |
258 | } else { |
259 | ucb1x00_disable(ucb: ts->ucb); |
260 | |
261 | /* |
262 | * Filtering is policy. Policy belongs in user |
263 | * space. We therefore leave it to user space |
264 | * to do any filtering they please. |
265 | */ |
266 | if (!ignore) { |
267 | ucb1x00_ts_evt_add(ts, pressure: p, x, y); |
268 | valid = 1; |
269 | } |
270 | |
271 | set_current_state(TASK_INTERRUPTIBLE); |
272 | timeout = HZ / 100; |
273 | } |
274 | |
275 | schedule_timeout(timeout); |
276 | } |
277 | |
278 | remove_wait_queue(wq_head: &ts->irq_wait, wq_entry: &wait); |
279 | |
280 | ts->rtask = NULL; |
281 | return 0; |
282 | } |
283 | |
284 | /* |
285 | * We only detect touch screen _touches_ with this interrupt |
286 | * handler, and even then we just schedule our task. |
287 | */ |
288 | static irqreturn_t ucb1x00_ts_irq(int irq, void *id) |
289 | { |
290 | struct ucb1x00_ts *ts = id; |
291 | |
292 | spin_lock(lock: &ts->irq_lock); |
293 | ts->irq_disabled = 1; |
294 | disable_irq_nosync(irq: ts->ucb->irq_base + UCB_IRQ_TSPX); |
295 | spin_unlock(lock: &ts->irq_lock); |
296 | wake_up(&ts->irq_wait); |
297 | |
298 | return IRQ_HANDLED; |
299 | } |
300 | |
301 | static int ucb1x00_ts_open(struct input_dev *idev) |
302 | { |
303 | struct ucb1x00_ts *ts = input_get_drvdata(dev: idev); |
304 | unsigned long flags = 0; |
305 | int ret = 0; |
306 | |
307 | BUG_ON(ts->rtask); |
308 | |
309 | if (machine_is_collie()) |
310 | flags = IRQF_TRIGGER_RISING; |
311 | else |
312 | flags = IRQF_TRIGGER_FALLING; |
313 | |
314 | ts->irq_disabled = 0; |
315 | |
316 | init_waitqueue_head(&ts->irq_wait); |
317 | ret = request_irq(irq: ts->ucb->irq_base + UCB_IRQ_TSPX, handler: ucb1x00_ts_irq, |
318 | flags, name: "ucb1x00-ts" , dev: ts); |
319 | if (ret < 0) |
320 | goto out; |
321 | |
322 | /* |
323 | * If we do this at all, we should allow the user to |
324 | * measure and read the X and Y resistance at any time. |
325 | */ |
326 | ucb1x00_adc_enable(ucb: ts->ucb); |
327 | ts->x_res = ucb1x00_ts_read_xres(ts); |
328 | ts->y_res = ucb1x00_ts_read_yres(ts); |
329 | ucb1x00_adc_disable(ucb: ts->ucb); |
330 | |
331 | ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd" ); |
332 | if (!IS_ERR(ptr: ts->rtask)) { |
333 | ret = 0; |
334 | } else { |
335 | free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts); |
336 | ts->rtask = NULL; |
337 | ret = -EFAULT; |
338 | } |
339 | |
340 | out: |
341 | return ret; |
342 | } |
343 | |
344 | /* |
345 | * Release touchscreen resources. Disable IRQs. |
346 | */ |
347 | static void ucb1x00_ts_close(struct input_dev *idev) |
348 | { |
349 | struct ucb1x00_ts *ts = input_get_drvdata(dev: idev); |
350 | |
351 | if (ts->rtask) |
352 | kthread_stop(k: ts->rtask); |
353 | |
354 | ucb1x00_enable(ucb: ts->ucb); |
355 | free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts); |
356 | ucb1x00_reg_write(ucb: ts->ucb, UCB_TS_CR, val: 0); |
357 | ucb1x00_disable(ucb: ts->ucb); |
358 | } |
359 | |
360 | |
361 | /* |
362 | * Initialisation. |
363 | */ |
364 | static int ucb1x00_ts_add(struct ucb1x00_dev *dev) |
365 | { |
366 | struct ucb1x00_ts *ts; |
367 | struct input_dev *idev; |
368 | int err; |
369 | |
370 | ts = kzalloc(size: sizeof(struct ucb1x00_ts), GFP_KERNEL); |
371 | idev = input_allocate_device(); |
372 | if (!ts || !idev) { |
373 | err = -ENOMEM; |
374 | goto fail; |
375 | } |
376 | |
377 | ts->ucb = dev->ucb; |
378 | ts->idev = idev; |
379 | ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC; |
380 | spin_lock_init(&ts->irq_lock); |
381 | |
382 | idev->name = "Touchscreen panel" ; |
383 | idev->id.product = ts->ucb->id; |
384 | idev->open = ucb1x00_ts_open; |
385 | idev->close = ucb1x00_ts_close; |
386 | idev->dev.parent = &ts->ucb->dev; |
387 | |
388 | idev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); |
389 | idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); |
390 | |
391 | input_set_drvdata(dev: idev, data: ts); |
392 | |
393 | ucb1x00_adc_enable(ucb: ts->ucb); |
394 | ts->x_res = ucb1x00_ts_read_xres(ts); |
395 | ts->y_res = ucb1x00_ts_read_yres(ts); |
396 | ucb1x00_adc_disable(ucb: ts->ucb); |
397 | |
398 | input_set_abs_params(dev: idev, ABS_X, min: 0, max: ts->x_res, fuzz: 0, flat: 0); |
399 | input_set_abs_params(dev: idev, ABS_Y, min: 0, max: ts->y_res, fuzz: 0, flat: 0); |
400 | input_set_abs_params(dev: idev, ABS_PRESSURE, min: 0, max: 0, fuzz: 0, flat: 0); |
401 | |
402 | err = input_register_device(idev); |
403 | if (err) |
404 | goto fail; |
405 | |
406 | dev->priv = ts; |
407 | |
408 | return 0; |
409 | |
410 | fail: |
411 | input_free_device(dev: idev); |
412 | kfree(objp: ts); |
413 | return err; |
414 | } |
415 | |
416 | static void ucb1x00_ts_remove(struct ucb1x00_dev *dev) |
417 | { |
418 | struct ucb1x00_ts *ts = dev->priv; |
419 | |
420 | input_unregister_device(ts->idev); |
421 | kfree(objp: ts); |
422 | } |
423 | |
424 | static struct ucb1x00_driver ucb1x00_ts_driver = { |
425 | .add = ucb1x00_ts_add, |
426 | .remove = ucb1x00_ts_remove, |
427 | }; |
428 | |
429 | static int __init ucb1x00_ts_init(void) |
430 | { |
431 | return ucb1x00_register_driver(&ucb1x00_ts_driver); |
432 | } |
433 | |
434 | static void __exit ucb1x00_ts_exit(void) |
435 | { |
436 | ucb1x00_unregister_driver(&ucb1x00_ts_driver); |
437 | } |
438 | |
439 | module_param(adcsync, int, 0444); |
440 | module_init(ucb1x00_ts_init); |
441 | module_exit(ucb1x00_ts_exit); |
442 | |
443 | MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>" ); |
444 | MODULE_DESCRIPTION("UCB1x00 touchscreen driver" ); |
445 | MODULE_LICENSE("GPL" ); |
446 | |