diff options
author | Simon Budig <simon.budig@kernelconcepts.de> | 2012-07-25 02:29:36 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-07-25 02:55:03 -0400 |
commit | 43c4d13e901a8f37d9abbd410f093ebe885b5322 (patch) | |
tree | 80ba618fd4ec2a5e07ed25ed803446f42e19b038 /drivers/input | |
parent | 07b8481d4aff73d6f451f25e74ea10240ff5131e (diff) |
Input: add driver for FT5x06 based EDT displays
This is a driver for the EDT "Polytouch" family of touch controllers
based on the FocalTech FT5x06 line of chips.
Signed-off-by: Simon Budig <simon.budig@kernelconcepts.de>
Reviewed-by: Henrik Rydberg <rydberg@euromail.se>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 13 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/edt-ft5x06.c | 898 |
3 files changed, 912 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 73bd2f6b82ec..1ba232cbc09d 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig | |||
@@ -472,6 +472,19 @@ config TOUCHSCREEN_PENMOUNT | |||
472 | To compile this driver as a module, choose M here: the | 472 | To compile this driver as a module, choose M here: the |
473 | module will be called penmount. | 473 | module will be called penmount. |
474 | 474 | ||
475 | config TOUCHSCREEN_EDT_FT5X06 | ||
476 | tristate "EDT FocalTech FT5x06 I2C Touchscreen support" | ||
477 | depends on I2C | ||
478 | help | ||
479 | Say Y here if you have an EDT "Polytouch" touchscreen based | ||
480 | on the FocalTech FT5x06 family of controllers connected to | ||
481 | your system. | ||
482 | |||
483 | If unsure, say N. | ||
484 | |||
485 | To compile this driver as a module, choose M here: the | ||
486 | module will be called edt-ft5x06. | ||
487 | |||
475 | config TOUCHSCREEN_MIGOR | 488 | config TOUCHSCREEN_MIGOR |
476 | tristate "Renesas MIGO-R touchscreen" | 489 | tristate "Renesas MIGO-R touchscreen" |
477 | depends on SH_MIGOR && I2C | 490 | depends on SH_MIGOR && I2C |
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 5920c60f999d..178eb128d90f 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile | |||
@@ -24,6 +24,7 @@ obj-$(CONFIG_TOUCHSCREEN_CYTTSP_SPI) += cyttsp_spi.o | |||
24 | obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o | 24 | obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o |
25 | obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o | 25 | obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o |
26 | obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o | 26 | obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o |
27 | obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06) += edt-ft5x06.o | ||
27 | obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o | 28 | obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o |
28 | obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o | 29 | obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o |
29 | obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o | 30 | obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o |
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c new file mode 100644 index 000000000000..9afc777a40a7 --- /dev/null +++ b/drivers/input/touchscreen/edt-ft5x06.c | |||
@@ -0,0 +1,898 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de> | ||
3 | * | ||
4 | * This software is licensed under the terms of the GNU General Public | ||
5 | * License version 2, as published by the Free Software Foundation, and | ||
6 | * may be copied, distributed, and modified under those terms. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public | ||
14 | * License along with this library; if not, write to the Free Software | ||
15 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
16 | */ | ||
17 | |||
18 | /* | ||
19 | * This is a driver for the EDT "Polytouch" family of touch controllers | ||
20 | * based on the FocalTech FT5x06 line of chips. | ||
21 | * | ||
22 | * Development of this driver has been sponsored by Glyn: | ||
23 | * http://www.glyn.com/Products/Displays | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/ratelimit.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | #include <linux/input.h> | ||
30 | #include <linux/i2c.h> | ||
31 | #include <linux/uaccess.h> | ||
32 | #include <linux/delay.h> | ||
33 | #include <linux/debugfs.h> | ||
34 | #include <linux/slab.h> | ||
35 | #include <linux/gpio.h> | ||
36 | #include <linux/input/mt.h> | ||
37 | #include <linux/input/edt-ft5x06.h> | ||
38 | |||
39 | #define MAX_SUPPORT_POINTS 5 | ||
40 | |||
41 | #define WORK_REGISTER_THRESHOLD 0x00 | ||
42 | #define WORK_REGISTER_REPORT_RATE 0x08 | ||
43 | #define WORK_REGISTER_GAIN 0x30 | ||
44 | #define WORK_REGISTER_OFFSET 0x31 | ||
45 | #define WORK_REGISTER_NUM_X 0x33 | ||
46 | #define WORK_REGISTER_NUM_Y 0x34 | ||
47 | |||
48 | #define WORK_REGISTER_OPMODE 0x3c | ||
49 | #define FACTORY_REGISTER_OPMODE 0x01 | ||
50 | |||
51 | #define TOUCH_EVENT_DOWN 0x00 | ||
52 | #define TOUCH_EVENT_UP 0x01 | ||
53 | #define TOUCH_EVENT_ON 0x02 | ||
54 | #define TOUCH_EVENT_RESERVED 0x03 | ||
55 | |||
56 | #define EDT_NAME_LEN 23 | ||
57 | #define EDT_SWITCH_MODE_RETRIES 10 | ||
58 | #define EDT_SWITCH_MODE_DELAY 5 /* msec */ | ||
59 | #define EDT_RAW_DATA_RETRIES 100 | ||
60 | #define EDT_RAW_DATA_DELAY 1 /* msec */ | ||
61 | |||
62 | struct edt_ft5x06_ts_data { | ||
63 | struct i2c_client *client; | ||
64 | struct input_dev *input; | ||
65 | u16 num_x; | ||
66 | u16 num_y; | ||
67 | |||
68 | #if defined(CONFIG_DEBUG_FS) | ||
69 | struct dentry *debug_dir; | ||
70 | u8 *raw_buffer; | ||
71 | size_t raw_bufsize; | ||
72 | #endif | ||
73 | |||
74 | struct mutex mutex; | ||
75 | bool factory_mode; | ||
76 | int threshold; | ||
77 | int gain; | ||
78 | int offset; | ||
79 | int report_rate; | ||
80 | |||
81 | char name[EDT_NAME_LEN]; | ||
82 | }; | ||
83 | |||
84 | static int edt_ft5x06_ts_readwrite(struct i2c_client *client, | ||
85 | u16 wr_len, u8 *wr_buf, | ||
86 | u16 rd_len, u8 *rd_buf) | ||
87 | { | ||
88 | struct i2c_msg wrmsg[2]; | ||
89 | int i = 0; | ||
90 | int ret; | ||
91 | |||
92 | if (wr_len) { | ||
93 | wrmsg[i].addr = client->addr; | ||
94 | wrmsg[i].flags = 0; | ||
95 | wrmsg[i].len = wr_len; | ||
96 | wrmsg[i].buf = wr_buf; | ||
97 | i++; | ||
98 | } | ||
99 | if (rd_len) { | ||
100 | wrmsg[i].addr = client->addr; | ||
101 | wrmsg[i].flags = I2C_M_RD; | ||
102 | wrmsg[i].len = rd_len; | ||
103 | wrmsg[i].buf = rd_buf; | ||
104 | i++; | ||
105 | } | ||
106 | |||
107 | ret = i2c_transfer(client->adapter, wrmsg, i); | ||
108 | if (ret < 0) | ||
109 | return ret; | ||
110 | if (ret != i) | ||
111 | return -EIO; | ||
112 | |||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata, | ||
117 | u8 *buf, int buflen) | ||
118 | { | ||
119 | int i; | ||
120 | u8 crc = 0; | ||
121 | |||
122 | for (i = 0; i < buflen - 1; i++) | ||
123 | crc ^= buf[i]; | ||
124 | |||
125 | if (crc != buf[buflen-1]) { | ||
126 | dev_err_ratelimited(&tsdata->client->dev, | ||
127 | "crc error: 0x%02x expected, got 0x%02x\n", | ||
128 | crc, buf[buflen-1]); | ||
129 | return false; | ||
130 | } | ||
131 | |||
132 | return true; | ||
133 | } | ||
134 | |||
135 | static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) | ||
136 | { | ||
137 | struct edt_ft5x06_ts_data *tsdata = dev_id; | ||
138 | struct device *dev = &tsdata->client->dev; | ||
139 | u8 cmd = 0xf9; | ||
140 | u8 rdbuf[26]; | ||
141 | int i, type, x, y, id; | ||
142 | int error; | ||
143 | |||
144 | memset(rdbuf, 0, sizeof(rdbuf)); | ||
145 | |||
146 | error = edt_ft5x06_ts_readwrite(tsdata->client, | ||
147 | sizeof(cmd), &cmd, | ||
148 | sizeof(rdbuf), rdbuf); | ||
149 | if (error) { | ||
150 | dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n", | ||
151 | error); | ||
152 | goto out; | ||
153 | } | ||
154 | |||
155 | if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa || rdbuf[2] != 26) { | ||
156 | dev_err_ratelimited(dev, "Unexpected header: %02x%02x%02x!\n", | ||
157 | rdbuf[0], rdbuf[1], rdbuf[2]); | ||
158 | goto out; | ||
159 | } | ||
160 | |||
161 | if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, 26)) | ||
162 | goto out; | ||
163 | |||
164 | for (i = 0; i < MAX_SUPPORT_POINTS; i++) { | ||
165 | u8 *buf = &rdbuf[i * 4 + 5]; | ||
166 | bool down; | ||
167 | |||
168 | type = buf[0] >> 6; | ||
169 | /* ignore Reserved events */ | ||
170 | if (type == TOUCH_EVENT_RESERVED) | ||
171 | continue; | ||
172 | |||
173 | x = ((buf[0] << 8) | buf[1]) & 0x0fff; | ||
174 | y = ((buf[2] << 8) | buf[3]) & 0x0fff; | ||
175 | id = (buf[2] >> 4) & 0x0f; | ||
176 | down = (type != TOUCH_EVENT_UP); | ||
177 | |||
178 | input_mt_slot(tsdata->input, id); | ||
179 | input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down); | ||
180 | |||
181 | if (!down) | ||
182 | continue; | ||
183 | |||
184 | input_report_abs(tsdata->input, ABS_MT_POSITION_X, x); | ||
185 | input_report_abs(tsdata->input, ABS_MT_POSITION_Y, y); | ||
186 | } | ||
187 | |||
188 | input_mt_report_pointer_emulation(tsdata->input, true); | ||
189 | input_sync(tsdata->input); | ||
190 | |||
191 | out: | ||
192 | return IRQ_HANDLED; | ||
193 | } | ||
194 | |||
195 | static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata, | ||
196 | u8 addr, u8 value) | ||
197 | { | ||
198 | u8 wrbuf[4]; | ||
199 | |||
200 | wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc; | ||
201 | wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f; | ||
202 | wrbuf[2] = value; | ||
203 | wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2]; | ||
204 | |||
205 | return edt_ft5x06_ts_readwrite(tsdata->client, 4, wrbuf, 0, NULL); | ||
206 | } | ||
207 | |||
208 | static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata, | ||
209 | u8 addr) | ||
210 | { | ||
211 | u8 wrbuf[2], rdbuf[2]; | ||
212 | int error; | ||
213 | |||
214 | wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc; | ||
215 | wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f; | ||
216 | wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40; | ||
217 | |||
218 | error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2, rdbuf); | ||
219 | if (error) | ||
220 | return error; | ||
221 | |||
222 | if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) { | ||
223 | dev_err(&tsdata->client->dev, | ||
224 | "crc error: 0x%02x expected, got 0x%02x\n", | ||
225 | wrbuf[0] ^ wrbuf[1] ^ rdbuf[0], rdbuf[1]); | ||
226 | return -EIO; | ||
227 | } | ||
228 | |||
229 | return rdbuf[0]; | ||
230 | } | ||
231 | |||
232 | struct edt_ft5x06_attribute { | ||
233 | struct device_attribute dattr; | ||
234 | size_t field_offset; | ||
235 | u8 limit_low; | ||
236 | u8 limit_high; | ||
237 | u8 addr; | ||
238 | }; | ||
239 | |||
240 | #define EDT_ATTR(_field, _mode, _addr, _limit_low, _limit_high) \ | ||
241 | struct edt_ft5x06_attribute edt_ft5x06_attr_##_field = { \ | ||
242 | .dattr = __ATTR(_field, _mode, \ | ||
243 | edt_ft5x06_setting_show, \ | ||
244 | edt_ft5x06_setting_store), \ | ||
245 | .field_offset = \ | ||
246 | offsetof(struct edt_ft5x06_ts_data, _field), \ | ||
247 | .limit_low = _limit_low, \ | ||
248 | .limit_high = _limit_high, \ | ||
249 | .addr = _addr, \ | ||
250 | } | ||
251 | |||
252 | static ssize_t edt_ft5x06_setting_show(struct device *dev, | ||
253 | struct device_attribute *dattr, | ||
254 | char *buf) | ||
255 | { | ||
256 | struct i2c_client *client = to_i2c_client(dev); | ||
257 | struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); | ||
258 | struct edt_ft5x06_attribute *attr = | ||
259 | container_of(dattr, struct edt_ft5x06_attribute, dattr); | ||
260 | u8 *field = (u8 *)((char *)tsdata + attr->field_offset); | ||
261 | int val; | ||
262 | size_t count = 0; | ||
263 | int error = 0; | ||
264 | |||
265 | mutex_lock(&tsdata->mutex); | ||
266 | |||
267 | if (tsdata->factory_mode) { | ||
268 | error = -EIO; | ||
269 | goto out; | ||
270 | } | ||
271 | |||
272 | val = edt_ft5x06_register_read(tsdata, attr->addr); | ||
273 | if (val < 0) { | ||
274 | error = val; | ||
275 | dev_err(&tsdata->client->dev, | ||
276 | "Failed to fetch attribute %s, error %d\n", | ||
277 | dattr->attr.name, error); | ||
278 | goto out; | ||
279 | } | ||
280 | |||
281 | if (val != *field) { | ||
282 | dev_warn(&tsdata->client->dev, | ||
283 | "%s: read (%d) and stored value (%d) differ\n", | ||
284 | dattr->attr.name, val, *field); | ||
285 | *field = val; | ||
286 | } | ||
287 | |||
288 | count = scnprintf(buf, PAGE_SIZE, "%d\n", val); | ||
289 | out: | ||
290 | mutex_unlock(&tsdata->mutex); | ||
291 | return error ?: count; | ||
292 | } | ||
293 | |||
294 | static ssize_t edt_ft5x06_setting_store(struct device *dev, | ||
295 | struct device_attribute *dattr, | ||
296 | const char *buf, size_t count) | ||
297 | { | ||
298 | struct i2c_client *client = to_i2c_client(dev); | ||
299 | struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); | ||
300 | struct edt_ft5x06_attribute *attr = | ||
301 | container_of(dattr, struct edt_ft5x06_attribute, dattr); | ||
302 | u8 *field = (u8 *)((char *)tsdata + attr->field_offset); | ||
303 | unsigned int val; | ||
304 | int error; | ||
305 | |||
306 | mutex_lock(&tsdata->mutex); | ||
307 | |||
308 | if (tsdata->factory_mode) { | ||
309 | error = -EIO; | ||
310 | goto out; | ||
311 | } | ||
312 | |||
313 | error = kstrtouint(buf, 0, &val); | ||
314 | if (error) | ||
315 | goto out; | ||
316 | |||
317 | if (val < attr->limit_low || val > attr->limit_high) { | ||
318 | error = -ERANGE; | ||
319 | goto out; | ||
320 | } | ||
321 | |||
322 | error = edt_ft5x06_register_write(tsdata, attr->addr, val); | ||
323 | if (error) { | ||
324 | dev_err(&tsdata->client->dev, | ||
325 | "Failed to update attribute %s, error: %d\n", | ||
326 | dattr->attr.name, error); | ||
327 | goto out; | ||
328 | } | ||
329 | |||
330 | *field = val; | ||
331 | |||
332 | out: | ||
333 | mutex_unlock(&tsdata->mutex); | ||
334 | return error ?: count; | ||
335 | } | ||
336 | |||
337 | static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN, 0, 31); | ||
338 | static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET, 0, 31); | ||
339 | static EDT_ATTR(threshold, S_IWUSR | S_IRUGO, | ||
340 | WORK_REGISTER_THRESHOLD, 20, 80); | ||
341 | static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, | ||
342 | WORK_REGISTER_REPORT_RATE, 3, 14); | ||
343 | |||
344 | static struct attribute *edt_ft5x06_attrs[] = { | ||
345 | &edt_ft5x06_attr_gain.dattr.attr, | ||
346 | &edt_ft5x06_attr_offset.dattr.attr, | ||
347 | &edt_ft5x06_attr_threshold.dattr.attr, | ||
348 | &edt_ft5x06_attr_report_rate.dattr.attr, | ||
349 | NULL | ||
350 | }; | ||
351 | |||
352 | static const struct attribute_group edt_ft5x06_attr_group = { | ||
353 | .attrs = edt_ft5x06_attrs, | ||
354 | }; | ||
355 | |||
356 | #ifdef CONFIG_DEBUG_FS | ||
357 | static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata) | ||
358 | { | ||
359 | struct i2c_client *client = tsdata->client; | ||
360 | int retries = EDT_SWITCH_MODE_RETRIES; | ||
361 | int ret; | ||
362 | int error; | ||
363 | |||
364 | disable_irq(client->irq); | ||
365 | |||
366 | if (!tsdata->raw_buffer) { | ||
367 | tsdata->raw_bufsize = tsdata->num_x * tsdata->num_y * | ||
368 | sizeof(u16); | ||
369 | tsdata->raw_buffer = kzalloc(tsdata->raw_bufsize, GFP_KERNEL); | ||
370 | if (!tsdata->raw_buffer) { | ||
371 | error = -ENOMEM; | ||
372 | goto err_out; | ||
373 | } | ||
374 | } | ||
375 | |||
376 | /* mode register is 0x3c when in the work mode */ | ||
377 | error = edt_ft5x06_register_write(tsdata, WORK_REGISTER_OPMODE, 0x03); | ||
378 | if (error) { | ||
379 | dev_err(&client->dev, | ||
380 | "failed to switch to factory mode, error %d\n", error); | ||
381 | goto err_out; | ||
382 | } | ||
383 | |||
384 | tsdata->factory_mode = true; | ||
385 | do { | ||
386 | mdelay(EDT_SWITCH_MODE_DELAY); | ||
387 | /* mode register is 0x01 when in factory mode */ | ||
388 | ret = edt_ft5x06_register_read(tsdata, FACTORY_REGISTER_OPMODE); | ||
389 | if (ret == 0x03) | ||
390 | break; | ||
391 | } while (--retries > 0); | ||
392 | |||
393 | if (retries == 0) { | ||
394 | dev_err(&client->dev, "not in factory mode after %dms.\n", | ||
395 | EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY); | ||
396 | error = -EIO; | ||
397 | goto err_out; | ||
398 | } | ||
399 | |||
400 | return 0; | ||
401 | |||
402 | err_out: | ||
403 | kfree(tsdata->raw_buffer); | ||
404 | tsdata->raw_buffer = NULL; | ||
405 | tsdata->factory_mode = false; | ||
406 | enable_irq(client->irq); | ||
407 | |||
408 | return error; | ||
409 | } | ||
410 | |||
411 | static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata) | ||
412 | { | ||
413 | struct i2c_client *client = tsdata->client; | ||
414 | int retries = EDT_SWITCH_MODE_RETRIES; | ||
415 | int ret; | ||
416 | int error; | ||
417 | |||
418 | /* mode register is 0x01 when in the factory mode */ | ||
419 | error = edt_ft5x06_register_write(tsdata, FACTORY_REGISTER_OPMODE, 0x1); | ||
420 | if (error) { | ||
421 | dev_err(&client->dev, | ||
422 | "failed to switch to work mode, error: %d\n", error); | ||
423 | return error; | ||
424 | } | ||
425 | |||
426 | tsdata->factory_mode = false; | ||
427 | |||
428 | do { | ||
429 | mdelay(EDT_SWITCH_MODE_DELAY); | ||
430 | /* mode register is 0x01 when in factory mode */ | ||
431 | ret = edt_ft5x06_register_read(tsdata, WORK_REGISTER_OPMODE); | ||
432 | if (ret == 0x01) | ||
433 | break; | ||
434 | } while (--retries > 0); | ||
435 | |||
436 | if (retries == 0) { | ||
437 | dev_err(&client->dev, "not in work mode after %dms.\n", | ||
438 | EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY); | ||
439 | tsdata->factory_mode = true; | ||
440 | return -EIO; | ||
441 | } | ||
442 | |||
443 | if (tsdata->raw_buffer) | ||
444 | kfree(tsdata->raw_buffer); | ||
445 | tsdata->raw_buffer = NULL; | ||
446 | |||
447 | /* restore parameters */ | ||
448 | edt_ft5x06_register_write(tsdata, WORK_REGISTER_THRESHOLD, | ||
449 | tsdata->threshold); | ||
450 | edt_ft5x06_register_write(tsdata, WORK_REGISTER_GAIN, | ||
451 | tsdata->gain); | ||
452 | edt_ft5x06_register_write(tsdata, WORK_REGISTER_OFFSET, | ||
453 | tsdata->offset); | ||
454 | edt_ft5x06_register_write(tsdata, WORK_REGISTER_REPORT_RATE, | ||
455 | tsdata->report_rate); | ||
456 | |||
457 | enable_irq(client->irq); | ||
458 | |||
459 | return 0; | ||
460 | } | ||
461 | |||
462 | static int edt_ft5x06_debugfs_mode_get(void *data, u64 *mode) | ||
463 | { | ||
464 | struct edt_ft5x06_ts_data *tsdata = data; | ||
465 | |||
466 | *mode = tsdata->factory_mode; | ||
467 | |||
468 | return 0; | ||
469 | }; | ||
470 | |||
471 | static int edt_ft5x06_debugfs_mode_set(void *data, u64 mode) | ||
472 | { | ||
473 | struct edt_ft5x06_ts_data *tsdata = data; | ||
474 | int retval = 0; | ||
475 | |||
476 | if (mode > 1) | ||
477 | return -ERANGE; | ||
478 | |||
479 | mutex_lock(&tsdata->mutex); | ||
480 | |||
481 | if (mode != tsdata->factory_mode) { | ||
482 | retval = mode ? edt_ft5x06_factory_mode(tsdata) : | ||
483 | edt_ft5x06_work_mode(tsdata); | ||
484 | } | ||
485 | |||
486 | mutex_unlock(&tsdata->mutex); | ||
487 | |||
488 | return retval; | ||
489 | }; | ||
490 | |||
491 | DEFINE_SIMPLE_ATTRIBUTE(debugfs_mode_fops, edt_ft5x06_debugfs_mode_get, | ||
492 | edt_ft5x06_debugfs_mode_set, "%llu\n"); | ||
493 | |||
494 | static int edt_ft5x06_debugfs_raw_data_open(struct inode *inode, | ||
495 | struct file *file) | ||
496 | { | ||
497 | file->private_data = inode->i_private; | ||
498 | |||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file, | ||
503 | char __user *buf, size_t count, loff_t *off) | ||
504 | { | ||
505 | struct edt_ft5x06_ts_data *tsdata = file->private_data; | ||
506 | struct i2c_client *client = tsdata->client; | ||
507 | int retries = EDT_RAW_DATA_RETRIES; | ||
508 | int val, i, error; | ||
509 | size_t read = 0; | ||
510 | int colbytes; | ||
511 | char wrbuf[3]; | ||
512 | u8 *rdbuf; | ||
513 | |||
514 | if (*off < 0 || *off >= tsdata->raw_bufsize) | ||
515 | return 0; | ||
516 | |||
517 | mutex_lock(&tsdata->mutex); | ||
518 | |||
519 | if (!tsdata->factory_mode || !tsdata->raw_buffer) { | ||
520 | error = -EIO; | ||
521 | goto out; | ||
522 | } | ||
523 | |||
524 | error = edt_ft5x06_register_write(tsdata, 0x08, 0x01); | ||
525 | if (error) { | ||
526 | dev_dbg(&client->dev, | ||
527 | "failed to write 0x08 register, error %d\n", error); | ||
528 | goto out; | ||
529 | } | ||
530 | |||
531 | do { | ||
532 | msleep(EDT_RAW_DATA_DELAY); | ||
533 | val = edt_ft5x06_register_read(tsdata, 0x08); | ||
534 | if (val < 1) | ||
535 | break; | ||
536 | } while (--retries > 0); | ||
537 | |||
538 | if (val < 0) { | ||
539 | error = val; | ||
540 | dev_dbg(&client->dev, | ||
541 | "failed to read 0x08 register, error %d\n", error); | ||
542 | goto out; | ||
543 | } | ||
544 | |||
545 | if (retries == 0) { | ||
546 | dev_dbg(&client->dev, | ||
547 | "timed out waiting for register to settle\n"); | ||
548 | error = -ETIMEDOUT; | ||
549 | goto out; | ||
550 | } | ||
551 | |||
552 | rdbuf = tsdata->raw_buffer; | ||
553 | colbytes = tsdata->num_y * sizeof(u16); | ||
554 | |||
555 | wrbuf[0] = 0xf5; | ||
556 | wrbuf[1] = 0x0e; | ||
557 | for (i = 0; i < tsdata->num_x; i++) { | ||
558 | wrbuf[2] = i; /* column index */ | ||
559 | error = edt_ft5x06_ts_readwrite(tsdata->client, | ||
560 | sizeof(wrbuf), wrbuf, | ||
561 | colbytes, rdbuf); | ||
562 | if (error) | ||
563 | goto out; | ||
564 | |||
565 | rdbuf += colbytes; | ||
566 | } | ||
567 | |||
568 | read = min_t(size_t, count, tsdata->raw_bufsize - *off); | ||
569 | error = copy_to_user(buf, tsdata->raw_buffer + *off, read); | ||
570 | if (!error) | ||
571 | *off += read; | ||
572 | out: | ||
573 | mutex_unlock(&tsdata->mutex); | ||
574 | return error ?: read; | ||
575 | }; | ||
576 | |||
577 | |||
578 | static const struct file_operations debugfs_raw_data_fops = { | ||
579 | .open = edt_ft5x06_debugfs_raw_data_open, | ||
580 | .read = edt_ft5x06_debugfs_raw_data_read, | ||
581 | }; | ||
582 | |||
583 | static void __devinit | ||
584 | edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, | ||
585 | const char *debugfs_name) | ||
586 | { | ||
587 | tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL); | ||
588 | if (!tsdata->debug_dir) | ||
589 | return; | ||
590 | |||
591 | debugfs_create_u16("num_x", S_IRUSR, tsdata->debug_dir, &tsdata->num_x); | ||
592 | debugfs_create_u16("num_y", S_IRUSR, tsdata->debug_dir, &tsdata->num_y); | ||
593 | |||
594 | debugfs_create_file("mode", S_IRUSR | S_IWUSR, | ||
595 | tsdata->debug_dir, tsdata, &debugfs_mode_fops); | ||
596 | debugfs_create_file("raw_data", S_IRUSR, | ||
597 | tsdata->debug_dir, tsdata, &debugfs_raw_data_fops); | ||
598 | } | ||
599 | |||
600 | static void __devexit | ||
601 | edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) | ||
602 | { | ||
603 | if (tsdata->debug_dir) | ||
604 | debugfs_remove_recursive(tsdata->debug_dir); | ||
605 | } | ||
606 | |||
607 | #else | ||
608 | |||
609 | static inline void | ||
610 | edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, | ||
611 | const char *debugfs_name) | ||
612 | { | ||
613 | } | ||
614 | |||
615 | static inline void | ||
616 | edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) | ||
617 | { | ||
618 | } | ||
619 | |||
620 | #endif /* CONFIG_DEBUGFS */ | ||
621 | |||
622 | |||
623 | |||
624 | static int __devinit edt_ft5x06_ts_reset(struct i2c_client *client, | ||
625 | int reset_pin) | ||
626 | { | ||
627 | int error; | ||
628 | |||
629 | if (gpio_is_valid(reset_pin)) { | ||
630 | /* this pulls reset down, enabling the low active reset */ | ||
631 | error = gpio_request_one(reset_pin, GPIOF_OUT_INIT_LOW, | ||
632 | "edt-ft5x06 reset"); | ||
633 | if (error) { | ||
634 | dev_err(&client->dev, | ||
635 | "Failed to request GPIO %d as reset pin, error %d\n", | ||
636 | reset_pin, error); | ||
637 | return error; | ||
638 | } | ||
639 | |||
640 | mdelay(50); | ||
641 | gpio_set_value(reset_pin, 1); | ||
642 | mdelay(100); | ||
643 | } | ||
644 | |||
645 | return 0; | ||
646 | } | ||
647 | |||
648 | static int __devinit edt_ft5x06_ts_identify(struct i2c_client *client, | ||
649 | char *model_name, | ||
650 | char *fw_version) | ||
651 | { | ||
652 | u8 rdbuf[EDT_NAME_LEN]; | ||
653 | char *p; | ||
654 | int error; | ||
655 | |||
656 | error = edt_ft5x06_ts_readwrite(client, 1, "\xbb", | ||
657 | EDT_NAME_LEN - 1, rdbuf); | ||
658 | if (error) | ||
659 | return error; | ||
660 | |||
661 | /* remove last '$' end marker */ | ||
662 | rdbuf[EDT_NAME_LEN - 1] = '\0'; | ||
663 | if (rdbuf[EDT_NAME_LEN - 2] == '$') | ||
664 | rdbuf[EDT_NAME_LEN - 2] = '\0'; | ||
665 | |||
666 | /* look for Model/Version separator */ | ||
667 | p = strchr(rdbuf, '*'); | ||
668 | if (p) | ||
669 | *p++ = '\0'; | ||
670 | |||
671 | strlcpy(model_name, rdbuf + 1, EDT_NAME_LEN); | ||
672 | strlcpy(fw_version, p ? p : "", EDT_NAME_LEN); | ||
673 | |||
674 | return 0; | ||
675 | } | ||
676 | |||
677 | #define EDT_ATTR_CHECKSET(name, reg) \ | ||
678 | if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \ | ||
679 | pdata->name <= edt_ft5x06_attr_##name.limit_high) \ | ||
680 | edt_ft5x06_register_write(tsdata, reg, pdata->name) | ||
681 | |||
682 | static void __devinit | ||
683 | edt_ft5x06_ts_get_defaults(struct edt_ft5x06_ts_data *tsdata, | ||
684 | const struct edt_ft5x06_platform_data *pdata) | ||
685 | { | ||
686 | if (!pdata->use_parameters) | ||
687 | return; | ||
688 | |||
689 | /* pick up defaults from the platform data */ | ||
690 | EDT_ATTR_CHECKSET(threshold, WORK_REGISTER_THRESHOLD); | ||
691 | EDT_ATTR_CHECKSET(gain, WORK_REGISTER_GAIN); | ||
692 | EDT_ATTR_CHECKSET(offset, WORK_REGISTER_OFFSET); | ||
693 | EDT_ATTR_CHECKSET(report_rate, WORK_REGISTER_REPORT_RATE); | ||
694 | } | ||
695 | |||
696 | static void __devinit | ||
697 | edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata) | ||
698 | { | ||
699 | tsdata->threshold = edt_ft5x06_register_read(tsdata, | ||
700 | WORK_REGISTER_THRESHOLD); | ||
701 | tsdata->gain = edt_ft5x06_register_read(tsdata, WORK_REGISTER_GAIN); | ||
702 | tsdata->offset = edt_ft5x06_register_read(tsdata, WORK_REGISTER_OFFSET); | ||
703 | tsdata->report_rate = edt_ft5x06_register_read(tsdata, | ||
704 | WORK_REGISTER_REPORT_RATE); | ||
705 | tsdata->num_x = edt_ft5x06_register_read(tsdata, WORK_REGISTER_NUM_X); | ||
706 | tsdata->num_y = edt_ft5x06_register_read(tsdata, WORK_REGISTER_NUM_Y); | ||
707 | } | ||
708 | |||
709 | static int __devinit edt_ft5x06_ts_probe(struct i2c_client *client, | ||
710 | const struct i2c_device_id *id) | ||
711 | { | ||
712 | const struct edt_ft5x06_platform_data *pdata = | ||
713 | client->dev.platform_data; | ||
714 | struct edt_ft5x06_ts_data *tsdata; | ||
715 | struct input_dev *input; | ||
716 | int error; | ||
717 | char fw_version[EDT_NAME_LEN]; | ||
718 | |||
719 | dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n"); | ||
720 | |||
721 | if (!pdata) { | ||
722 | dev_err(&client->dev, "no platform data?\n"); | ||
723 | return -EINVAL; | ||
724 | } | ||
725 | |||
726 | error = edt_ft5x06_ts_reset(client, pdata->reset_pin); | ||
727 | if (error) | ||
728 | return error; | ||
729 | |||
730 | if (gpio_is_valid(pdata->irq_pin)) { | ||
731 | error = gpio_request_one(pdata->irq_pin, | ||
732 | GPIOF_IN, "edt-ft5x06 irq"); | ||
733 | if (error) { | ||
734 | dev_err(&client->dev, | ||
735 | "Failed to request GPIO %d, error %d\n", | ||
736 | pdata->irq_pin, error); | ||
737 | return error; | ||
738 | } | ||
739 | } | ||
740 | |||
741 | tsdata = kzalloc(sizeof(*tsdata), GFP_KERNEL); | ||
742 | input = input_allocate_device(); | ||
743 | if (!tsdata || !input) { | ||
744 | dev_err(&client->dev, "failed to allocate driver data.\n"); | ||
745 | error = -ENOMEM; | ||
746 | goto err_free_mem; | ||
747 | } | ||
748 | |||
749 | mutex_init(&tsdata->mutex); | ||
750 | tsdata->client = client; | ||
751 | tsdata->input = input; | ||
752 | tsdata->factory_mode = false; | ||
753 | |||
754 | error = edt_ft5x06_ts_identify(client, tsdata->name, fw_version); | ||
755 | if (error) { | ||
756 | dev_err(&client->dev, "touchscreen probe failed\n"); | ||
757 | goto err_free_mem; | ||
758 | } | ||
759 | |||
760 | edt_ft5x06_ts_get_defaults(tsdata, pdata); | ||
761 | edt_ft5x06_ts_get_parameters(tsdata); | ||
762 | |||
763 | dev_dbg(&client->dev, | ||
764 | "Model \"%s\", Rev. \"%s\", %dx%d sensors\n", | ||
765 | tsdata->name, fw_version, tsdata->num_x, tsdata->num_y); | ||
766 | |||
767 | input->name = tsdata->name; | ||
768 | input->id.bustype = BUS_I2C; | ||
769 | input->dev.parent = &client->dev; | ||
770 | |||
771 | __set_bit(EV_SYN, input->evbit); | ||
772 | __set_bit(EV_KEY, input->evbit); | ||
773 | __set_bit(EV_ABS, input->evbit); | ||
774 | __set_bit(BTN_TOUCH, input->keybit); | ||
775 | input_set_abs_params(input, ABS_X, 0, tsdata->num_x * 64 - 1, 0, 0); | ||
776 | input_set_abs_params(input, ABS_Y, 0, tsdata->num_y * 64 - 1, 0, 0); | ||
777 | input_set_abs_params(input, ABS_MT_POSITION_X, | ||
778 | 0, tsdata->num_x * 64 - 1, 0, 0); | ||
779 | input_set_abs_params(input, ABS_MT_POSITION_Y, | ||
780 | 0, tsdata->num_y * 64 - 1, 0, 0); | ||
781 | error = input_mt_init_slots(input, MAX_SUPPORT_POINTS); | ||
782 | if (error) { | ||
783 | dev_err(&client->dev, "Unable to init MT slots.\n"); | ||
784 | goto err_free_mem; | ||
785 | } | ||
786 | |||
787 | input_set_drvdata(input, tsdata); | ||
788 | i2c_set_clientdata(client, tsdata); | ||
789 | |||
790 | error = request_threaded_irq(client->irq, NULL, edt_ft5x06_ts_isr, | ||
791 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | ||
792 | client->name, tsdata); | ||
793 | if (error) { | ||
794 | dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); | ||
795 | goto err_free_mem; | ||
796 | } | ||
797 | |||
798 | error = sysfs_create_group(&client->dev.kobj, &edt_ft5x06_attr_group); | ||
799 | if (error) | ||
800 | goto err_free_irq; | ||
801 | |||
802 | error = input_register_device(input); | ||
803 | if (error) | ||
804 | goto err_remove_attrs; | ||
805 | |||
806 | edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev)); | ||
807 | device_init_wakeup(&client->dev, 1); | ||
808 | |||
809 | dev_dbg(&client->dev, | ||
810 | "EDT FT5x06 initialized: IRQ pin %d, Reset pin %d.\n", | ||
811 | pdata->irq_pin, pdata->reset_pin); | ||
812 | |||
813 | return 0; | ||
814 | |||
815 | err_remove_attrs: | ||
816 | sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group); | ||
817 | err_free_irq: | ||
818 | free_irq(client->irq, tsdata); | ||
819 | err_free_mem: | ||
820 | input_free_device(input); | ||
821 | kfree(tsdata); | ||
822 | |||
823 | if (gpio_is_valid(pdata->irq_pin)) | ||
824 | gpio_free(pdata->irq_pin); | ||
825 | |||
826 | return error; | ||
827 | } | ||
828 | |||
829 | static int __devexit edt_ft5x06_ts_remove(struct i2c_client *client) | ||
830 | { | ||
831 | const struct edt_ft5x06_platform_data *pdata = | ||
832 | dev_get_platdata(&client->dev); | ||
833 | struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); | ||
834 | |||
835 | edt_ft5x06_ts_teardown_debugfs(tsdata); | ||
836 | sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group); | ||
837 | |||
838 | free_irq(client->irq, tsdata); | ||
839 | input_unregister_device(tsdata->input); | ||
840 | |||
841 | if (gpio_is_valid(pdata->irq_pin)) | ||
842 | gpio_free(pdata->irq_pin); | ||
843 | if (gpio_is_valid(pdata->reset_pin)) | ||
844 | gpio_free(pdata->reset_pin); | ||
845 | |||
846 | kfree(tsdata->raw_buffer); | ||
847 | kfree(tsdata); | ||
848 | |||
849 | return 0; | ||
850 | } | ||
851 | |||
852 | #ifdef CONFIG_PM_SLEEP | ||
853 | static int edt_ft5x06_ts_suspend(struct device *dev) | ||
854 | { | ||
855 | struct i2c_client *client = to_i2c_client(dev); | ||
856 | |||
857 | if (device_may_wakeup(dev)) | ||
858 | enable_irq_wake(client->irq); | ||
859 | |||
860 | return 0; | ||
861 | } | ||
862 | |||
863 | static int edt_ft5x06_ts_resume(struct device *dev) | ||
864 | { | ||
865 | struct i2c_client *client = to_i2c_client(dev); | ||
866 | |||
867 | if (device_may_wakeup(dev)) | ||
868 | disable_irq_wake(client->irq); | ||
869 | |||
870 | return 0; | ||
871 | } | ||
872 | #endif | ||
873 | |||
874 | static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops, | ||
875 | edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume); | ||
876 | |||
877 | static const struct i2c_device_id edt_ft5x06_ts_id[] = { | ||
878 | { "edt-ft5x06", 0 }, | ||
879 | { } | ||
880 | }; | ||
881 | MODULE_DEVICE_TABLE(i2c, edt_ft5x06_ts_id); | ||
882 | |||
883 | static struct i2c_driver edt_ft5x06_ts_driver = { | ||
884 | .driver = { | ||
885 | .owner = THIS_MODULE, | ||
886 | .name = "edt_ft5x06", | ||
887 | .pm = &edt_ft5x06_ts_pm_ops, | ||
888 | }, | ||
889 | .id_table = edt_ft5x06_ts_id, | ||
890 | .probe = edt_ft5x06_ts_probe, | ||
891 | .remove = __devexit_p(edt_ft5x06_ts_remove), | ||
892 | }; | ||
893 | |||
894 | module_i2c_driver(edt_ft5x06_ts_driver); | ||
895 | |||
896 | MODULE_AUTHOR("Simon Budig <simon.budig@kernelconcepts.de>"); | ||
897 | MODULE_DESCRIPTION("EDT FT5x06 I2C Touchscreen Driver"); | ||
898 | MODULE_LICENSE("GPL"); | ||