diff options
author | Todd Fischer <todd.fischer@ridgerun.com> | 2010-04-05 19:53:12 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2010-05-27 19:37:38 -0400 |
commit | 752599667048898b6969e06e4637f906b04ec752 (patch) | |
tree | 91fd727ff21048eac193a84ff26833ff69647d5d /drivers/input/touchscreen | |
parent | 31dd6a2672e337f5de188df3e5169ee732798236 (diff) |
input: Touchscreen driver for TPS6507x
Add touch screen input driver for TPS6507x family of multi-function
chips. Uses the TPS6507x MFD driver. No interrupt support due to
testing limitations of current hardware.
Signed-off-by: Todd Fischer <todd.fischer@ridgerun.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/input/touchscreen')
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 13 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/tps6507x-ts.c | 400 |
3 files changed, 414 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index b9f58ca82fd..6703c6b9800 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig | |||
@@ -590,4 +590,17 @@ config TOUCHSCREEN_PCAP | |||
590 | 590 | ||
591 | To compile this driver as a module, choose M here: the | 591 | To compile this driver as a module, choose M here: the |
592 | module will be called pcap_ts. | 592 | module will be called pcap_ts. |
593 | |||
594 | config TOUCHSCREEN_TPS6507X | ||
595 | tristate "TPS6507x based touchscreens" | ||
596 | depends on I2C | ||
597 | help | ||
598 | Say Y here if you have a TPS6507x based touchscreen | ||
599 | controller. | ||
600 | |||
601 | If unsure, say N. | ||
602 | |||
603 | To compile this driver as a module, choose M here: the | ||
604 | module will be called tps6507x_ts. | ||
605 | |||
593 | endif | 606 | endif |
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 8ad36eef90a..497964a7a21 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile | |||
@@ -46,3 +46,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL) += atmel-wm97xx.o | |||
46 | obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o | 46 | obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o |
47 | obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o | 47 | obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o |
48 | obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o | 48 | obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o |
49 | obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o | ||
diff --git a/drivers/input/touchscreen/tps6507x-ts.c b/drivers/input/touchscreen/tps6507x-ts.c new file mode 100644 index 00000000000..5de80a1a730 --- /dev/null +++ b/drivers/input/touchscreen/tps6507x-ts.c | |||
@@ -0,0 +1,400 @@ | |||
1 | /* | ||
2 | * drivers/input/touchscreen/tps6507x_ts.c | ||
3 | * | ||
4 | * Touchscreen driver for the tps6507x chip. | ||
5 | * | ||
6 | * Copyright (c) 2009 RidgeRun (todd.fischer@ridgerun.com) | ||
7 | * | ||
8 | * Credits: | ||
9 | * | ||
10 | * Using code from tsc2007, MtekVision Co., Ltd. | ||
11 | * | ||
12 | * For licencing details see kernel-base/COPYING | ||
13 | * | ||
14 | * TPS65070, TPS65073, TPS650731, and TPS650732 support | ||
15 | * 10 bit touch screen interface. | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/workqueue.h> | ||
20 | #include <linux/slab.h> | ||
21 | #include <linux/input.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/mfd/tps6507x.h> | ||
24 | #include <linux/input/tps6507x-ts.h> | ||
25 | #include <linux/delay.h> | ||
26 | |||
27 | #define TSC_DEFAULT_POLL_PERIOD 30 /* ms */ | ||
28 | #define TPS_DEFAULT_MIN_PRESSURE 0x30 | ||
29 | #define MAX_10BIT ((1 << 10) - 1) | ||
30 | |||
31 | #define TPS6507X_ADCONFIG_CONVERT_TS (TPS6507X_ADCONFIG_AD_ENABLE | \ | ||
32 | TPS6507X_ADCONFIG_START_CONVERSION | \ | ||
33 | TPS6507X_ADCONFIG_INPUT_REAL_TSC) | ||
34 | #define TPS6507X_ADCONFIG_POWER_DOWN_TS (TPS6507X_ADCONFIG_INPUT_REAL_TSC) | ||
35 | |||
36 | struct ts_event { | ||
37 | u16 x; | ||
38 | u16 y; | ||
39 | u16 pressure; | ||
40 | }; | ||
41 | |||
42 | struct tps6507x_ts { | ||
43 | struct input_dev *input_dev; | ||
44 | struct device *dev; | ||
45 | char phys[32]; | ||
46 | struct workqueue_struct *wq; | ||
47 | struct delayed_work work; | ||
48 | unsigned polling; /* polling is active */ | ||
49 | struct ts_event tc; | ||
50 | struct tps6507x_dev *mfd; | ||
51 | u16 model; | ||
52 | unsigned pendown; | ||
53 | int irq; | ||
54 | void (*clear_penirq)(void); | ||
55 | unsigned long poll_period; /* ms */ | ||
56 | u16 min_pressure; | ||
57 | int vref; /* non-zero to leave vref on */ | ||
58 | }; | ||
59 | |||
60 | static int tps6507x_read_u8(struct tps6507x_ts *tsc, u8 reg, u8 *data) | ||
61 | { | ||
62 | int err; | ||
63 | |||
64 | err = tsc->mfd->read_dev(tsc->mfd, reg, 1, data); | ||
65 | |||
66 | if (err) | ||
67 | return err; | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | static int tps6507x_write_u8(struct tps6507x_ts *tsc, u8 reg, u8 data) | ||
73 | { | ||
74 | return tsc->mfd->write_dev(tsc->mfd, reg, 1, &data); | ||
75 | } | ||
76 | |||
77 | static s32 tps6507x_adc_conversion(struct tps6507x_ts *tsc, | ||
78 | u8 tsc_mode, u16 *value) | ||
79 | { | ||
80 | s32 ret; | ||
81 | u8 adc_status; | ||
82 | u8 result; | ||
83 | |||
84 | /* Route input signal to A/D converter */ | ||
85 | |||
86 | ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE, tsc_mode); | ||
87 | if (ret) { | ||
88 | dev_err(tsc->dev, "TSC mode read failed\n"); | ||
89 | goto err; | ||
90 | } | ||
91 | |||
92 | /* Start A/D conversion */ | ||
93 | |||
94 | ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG, | ||
95 | TPS6507X_ADCONFIG_CONVERT_TS); | ||
96 | if (ret) { | ||
97 | dev_err(tsc->dev, "ADC config write failed\n"); | ||
98 | return ret; | ||
99 | } | ||
100 | |||
101 | do { | ||
102 | ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADCONFIG, | ||
103 | &adc_status); | ||
104 | if (ret) { | ||
105 | dev_err(tsc->dev, "ADC config read failed\n"); | ||
106 | goto err; | ||
107 | } | ||
108 | } while (adc_status & TPS6507X_ADCONFIG_START_CONVERSION); | ||
109 | |||
110 | ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_2, &result); | ||
111 | if (ret) { | ||
112 | dev_err(tsc->dev, "ADC result 2 read failed\n"); | ||
113 | goto err; | ||
114 | } | ||
115 | |||
116 | *value = (result & TPS6507X_REG_ADRESULT_2_MASK) << 8; | ||
117 | |||
118 | ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_1, &result); | ||
119 | if (ret) { | ||
120 | dev_err(tsc->dev, "ADC result 1 read failed\n"); | ||
121 | goto err; | ||
122 | } | ||
123 | |||
124 | *value |= result; | ||
125 | |||
126 | dev_dbg(tsc->dev, "TSC channel %d = 0x%X\n", tsc_mode, *value); | ||
127 | |||
128 | err: | ||
129 | return ret; | ||
130 | } | ||
131 | |||
132 | /* Need to call tps6507x_adc_standby() after using A/D converter for the | ||
133 | * touch screen interrupt to work properly. | ||
134 | */ | ||
135 | |||
136 | static s32 tps6507x_adc_standby(struct tps6507x_ts *tsc) | ||
137 | { | ||
138 | s32 ret; | ||
139 | s32 loops = 0; | ||
140 | u8 val; | ||
141 | |||
142 | ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG, | ||
143 | TPS6507X_ADCONFIG_INPUT_TSC); | ||
144 | if (ret) | ||
145 | return ret; | ||
146 | |||
147 | ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE, | ||
148 | TPS6507X_TSCMODE_STANDBY); | ||
149 | if (ret) | ||
150 | return ret; | ||
151 | |||
152 | ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val); | ||
153 | if (ret) | ||
154 | return ret; | ||
155 | |||
156 | while (val & TPS6507X_REG_TSC_INT) { | ||
157 | mdelay(10); | ||
158 | ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val); | ||
159 | if (ret) | ||
160 | return ret; | ||
161 | loops++; | ||
162 | } | ||
163 | |||
164 | return ret; | ||
165 | } | ||
166 | |||
167 | static void tps6507x_ts_handler(struct work_struct *work) | ||
168 | { | ||
169 | struct tps6507x_ts *tsc = container_of(work, | ||
170 | struct tps6507x_ts, work.work); | ||
171 | struct input_dev *input_dev = tsc->input_dev; | ||
172 | int pendown; | ||
173 | int schd; | ||
174 | int poll = 0; | ||
175 | s32 ret; | ||
176 | |||
177 | ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_PRESSURE, | ||
178 | &tsc->tc.pressure); | ||
179 | if (ret) | ||
180 | goto done; | ||
181 | |||
182 | pendown = tsc->tc.pressure > tsc->min_pressure; | ||
183 | |||
184 | if (unlikely(!pendown && tsc->pendown)) { | ||
185 | dev_dbg(tsc->dev, "UP\n"); | ||
186 | input_report_key(input_dev, BTN_TOUCH, 0); | ||
187 | input_report_abs(input_dev, ABS_PRESSURE, 0); | ||
188 | input_sync(input_dev); | ||
189 | tsc->pendown = 0; | ||
190 | } | ||
191 | |||
192 | if (pendown) { | ||
193 | |||
194 | if (!tsc->pendown) { | ||
195 | dev_dbg(tsc->dev, "DOWN\n"); | ||
196 | input_report_key(input_dev, BTN_TOUCH, 1); | ||
197 | } else | ||
198 | dev_dbg(tsc->dev, "still down\n"); | ||
199 | |||
200 | ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_X_POSITION, | ||
201 | &tsc->tc.x); | ||
202 | if (ret) | ||
203 | goto done; | ||
204 | |||
205 | ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_Y_POSITION, | ||
206 | &tsc->tc.y); | ||
207 | if (ret) | ||
208 | goto done; | ||
209 | |||
210 | input_report_abs(input_dev, ABS_X, tsc->tc.x); | ||
211 | input_report_abs(input_dev, ABS_Y, tsc->tc.y); | ||
212 | input_report_abs(input_dev, ABS_PRESSURE, tsc->tc.pressure); | ||
213 | input_sync(input_dev); | ||
214 | tsc->pendown = 1; | ||
215 | poll = 1; | ||
216 | } | ||
217 | |||
218 | done: | ||
219 | /* always poll if not using interrupts */ | ||
220 | poll = 1; | ||
221 | |||
222 | if (poll) { | ||
223 | schd = queue_delayed_work(tsc->wq, &tsc->work, | ||
224 | tsc->poll_period * HZ / 1000); | ||
225 | if (schd) | ||
226 | tsc->polling = 1; | ||
227 | else { | ||
228 | tsc->polling = 0; | ||
229 | dev_err(tsc->dev, "re-schedule failed"); | ||
230 | } | ||
231 | } else | ||
232 | tsc->polling = 0; | ||
233 | |||
234 | ret = tps6507x_adc_standby(tsc); | ||
235 | } | ||
236 | |||
237 | static int tps6507x_ts_probe(struct platform_device *pdev) | ||
238 | { | ||
239 | int error; | ||
240 | struct tps6507x_ts *tsc; | ||
241 | struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent); | ||
242 | struct touchscreen_init_data *init_data; | ||
243 | struct input_dev *input_dev; | ||
244 | struct tps6507x_board *tps_board; | ||
245 | int schd; | ||
246 | |||
247 | /** | ||
248 | * tps_board points to pmic related constants | ||
249 | * coming from the board-evm file. | ||
250 | */ | ||
251 | |||
252 | tps_board = (struct tps6507x_board *)tps6507x_dev->dev->platform_data; | ||
253 | |||
254 | if (!tps_board) { | ||
255 | dev_err(tps6507x_dev->dev, | ||
256 | "Could not find tps6507x platform data\n"); | ||
257 | return -EIO; | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * init_data points to array of regulator_init structures | ||
262 | * coming from the board-evm file. | ||
263 | */ | ||
264 | |||
265 | init_data = tps_board->tps6507x_ts_init_data; | ||
266 | |||
267 | tsc = kzalloc(sizeof(struct tps6507x_ts), GFP_KERNEL); | ||
268 | if (!tsc) { | ||
269 | dev_err(tps6507x_dev->dev, "failed to allocate driver data\n"); | ||
270 | error = -ENOMEM; | ||
271 | goto err0; | ||
272 | } | ||
273 | |||
274 | tps6507x_dev->ts = tsc; | ||
275 | tsc->mfd = tps6507x_dev; | ||
276 | tsc->dev = tps6507x_dev->dev; | ||
277 | input_dev = input_allocate_device(); | ||
278 | if (!input_dev) { | ||
279 | dev_err(tsc->dev, "Failed to allocate input device.\n"); | ||
280 | error = -ENOMEM; | ||
281 | goto err1; | ||
282 | } | ||
283 | |||
284 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | ||
285 | input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | ||
286 | |||
287 | input_set_abs_params(input_dev, ABS_X, 0, MAX_10BIT, 0, 0); | ||
288 | input_set_abs_params(input_dev, ABS_Y, 0, MAX_10BIT, 0, 0); | ||
289 | input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_10BIT, 0, 0); | ||
290 | |||
291 | input_dev->name = "TPS6507x Touchscreen"; | ||
292 | input_dev->id.bustype = BUS_I2C; | ||
293 | input_dev->dev.parent = tsc->dev; | ||
294 | |||
295 | snprintf(tsc->phys, sizeof(tsc->phys), | ||
296 | "%s/input0", dev_name(tsc->dev)); | ||
297 | input_dev->phys = tsc->phys; | ||
298 | |||
299 | dev_dbg(tsc->dev, "device: %s\n", input_dev->phys); | ||
300 | |||
301 | input_set_drvdata(input_dev, tsc); | ||
302 | |||
303 | tsc->input_dev = input_dev; | ||
304 | |||
305 | INIT_DELAYED_WORK(&tsc->work, tps6507x_ts_handler); | ||
306 | tsc->wq = create_workqueue("TPS6507x Touchscreen"); | ||
307 | |||
308 | if (init_data) { | ||
309 | tsc->poll_period = init_data->poll_period; | ||
310 | tsc->vref = init_data->vref; | ||
311 | tsc->min_pressure = init_data->min_pressure; | ||
312 | input_dev->id.vendor = init_data->vendor; | ||
313 | input_dev->id.product = init_data->product; | ||
314 | input_dev->id.version = init_data->version; | ||
315 | } else { | ||
316 | tsc->poll_period = TSC_DEFAULT_POLL_PERIOD; | ||
317 | tsc->min_pressure = TPS_DEFAULT_MIN_PRESSURE; | ||
318 | } | ||
319 | |||
320 | error = tps6507x_adc_standby(tsc); | ||
321 | if (error) | ||
322 | goto err2; | ||
323 | |||
324 | error = input_register_device(input_dev); | ||
325 | if (error) | ||
326 | goto err2; | ||
327 | |||
328 | schd = queue_delayed_work(tsc->wq, &tsc->work, | ||
329 | tsc->poll_period * HZ / 1000); | ||
330 | |||
331 | if (schd) | ||
332 | tsc->polling = 1; | ||
333 | else { | ||
334 | tsc->polling = 0; | ||
335 | dev_err(tsc->dev, "schedule failed"); | ||
336 | goto err2; | ||
337 | } | ||
338 | |||
339 | return 0; | ||
340 | |||
341 | err2: | ||
342 | cancel_delayed_work(&tsc->work); | ||
343 | flush_workqueue(tsc->wq); | ||
344 | destroy_workqueue(tsc->wq); | ||
345 | tsc->wq = 0; | ||
346 | input_free_device(input_dev); | ||
347 | err1: | ||
348 | kfree(tsc); | ||
349 | tps6507x_dev->ts = NULL; | ||
350 | err0: | ||
351 | return error; | ||
352 | } | ||
353 | |||
354 | static int __devexit tps6507x_ts_remove(struct platform_device *pdev) | ||
355 | { | ||
356 | struct tps6507x_dev *tps6507x_dev = platform_get_drvdata(pdev); | ||
357 | struct tps6507x_ts *tsc = tps6507x_dev->ts; | ||
358 | struct input_dev *input_dev = tsc->input_dev; | ||
359 | |||
360 | if (!tsc) | ||
361 | return 0; | ||
362 | |||
363 | cancel_delayed_work(&tsc->work); | ||
364 | flush_workqueue(tsc->wq); | ||
365 | destroy_workqueue(tsc->wq); | ||
366 | tsc->wq = 0; | ||
367 | |||
368 | input_free_device(input_dev); | ||
369 | |||
370 | tps6507x_dev->ts = NULL; | ||
371 | kfree(tsc); | ||
372 | |||
373 | return 0; | ||
374 | } | ||
375 | |||
376 | static struct platform_driver tps6507x_ts_driver = { | ||
377 | .driver = { | ||
378 | .name = "tps6507x-ts", | ||
379 | .owner = THIS_MODULE, | ||
380 | }, | ||
381 | .probe = tps6507x_ts_probe, | ||
382 | .remove = __devexit_p(tps6507x_ts_remove), | ||
383 | }; | ||
384 | |||
385 | static int __init tps6507x_ts_init(void) | ||
386 | { | ||
387 | return platform_driver_register(&tps6507x_ts_driver); | ||
388 | } | ||
389 | module_init(tps6507x_ts_init); | ||
390 | |||
391 | static void __exit tps6507x_ts_exit(void) | ||
392 | { | ||
393 | platform_driver_unregister(&tps6507x_ts_driver); | ||
394 | } | ||
395 | module_exit(tps6507x_ts_exit); | ||
396 | |||
397 | MODULE_AUTHOR("Todd Fischer <todd.fischer@ridgerun.com>"); | ||
398 | MODULE_DESCRIPTION("TPS6507x - TouchScreen driver"); | ||
399 | MODULE_LICENSE("GPL v2"); | ||
400 | MODULE_ALIAS("platform:tps6507x-tsc"); | ||