Commit 43c4d13e901a8f37d9abbd410f093ebe885b5322
Committed by
Dmitry Torokhov
1 parent
07b8481d4a
Exists in
master
and in
20 other branches
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>
Showing 5 changed files with 990 additions and 0 deletions Side-by-side Diff
Documentation/input/edt-ft5x06.txt
1 | +EDT ft5x06 based Polytouch devices | |
2 | +---------------------------------- | |
3 | + | |
4 | +The edt-ft5x06 driver is useful for the EDT "Polytouch" family of capacitive | |
5 | +touch screens. Note that it is *not* suitable for other devices based on the | |
6 | +focaltec ft5x06 devices, since they contain vendor-specific firmware. In | |
7 | +particular this driver is not suitable for the Nook tablet. | |
8 | + | |
9 | +It has been tested with the following devices: | |
10 | + * EP0350M06 | |
11 | + * EP0430M06 | |
12 | + * EP0570M06 | |
13 | + * EP0700M06 | |
14 | + | |
15 | +The driver allows configuration of the touch screen via a set of sysfs files: | |
16 | + | |
17 | +/sys/class/input/eventX/device/device/threshold: | |
18 | + allows setting the "click"-threshold in the range from 20 to 80. | |
19 | + | |
20 | +/sys/class/input/eventX/device/device/gain: | |
21 | + allows setting the sensitivity in the range from 0 to 31. Note that | |
22 | + lower values indicate higher sensitivity. | |
23 | + | |
24 | +/sys/class/input/eventX/device/device/offset: | |
25 | + allows setting the edge compensation in the range from 0 to 31. | |
26 | + | |
27 | +/sys/class/input/eventX/device/device/report_rate: | |
28 | + allows setting the report rate in the range from 3 to 14. | |
29 | + | |
30 | + | |
31 | +For debugging purposes the driver provides a few files in the debug | |
32 | +filesystem (if available in the kernel). In /sys/kernel/debug/edt_ft5x06 | |
33 | +you'll find the following files: | |
34 | + | |
35 | +num_x, num_y: | |
36 | + (readonly) contains the number of sensor fields in X- and | |
37 | + Y-direction. | |
38 | + | |
39 | +mode: | |
40 | + allows switching the sensor between "factory mode" and "operation | |
41 | + mode" by writing "1" or "0" to it. In factory mode (1) it is | |
42 | + possible to get the raw data from the sensor. Note that in factory | |
43 | + mode regular events don't get delivered and the options described | |
44 | + above are unavailable. | |
45 | + | |
46 | +raw_data: | |
47 | + contains num_x * num_y big endian 16 bit values describing the raw | |
48 | + values for each sensor field. Note that each read() call on this | |
49 | + files triggers a new readout. It is recommended to provide a buffer | |
50 | + big enough to contain num_x * num_y * 2 bytes. | |
51 | + | |
52 | +Note that reading raw_data gives a I/O error when the device is not in factory | |
53 | +mode. The same happens when reading/writing to the parameter files when the | |
54 | +device is not in regular operation mode. |
drivers/input/touchscreen/Kconfig
... | ... | @@ -472,6 +472,19 @@ |
472 | 472 | To compile this driver as a module, choose M here: the |
473 | 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 | 488 | config TOUCHSCREEN_MIGOR |
476 | 489 | tristate "Renesas MIGO-R touchscreen" |
477 | 490 | depends on SH_MIGOR && I2C |
drivers/input/touchscreen/Makefile
... | ... | @@ -24,6 +24,7 @@ |
24 | 24 | obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o |
25 | 25 | obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o |
26 | 26 | obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o |
27 | +obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06) += edt-ft5x06.o | |
27 | 28 | obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o |
28 | 29 | obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o |
29 | 30 | obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o |
drivers/input/touchscreen/edt-ft5x06.c
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"); |
include/linux/input/edt-ft5x06.h
1 | +#ifndef _EDT_FT5X06_H | |
2 | +#define _EDT_FT5X06_H | |
3 | + | |
4 | +/* | |
5 | + * Copyright (c) 2012 Simon Budig, <simon.budig@kernelconcepts.de> | |
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 | + | |
12 | +struct edt_ft5x06_platform_data { | |
13 | + int irq_pin; | |
14 | + int reset_pin; | |
15 | + | |
16 | + /* startup defaults for operational parameters */ | |
17 | + bool use_parameters; | |
18 | + u8 gain; | |
19 | + u8 threshold; | |
20 | + u8 offset; | |
21 | + u8 report_rate; | |
22 | +}; | |
23 | + | |
24 | +#endif /* _EDT_FT5X06_H */ |