]>
Commit | Line | Data |
---|---|---|
50b6f1f4 KL |
1 | /* |
2 | * drivers/input/touchscreen/tsc2007.c | |
3 | * | |
4 | * Copyright (c) 2008 MtekVision Co., Ltd. | |
5 | * Kwangwoo Lee <kwlee@mtekvision.com> | |
6 | * | |
7 | * Using code from: | |
8 | * - ads7846.c | |
9 | * Copyright (c) 2005 David Brownell | |
10 | * Copyright (c) 2006 Nokia Corporation | |
11 | * - corgi_ts.c | |
12 | * Copyright (C) 2004-2005 Richard Purdie | |
13 | * - omap_ts.[hc], ads7846.h, ts_osk.c | |
14 | * Copyright (C) 2002 MontaVista Software | |
15 | * Copyright (C) 2004 Texas Instruments | |
16 | * Copyright (C) 2005 Dirk Behme | |
17 | * | |
18 | * This program is free software; you can redistribute it and/or modify | |
19 | * it under the terms of the GNU General Public License version 2 as | |
20 | * published by the Free Software Foundation. | |
21 | */ | |
22 | ||
23 | #include <linux/module.h> | |
24 | #include <linux/hrtimer.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/input.h> | |
27 | #include <linux/interrupt.h> | |
28 | #include <linux/i2c.h> | |
29 | #include <linux/i2c/tsc2007.h> | |
30 | ||
31 | #define TS_POLL_DELAY (10 * 1000) /* ns delay before the first sample */ | |
32 | #define TS_POLL_PERIOD (5 * 1000) /* ns delay between samples */ | |
33 | ||
34 | #define TSC2007_MEASURE_TEMP0 (0x0 << 4) | |
35 | #define TSC2007_MEASURE_AUX (0x2 << 4) | |
36 | #define TSC2007_MEASURE_TEMP1 (0x4 << 4) | |
37 | #define TSC2007_ACTIVATE_XN (0x8 << 4) | |
38 | #define TSC2007_ACTIVATE_YN (0x9 << 4) | |
39 | #define TSC2007_ACTIVATE_YP_XN (0xa << 4) | |
40 | #define TSC2007_SETUP (0xb << 4) | |
41 | #define TSC2007_MEASURE_X (0xc << 4) | |
42 | #define TSC2007_MEASURE_Y (0xd << 4) | |
43 | #define TSC2007_MEASURE_Z1 (0xe << 4) | |
44 | #define TSC2007_MEASURE_Z2 (0xf << 4) | |
45 | ||
46 | #define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2) | |
47 | #define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2) | |
48 | #define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2) | |
49 | #define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2) | |
50 | ||
51 | #define TSC2007_12BIT (0x0 << 1) | |
52 | #define TSC2007_8BIT (0x1 << 1) | |
53 | ||
54 | #define MAX_12BIT ((1 << 12) - 1) | |
55 | ||
56 | #define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0) | |
57 | ||
58 | #define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y) | |
59 | #define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1) | |
60 | #define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2) | |
61 | #define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X) | |
62 | #define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN) | |
63 | ||
64 | struct ts_event { | |
65 | u16 x; | |
66 | u16 y; | |
67 | u16 z1, z2; | |
68 | }; | |
69 | ||
70 | struct tsc2007 { | |
71 | struct input_dev *input; | |
72 | char phys[32]; | |
73 | struct hrtimer timer; | |
74 | struct ts_event tc; | |
75 | ||
76 | struct i2c_client *client; | |
77 | ||
78 | spinlock_t lock; | |
79 | ||
80 | u16 model; | |
81 | u16 x_plate_ohms; | |
82 | ||
83 | unsigned pendown; | |
84 | int irq; | |
85 | ||
86 | int (*get_pendown_state)(void); | |
87 | void (*clear_penirq)(void); | |
88 | }; | |
89 | ||
90 | static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd) | |
91 | { | |
92 | s32 data; | |
93 | u16 val; | |
94 | ||
95 | data = i2c_smbus_read_word_data(tsc->client, cmd); | |
96 | if (data < 0) { | |
97 | dev_err(&tsc->client->dev, "i2c io error: %d\n", data); | |
98 | return data; | |
99 | } | |
100 | ||
101 | /* The protocol and raw data format from i2c interface: | |
102 | * S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P | |
103 | * Where DataLow has [D11-D4], DataHigh has [D3-D0 << 4 | Dummy 4bit]. | |
104 | */ | |
105 | val = swab16(data) >> 4; | |
106 | ||
107 | dev_dbg(&tsc->client->dev, "data: 0x%x, val: 0x%x\n", data, val); | |
108 | ||
109 | return val; | |
110 | } | |
111 | ||
112 | static void tsc2007_send_event(void *tsc) | |
113 | { | |
114 | struct tsc2007 *ts = tsc; | |
115 | u32 rt; | |
116 | u16 x, y, z1, z2; | |
117 | ||
118 | x = ts->tc.x; | |
119 | y = ts->tc.y; | |
120 | z1 = ts->tc.z1; | |
121 | z2 = ts->tc.z2; | |
122 | ||
123 | /* range filtering */ | |
124 | if (x == MAX_12BIT) | |
125 | x = 0; | |
126 | ||
127 | if (likely(x && z1)) { | |
128 | /* compute touch pressure resistance using equation #1 */ | |
129 | rt = z2; | |
130 | rt -= z1; | |
131 | rt *= x; | |
132 | rt *= ts->x_plate_ohms; | |
133 | rt /= z1; | |
134 | rt = (rt + 2047) >> 12; | |
135 | } else | |
136 | rt = 0; | |
137 | ||
138 | /* Sample found inconsistent by debouncing or pressure is beyond | |
139 | * the maximum. Don't report it to user space, repeat at least | |
140 | * once more the measurement | |
141 | */ | |
142 | if (rt > MAX_12BIT) { | |
143 | dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt); | |
144 | ||
145 | hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), | |
146 | HRTIMER_MODE_REL); | |
147 | return; | |
148 | } | |
149 | ||
150 | /* NOTE: We can't rely on the pressure to determine the pen down | |
151 | * state, even this controller has a pressure sensor. The pressure | |
152 | * value can fluctuate for quite a while after lifting the pen and | |
153 | * in some cases may not even settle at the expected value. | |
154 | * | |
155 | * The only safe way to check for the pen up condition is in the | |
156 | * timer by reading the pen signal state (it's a GPIO _and_ IRQ). | |
157 | */ | |
158 | if (rt) { | |
159 | struct input_dev *input = ts->input; | |
160 | ||
161 | if (!ts->pendown) { | |
162 | dev_dbg(&ts->client->dev, "DOWN\n"); | |
163 | ||
164 | input_report_key(input, BTN_TOUCH, 1); | |
165 | ts->pendown = 1; | |
166 | } | |
167 | ||
168 | input_report_abs(input, ABS_X, x); | |
169 | input_report_abs(input, ABS_Y, y); | |
170 | input_report_abs(input, ABS_PRESSURE, rt); | |
171 | ||
172 | input_sync(input); | |
173 | ||
174 | dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n", | |
175 | x, y, rt); | |
176 | } | |
177 | ||
178 | hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), | |
179 | HRTIMER_MODE_REL); | |
180 | } | |
181 | ||
182 | static int tsc2007_read_values(struct tsc2007 *tsc) | |
183 | { | |
184 | /* y- still on; turn on only y+ (and ADC) */ | |
185 | tsc->tc.y = tsc2007_xfer(tsc, READ_Y); | |
186 | ||
187 | /* turn y- off, x+ on, then leave in lowpower */ | |
188 | tsc->tc.x = tsc2007_xfer(tsc, READ_X); | |
189 | ||
190 | /* turn y+ off, x- on; we'll use formula #1 */ | |
191 | tsc->tc.z1 = tsc2007_xfer(tsc, READ_Z1); | |
192 | tsc->tc.z2 = tsc2007_xfer(tsc, READ_Z2); | |
193 | ||
194 | /* power down */ | |
195 | tsc2007_xfer(tsc, PWRDOWN); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static enum hrtimer_restart tsc2007_timer(struct hrtimer *handle) | |
201 | { | |
202 | struct tsc2007 *ts = container_of(handle, struct tsc2007, timer); | |
203 | ||
204 | spin_lock_irq(&ts->lock); | |
205 | ||
206 | if (unlikely(!ts->get_pendown_state() && ts->pendown)) { | |
207 | struct input_dev *input = ts->input; | |
208 | ||
209 | dev_dbg(&ts->client->dev, "UP\n"); | |
210 | ||
211 | input_report_key(input, BTN_TOUCH, 0); | |
212 | input_report_abs(input, ABS_PRESSURE, 0); | |
213 | input_sync(input); | |
214 | ||
215 | ts->pendown = 0; | |
216 | enable_irq(ts->irq); | |
217 | } else { | |
218 | /* pen is still down, continue with the measurement */ | |
219 | dev_dbg(&ts->client->dev, "pen is still down\n"); | |
220 | ||
221 | tsc2007_read_values(ts); | |
222 | tsc2007_send_event(ts); | |
223 | } | |
224 | ||
225 | spin_unlock_irq(&ts->lock); | |
226 | ||
227 | return HRTIMER_NORESTART; | |
228 | } | |
229 | ||
230 | static irqreturn_t tsc2007_irq(int irq, void *handle) | |
231 | { | |
232 | struct tsc2007 *ts = handle; | |
233 | unsigned long flags; | |
234 | ||
235 | spin_lock_irqsave(&ts->lock, flags); | |
236 | ||
237 | if (likely(ts->get_pendown_state())) { | |
238 | disable_irq(ts->irq); | |
239 | hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_DELAY), | |
240 | HRTIMER_MODE_REL); | |
241 | } | |
242 | ||
243 | if (ts->clear_penirq) | |
244 | ts->clear_penirq(); | |
245 | ||
246 | spin_unlock_irqrestore(&ts->lock, flags); | |
247 | ||
248 | return IRQ_HANDLED; | |
249 | } | |
250 | ||
251 | static int tsc2007_probe(struct i2c_client *client, | |
252 | const struct i2c_device_id *id) | |
253 | { | |
254 | struct tsc2007 *ts; | |
255 | struct tsc2007_platform_data *pdata = pdata = client->dev.platform_data; | |
256 | struct input_dev *input_dev; | |
257 | int err; | |
258 | ||
259 | if (!pdata) { | |
260 | dev_err(&client->dev, "platform data is required!\n"); | |
261 | return -EINVAL; | |
262 | } | |
263 | ||
264 | if (!i2c_check_functionality(client->adapter, | |
265 | I2C_FUNC_SMBUS_READ_WORD_DATA)) | |
266 | return -EIO; | |
267 | ||
268 | ts = kzalloc(sizeof(struct tsc2007), GFP_KERNEL); | |
269 | input_dev = input_allocate_device(); | |
270 | if (!ts || !input_dev) { | |
271 | err = -ENOMEM; | |
272 | goto err_free_mem; | |
273 | } | |
274 | ||
275 | ts->client = client; | |
276 | i2c_set_clientdata(client, ts); | |
277 | ||
278 | ts->input = input_dev; | |
279 | ||
280 | hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | |
281 | ts->timer.function = tsc2007_timer; | |
282 | ||
283 | spin_lock_init(&ts->lock); | |
284 | ||
285 | ts->model = pdata->model; | |
286 | ts->x_plate_ohms = pdata->x_plate_ohms; | |
287 | ts->get_pendown_state = pdata->get_pendown_state; | |
288 | ts->clear_penirq = pdata->clear_penirq; | |
289 | ||
290 | pdata->init_platform_hw(); | |
291 | ||
292 | snprintf(ts->phys, sizeof(ts->phys), "%s/input0", client->dev.bus_id); | |
293 | ||
294 | input_dev->name = "TSC2007 Touchscreen"; | |
295 | input_dev->phys = ts->phys; | |
296 | input_dev->id.bustype = BUS_I2C; | |
297 | ||
298 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | |
299 | input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | |
300 | ||
301 | input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0); | |
302 | input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0); | |
303 | input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0); | |
304 | ||
305 | tsc2007_read_values(ts); | |
306 | ||
307 | ts->irq = client->irq; | |
308 | ||
309 | err = request_irq(ts->irq, tsc2007_irq, 0, | |
310 | client->dev.driver->name, ts); | |
311 | if (err < 0) { | |
312 | dev_err(&client->dev, "irq %d busy?\n", ts->irq); | |
313 | goto err_free_mem; | |
314 | } | |
315 | ||
316 | err = input_register_device(input_dev); | |
317 | if (err) | |
318 | goto err_free_irq; | |
319 | ||
320 | dev_info(&client->dev, "registered with irq (%d)\n", ts->irq); | |
321 | ||
322 | return 0; | |
323 | ||
324 | err_free_irq: | |
325 | free_irq(ts->irq, ts); | |
326 | hrtimer_cancel(&ts->timer); | |
327 | err_free_mem: | |
328 | input_free_device(input_dev); | |
329 | kfree(ts); | |
330 | return err; | |
331 | } | |
332 | ||
333 | static int tsc2007_remove(struct i2c_client *client) | |
334 | { | |
335 | struct tsc2007 *ts = i2c_get_clientdata(client); | |
336 | struct tsc2007_platform_data *pdata; | |
337 | ||
338 | pdata = client->dev.platform_data; | |
339 | pdata->exit_platform_hw(); | |
340 | ||
341 | free_irq(ts->irq, ts); | |
342 | hrtimer_cancel(&ts->timer); | |
343 | input_unregister_device(ts->input); | |
344 | kfree(ts); | |
345 | ||
346 | return 0; | |
347 | } | |
348 | ||
349 | static struct i2c_device_id tsc2007_idtable[] = { | |
350 | { "tsc2007", 0 }, | |
351 | { } | |
352 | }; | |
353 | ||
354 | MODULE_DEVICE_TABLE(i2c, tsc2007_idtable); | |
355 | ||
356 | static struct i2c_driver tsc2007_driver = { | |
357 | .driver = { | |
358 | .owner = THIS_MODULE, | |
359 | .name = "tsc2007" | |
360 | }, | |
361 | .id_table = tsc2007_idtable, | |
362 | .probe = tsc2007_probe, | |
363 | .remove = tsc2007_remove, | |
364 | }; | |
365 | ||
366 | static int __init tsc2007_init(void) | |
367 | { | |
368 | return i2c_add_driver(&tsc2007_driver); | |
369 | } | |
370 | ||
371 | static void __exit tsc2007_exit(void) | |
372 | { | |
373 | i2c_del_driver(&tsc2007_driver); | |
374 | } | |
375 | ||
376 | module_init(tsc2007_init); | |
377 | module_exit(tsc2007_exit); | |
378 | ||
379 | MODULE_AUTHOR("Kwangwoo Lee <kwlee@mtekvision.com>"); | |
380 | MODULE_DESCRIPTION("TSC2007 TouchScreen Driver"); | |
381 | MODULE_LICENSE("GPL"); |