diff options
Diffstat (limited to 'drivers/input/touchscreen/wdt87xx_i2c.c')
-rw-r--r-- | drivers/input/touchscreen/wdt87xx_i2c.c | 1149 |
1 files changed, 1149 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/wdt87xx_i2c.c b/drivers/input/touchscreen/wdt87xx_i2c.c new file mode 100644 index 000000000000..fb92ae1c5fae --- /dev/null +++ b/drivers/input/touchscreen/wdt87xx_i2c.c | |||
@@ -0,0 +1,1149 @@ | |||
1 | /* | ||
2 | * Weida HiTech WDT87xx TouchScreen I2C driver | ||
3 | * | ||
4 | * Copyright (c) 2015 Weida Hi-Tech Co., Ltd. | ||
5 | * HN Chen <hn.chen@weidahitech.com> | ||
6 | * | ||
7 | * This software is licensed under the terms of the GNU General Public | ||
8 | * License, as published by the Free Software Foundation, and | ||
9 | * may be copied, distributed, and modified under those terms. | ||
10 | */ | ||
11 | |||
12 | #include <linux/i2c.h> | ||
13 | #include <linux/input.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/irq.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/firmware.h> | ||
21 | #include <linux/input/mt.h> | ||
22 | #include <linux/acpi.h> | ||
23 | #include <asm/unaligned.h> | ||
24 | |||
25 | #define WDT87XX_NAME "wdt87xx_i2c" | ||
26 | #define WDT87XX_DRV_VER "0.9.6" | ||
27 | #define WDT87XX_FW_NAME "wdt87xx_fw.bin" | ||
28 | #define WDT87XX_CFG_NAME "wdt87xx_cfg.bin" | ||
29 | |||
30 | #define MODE_ACTIVE 0x01 | ||
31 | #define MODE_READY 0x02 | ||
32 | #define MODE_IDLE 0x03 | ||
33 | #define MODE_SLEEP 0x04 | ||
34 | #define MODE_STOP 0xFF | ||
35 | |||
36 | #define WDT_MAX_FINGER 10 | ||
37 | #define WDT_RAW_BUF_COUNT 54 | ||
38 | #define WDT_V1_RAW_BUF_COUNT 74 | ||
39 | #define WDT_FIRMWARE_ID 0xa9e368f5 | ||
40 | |||
41 | #define PG_SIZE 0x1000 | ||
42 | #define MAX_RETRIES 3 | ||
43 | |||
44 | #define MAX_UNIT_AXIS 0x7FFF | ||
45 | |||
46 | #define PKT_READ_SIZE 72 | ||
47 | #define PKT_WRITE_SIZE 80 | ||
48 | |||
49 | /* the finger definition of the report event */ | ||
50 | #define FINGER_EV_OFFSET_ID 0 | ||
51 | #define FINGER_EV_OFFSET_X 1 | ||
52 | #define FINGER_EV_OFFSET_Y 3 | ||
53 | #define FINGER_EV_SIZE 5 | ||
54 | |||
55 | #define FINGER_EV_V1_OFFSET_ID 0 | ||
56 | #define FINGER_EV_V1_OFFSET_W 1 | ||
57 | #define FINGER_EV_V1_OFFSET_P 2 | ||
58 | #define FINGER_EV_V1_OFFSET_X 3 | ||
59 | #define FINGER_EV_V1_OFFSET_Y 5 | ||
60 | #define FINGER_EV_V1_SIZE 7 | ||
61 | |||
62 | /* The definition of a report packet */ | ||
63 | #define TOUCH_PK_OFFSET_REPORT_ID 0 | ||
64 | #define TOUCH_PK_OFFSET_EVENT 1 | ||
65 | #define TOUCH_PK_OFFSET_SCAN_TIME 51 | ||
66 | #define TOUCH_PK_OFFSET_FNGR_NUM 53 | ||
67 | |||
68 | #define TOUCH_PK_V1_OFFSET_REPORT_ID 0 | ||
69 | #define TOUCH_PK_V1_OFFSET_EVENT 1 | ||
70 | #define TOUCH_PK_V1_OFFSET_SCAN_TIME 71 | ||
71 | #define TOUCH_PK_V1_OFFSET_FNGR_NUM 73 | ||
72 | |||
73 | /* The definition of the controller parameters */ | ||
74 | #define CTL_PARAM_OFFSET_FW_ID 0 | ||
75 | #define CTL_PARAM_OFFSET_PLAT_ID 2 | ||
76 | #define CTL_PARAM_OFFSET_XMLS_ID1 4 | ||
77 | #define CTL_PARAM_OFFSET_XMLS_ID2 6 | ||
78 | #define CTL_PARAM_OFFSET_PHY_CH_X 8 | ||
79 | #define CTL_PARAM_OFFSET_PHY_CH_Y 10 | ||
80 | #define CTL_PARAM_OFFSET_PHY_X0 12 | ||
81 | #define CTL_PARAM_OFFSET_PHY_X1 14 | ||
82 | #define CTL_PARAM_OFFSET_PHY_Y0 16 | ||
83 | #define CTL_PARAM_OFFSET_PHY_Y1 18 | ||
84 | #define CTL_PARAM_OFFSET_PHY_W 22 | ||
85 | #define CTL_PARAM_OFFSET_PHY_H 24 | ||
86 | #define CTL_PARAM_OFFSET_FACTOR 32 | ||
87 | |||
88 | /* Communication commands */ | ||
89 | #define PACKET_SIZE 56 | ||
90 | #define VND_REQ_READ 0x06 | ||
91 | #define VND_READ_DATA 0x07 | ||
92 | #define VND_REQ_WRITE 0x08 | ||
93 | |||
94 | #define VND_CMD_START 0x00 | ||
95 | #define VND_CMD_STOP 0x01 | ||
96 | #define VND_CMD_RESET 0x09 | ||
97 | |||
98 | #define VND_CMD_ERASE 0x1A | ||
99 | |||
100 | #define VND_GET_CHECKSUM 0x66 | ||
101 | |||
102 | #define VND_SET_DATA 0x83 | ||
103 | #define VND_SET_COMMAND_DATA 0x84 | ||
104 | #define VND_SET_CHECKSUM_CALC 0x86 | ||
105 | #define VND_SET_CHECKSUM_LENGTH 0x87 | ||
106 | |||
107 | #define VND_CMD_SFLCK 0xFC | ||
108 | #define VND_CMD_SFUNL 0xFD | ||
109 | |||
110 | #define CMD_SFLCK_KEY 0xC39B | ||
111 | #define CMD_SFUNL_KEY 0x95DA | ||
112 | |||
113 | #define STRIDX_PLATFORM_ID 0x80 | ||
114 | #define STRIDX_PARAMETERS 0x81 | ||
115 | |||
116 | #define CMD_BUF_SIZE 8 | ||
117 | #define PKT_BUF_SIZE 64 | ||
118 | |||
119 | /* The definition of the command packet */ | ||
120 | #define CMD_REPORT_ID_OFFSET 0x0 | ||
121 | #define CMD_TYPE_OFFSET 0x1 | ||
122 | #define CMD_INDEX_OFFSET 0x2 | ||
123 | #define CMD_KEY_OFFSET 0x3 | ||
124 | #define CMD_LENGTH_OFFSET 0x4 | ||
125 | #define CMD_DATA_OFFSET 0x8 | ||
126 | |||
127 | /* The definition of firmware chunk tags */ | ||
128 | #define FOURCC_ID_RIFF 0x46464952 | ||
129 | #define FOURCC_ID_WHIF 0x46494857 | ||
130 | #define FOURCC_ID_FRMT 0x544D5246 | ||
131 | #define FOURCC_ID_FRWR 0x52575246 | ||
132 | #define FOURCC_ID_CNFG 0x47464E43 | ||
133 | |||
134 | #define CHUNK_ID_FRMT FOURCC_ID_FRMT | ||
135 | #define CHUNK_ID_FRWR FOURCC_ID_FRWR | ||
136 | #define CHUNK_ID_CNFG FOURCC_ID_CNFG | ||
137 | |||
138 | #define FW_FOURCC1_OFFSET 0 | ||
139 | #define FW_SIZE_OFFSET 4 | ||
140 | #define FW_FOURCC2_OFFSET 8 | ||
141 | #define FW_PAYLOAD_OFFSET 40 | ||
142 | |||
143 | #define FW_CHUNK_ID_OFFSET 0 | ||
144 | #define FW_CHUNK_SIZE_OFFSET 4 | ||
145 | #define FW_CHUNK_TGT_START_OFFSET 8 | ||
146 | #define FW_CHUNK_PAYLOAD_LEN_OFFSET 12 | ||
147 | #define FW_CHUNK_SRC_START_OFFSET 16 | ||
148 | #define FW_CHUNK_VERSION_OFFSET 20 | ||
149 | #define FW_CHUNK_ATTR_OFFSET 24 | ||
150 | #define FW_CHUNK_PAYLOAD_OFFSET 32 | ||
151 | |||
152 | /* Controller requires minimum 300us between commands */ | ||
153 | #define WDT_COMMAND_DELAY_MS 2 | ||
154 | #define WDT_FLASH_WRITE_DELAY_MS 4 | ||
155 | |||
156 | struct wdt87xx_sys_param { | ||
157 | u16 fw_id; | ||
158 | u16 plat_id; | ||
159 | u16 xmls_id1; | ||
160 | u16 xmls_id2; | ||
161 | u16 phy_ch_x; | ||
162 | u16 phy_ch_y; | ||
163 | u16 phy_w; | ||
164 | u16 phy_h; | ||
165 | u16 scaling_factor; | ||
166 | u32 max_x; | ||
167 | u32 max_y; | ||
168 | }; | ||
169 | |||
170 | struct wdt87xx_data { | ||
171 | struct i2c_client *client; | ||
172 | struct input_dev *input; | ||
173 | /* Mutex for fw update to prevent concurrent access */ | ||
174 | struct mutex fw_mutex; | ||
175 | struct wdt87xx_sys_param param; | ||
176 | u8 phys[32]; | ||
177 | }; | ||
178 | |||
179 | static int wdt87xx_i2c_xfer(struct i2c_client *client, | ||
180 | void *txdata, size_t txlen, | ||
181 | void *rxdata, size_t rxlen) | ||
182 | { | ||
183 | struct i2c_msg msgs[] = { | ||
184 | { | ||
185 | .addr = client->addr, | ||
186 | .flags = 0, | ||
187 | .len = txlen, | ||
188 | .buf = txdata, | ||
189 | }, | ||
190 | { | ||
191 | .addr = client->addr, | ||
192 | .flags = I2C_M_RD, | ||
193 | .len = rxlen, | ||
194 | .buf = rxdata, | ||
195 | }, | ||
196 | }; | ||
197 | int error; | ||
198 | int ret; | ||
199 | |||
200 | ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); | ||
201 | if (ret != ARRAY_SIZE(msgs)) { | ||
202 | error = ret < 0 ? ret : -EIO; | ||
203 | dev_err(&client->dev, "%s: i2c transfer failed: %d\n", | ||
204 | __func__, error); | ||
205 | return error; | ||
206 | } | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | static int wdt87xx_get_string(struct i2c_client *client, u8 str_idx, | ||
212 | u8 *buf, size_t len) | ||
213 | { | ||
214 | u8 tx_buf[] = { 0x22, 0x00, 0x13, 0x0E, str_idx, 0x23, 0x00 }; | ||
215 | u8 rx_buf[PKT_WRITE_SIZE]; | ||
216 | size_t rx_len = len + 2; | ||
217 | int error; | ||
218 | |||
219 | if (rx_len > sizeof(rx_buf)) | ||
220 | return -EINVAL; | ||
221 | |||
222 | error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf), | ||
223 | rx_buf, rx_len); | ||
224 | if (error) { | ||
225 | dev_err(&client->dev, "get string failed: %d\n", error); | ||
226 | return error; | ||
227 | } | ||
228 | |||
229 | if (rx_buf[1] != 0x03) { | ||
230 | dev_err(&client->dev, "unexpected response to get string: %d\n", | ||
231 | rx_buf[1]); | ||
232 | return -EINVAL; | ||
233 | } | ||
234 | |||
235 | rx_len = min_t(size_t, len, rx_buf[0]); | ||
236 | memcpy(buf, &rx_buf[2], rx_len); | ||
237 | |||
238 | mdelay(WDT_COMMAND_DELAY_MS); | ||
239 | |||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static int wdt87xx_get_feature(struct i2c_client *client, | ||
244 | u8 *buf, size_t buf_size) | ||
245 | { | ||
246 | u8 tx_buf[8]; | ||
247 | u8 rx_buf[PKT_WRITE_SIZE]; | ||
248 | size_t tx_len = 0; | ||
249 | size_t rx_len = buf_size + 2; | ||
250 | int error; | ||
251 | |||
252 | if (rx_len > sizeof(rx_buf)) | ||
253 | return -EINVAL; | ||
254 | |||
255 | /* Get feature command packet */ | ||
256 | tx_buf[tx_len++] = 0x22; | ||
257 | tx_buf[tx_len++] = 0x00; | ||
258 | if (buf[CMD_REPORT_ID_OFFSET] > 0xF) { | ||
259 | tx_buf[tx_len++] = 0x30; | ||
260 | tx_buf[tx_len++] = 0x02; | ||
261 | tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET]; | ||
262 | } else { | ||
263 | tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET]; | ||
264 | tx_buf[tx_len++] = 0x02; | ||
265 | } | ||
266 | tx_buf[tx_len++] = 0x23; | ||
267 | tx_buf[tx_len++] = 0x00; | ||
268 | |||
269 | error = wdt87xx_i2c_xfer(client, tx_buf, tx_len, rx_buf, rx_len); | ||
270 | if (error) { | ||
271 | dev_err(&client->dev, "get feature failed: %d\n", error); | ||
272 | return error; | ||
273 | } | ||
274 | |||
275 | rx_len = min_t(size_t, buf_size, get_unaligned_le16(rx_buf)); | ||
276 | memcpy(buf, &rx_buf[2], rx_len); | ||
277 | |||
278 | mdelay(WDT_COMMAND_DELAY_MS); | ||
279 | |||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | static int wdt87xx_set_feature(struct i2c_client *client, | ||
284 | const u8 *buf, size_t buf_size) | ||
285 | { | ||
286 | u8 tx_buf[PKT_WRITE_SIZE]; | ||
287 | int tx_len = 0; | ||
288 | int error; | ||
289 | |||
290 | /* Set feature command packet */ | ||
291 | tx_buf[tx_len++] = 0x22; | ||
292 | tx_buf[tx_len++] = 0x00; | ||
293 | if (buf[CMD_REPORT_ID_OFFSET] > 0xF) { | ||
294 | tx_buf[tx_len++] = 0x30; | ||
295 | tx_buf[tx_len++] = 0x03; | ||
296 | tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET]; | ||
297 | } else { | ||
298 | tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET]; | ||
299 | tx_buf[tx_len++] = 0x03; | ||
300 | } | ||
301 | tx_buf[tx_len++] = 0x23; | ||
302 | tx_buf[tx_len++] = 0x00; | ||
303 | tx_buf[tx_len++] = (buf_size & 0xFF); | ||
304 | tx_buf[tx_len++] = ((buf_size & 0xFF00) >> 8); | ||
305 | |||
306 | if (tx_len + buf_size > sizeof(tx_buf)) | ||
307 | return -EINVAL; | ||
308 | |||
309 | memcpy(&tx_buf[tx_len], buf, buf_size); | ||
310 | tx_len += buf_size; | ||
311 | |||
312 | error = i2c_master_send(client, tx_buf, tx_len); | ||
313 | if (error < 0) { | ||
314 | dev_err(&client->dev, "set feature failed: %d\n", error); | ||
315 | return error; | ||
316 | } | ||
317 | |||
318 | mdelay(WDT_COMMAND_DELAY_MS); | ||
319 | |||
320 | return 0; | ||
321 | } | ||
322 | |||
323 | static int wdt87xx_send_command(struct i2c_client *client, int cmd, int value) | ||
324 | { | ||
325 | u8 cmd_buf[CMD_BUF_SIZE]; | ||
326 | |||
327 | /* Set the command packet */ | ||
328 | cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; | ||
329 | cmd_buf[CMD_TYPE_OFFSET] = VND_SET_COMMAND_DATA; | ||
330 | put_unaligned_le16((u16)cmd, &cmd_buf[CMD_INDEX_OFFSET]); | ||
331 | |||
332 | switch (cmd) { | ||
333 | case VND_CMD_START: | ||
334 | case VND_CMD_STOP: | ||
335 | case VND_CMD_RESET: | ||
336 | /* Mode selector */ | ||
337 | put_unaligned_le32((value & 0xFF), &cmd_buf[CMD_LENGTH_OFFSET]); | ||
338 | break; | ||
339 | |||
340 | case VND_CMD_SFLCK: | ||
341 | put_unaligned_le16(CMD_SFLCK_KEY, &cmd_buf[CMD_KEY_OFFSET]); | ||
342 | break; | ||
343 | |||
344 | case VND_CMD_SFUNL: | ||
345 | put_unaligned_le16(CMD_SFUNL_KEY, &cmd_buf[CMD_KEY_OFFSET]); | ||
346 | break; | ||
347 | |||
348 | case VND_CMD_ERASE: | ||
349 | case VND_SET_CHECKSUM_CALC: | ||
350 | case VND_SET_CHECKSUM_LENGTH: | ||
351 | put_unaligned_le32(value, &cmd_buf[CMD_KEY_OFFSET]); | ||
352 | break; | ||
353 | |||
354 | default: | ||
355 | cmd_buf[CMD_REPORT_ID_OFFSET] = 0; | ||
356 | dev_err(&client->dev, "Invalid command: %d\n", cmd); | ||
357 | return -EINVAL; | ||
358 | } | ||
359 | |||
360 | return wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf)); | ||
361 | } | ||
362 | |||
363 | static int wdt87xx_sw_reset(struct i2c_client *client) | ||
364 | { | ||
365 | int error; | ||
366 | |||
367 | dev_dbg(&client->dev, "resetting device now\n"); | ||
368 | |||
369 | error = wdt87xx_send_command(client, VND_CMD_RESET, 0); | ||
370 | if (error) { | ||
371 | dev_err(&client->dev, "reset failed\n"); | ||
372 | return error; | ||
373 | } | ||
374 | |||
375 | /* Wait the device to be ready */ | ||
376 | msleep(200); | ||
377 | |||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | static const void *wdt87xx_get_fw_chunk(const struct firmware *fw, u32 id) | ||
382 | { | ||
383 | size_t pos = FW_PAYLOAD_OFFSET; | ||
384 | u32 chunk_id, chunk_size; | ||
385 | |||
386 | while (pos < fw->size) { | ||
387 | chunk_id = get_unaligned_le32(fw->data + | ||
388 | pos + FW_CHUNK_ID_OFFSET); | ||
389 | if (chunk_id == id) | ||
390 | return fw->data + pos; | ||
391 | |||
392 | chunk_size = get_unaligned_le32(fw->data + | ||
393 | pos + FW_CHUNK_SIZE_OFFSET); | ||
394 | pos += chunk_size + 2 * sizeof(u32); /* chunk ID + size */ | ||
395 | } | ||
396 | |||
397 | return NULL; | ||
398 | } | ||
399 | |||
400 | static int wdt87xx_get_sysparam(struct i2c_client *client, | ||
401 | struct wdt87xx_sys_param *param) | ||
402 | { | ||
403 | u8 buf[PKT_READ_SIZE]; | ||
404 | int error; | ||
405 | |||
406 | error = wdt87xx_get_string(client, STRIDX_PARAMETERS, buf, 34); | ||
407 | if (error) { | ||
408 | dev_err(&client->dev, "failed to get parameters\n"); | ||
409 | return error; | ||
410 | } | ||
411 | |||
412 | param->xmls_id1 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID1); | ||
413 | param->xmls_id2 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID2); | ||
414 | param->phy_ch_x = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_X); | ||
415 | param->phy_ch_y = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_Y); | ||
416 | param->phy_w = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_W) / 10; | ||
417 | param->phy_h = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_H) / 10; | ||
418 | |||
419 | /* Get the scaling factor of pixel to logical coordinate */ | ||
420 | param->scaling_factor = | ||
421 | get_unaligned_le16(buf + CTL_PARAM_OFFSET_FACTOR); | ||
422 | |||
423 | param->max_x = MAX_UNIT_AXIS; | ||
424 | param->max_y = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS * param->phy_h, | ||
425 | param->phy_w); | ||
426 | |||
427 | error = wdt87xx_get_string(client, STRIDX_PLATFORM_ID, buf, 8); | ||
428 | if (error) { | ||
429 | dev_err(&client->dev, "failed to get platform id\n"); | ||
430 | return error; | ||
431 | } | ||
432 | |||
433 | param->plat_id = buf[1]; | ||
434 | |||
435 | buf[0] = 0xf2; | ||
436 | error = wdt87xx_get_feature(client, buf, 16); | ||
437 | if (error) { | ||
438 | dev_err(&client->dev, "failed to get firmware id\n"); | ||
439 | return error; | ||
440 | } | ||
441 | |||
442 | if (buf[0] != 0xf2) { | ||
443 | dev_err(&client->dev, "wrong id of fw response: 0x%x\n", | ||
444 | buf[0]); | ||
445 | return -EINVAL; | ||
446 | } | ||
447 | |||
448 | param->fw_id = get_unaligned_le16(&buf[1]); | ||
449 | |||
450 | dev_info(&client->dev, | ||
451 | "fw_id: 0x%x, plat_id: 0x%x, xml_id1: %04x, xml_id2: %04x\n", | ||
452 | param->fw_id, param->plat_id, | ||
453 | param->xmls_id1, param->xmls_id2); | ||
454 | |||
455 | return 0; | ||
456 | } | ||
457 | |||
458 | static int wdt87xx_validate_firmware(struct wdt87xx_data *wdt, | ||
459 | const struct firmware *fw) | ||
460 | { | ||
461 | const void *fw_chunk; | ||
462 | u32 data1, data2; | ||
463 | u32 size; | ||
464 | u8 fw_chip_id; | ||
465 | u8 chip_id; | ||
466 | |||
467 | data1 = get_unaligned_le32(fw->data + FW_FOURCC1_OFFSET); | ||
468 | data2 = get_unaligned_le32(fw->data + FW_FOURCC2_OFFSET); | ||
469 | if (data1 != FOURCC_ID_RIFF || data2 != FOURCC_ID_WHIF) { | ||
470 | dev_err(&wdt->client->dev, "check fw tag failed\n"); | ||
471 | return -EINVAL; | ||
472 | } | ||
473 | |||
474 | size = get_unaligned_le32(fw->data + FW_SIZE_OFFSET); | ||
475 | if (size != fw->size) { | ||
476 | dev_err(&wdt->client->dev, | ||
477 | "fw size mismatch: expected %d, actual %zu\n", | ||
478 | size, fw->size); | ||
479 | return -EINVAL; | ||
480 | } | ||
481 | |||
482 | /* | ||
483 | * Get the chip_id from the firmware. Make sure that it is the | ||
484 | * right controller to do the firmware and config update. | ||
485 | */ | ||
486 | fw_chunk = wdt87xx_get_fw_chunk(fw, CHUNK_ID_FRWR); | ||
487 | if (!fw_chunk) { | ||
488 | dev_err(&wdt->client->dev, | ||
489 | "unable to locate firmware chunk\n"); | ||
490 | return -EINVAL; | ||
491 | } | ||
492 | |||
493 | fw_chip_id = (get_unaligned_le32(fw_chunk + | ||
494 | FW_CHUNK_VERSION_OFFSET) >> 12) & 0xF; | ||
495 | chip_id = (wdt->param.fw_id >> 12) & 0xF; | ||
496 | |||
497 | if (fw_chip_id != chip_id) { | ||
498 | dev_err(&wdt->client->dev, | ||
499 | "fw version mismatch: fw %d vs. chip %d\n", | ||
500 | fw_chip_id, chip_id); | ||
501 | return -ENODEV; | ||
502 | } | ||
503 | |||
504 | return 0; | ||
505 | } | ||
506 | |||
507 | static int wdt87xx_validate_fw_chunk(const void *data, int id) | ||
508 | { | ||
509 | if (id == CHUNK_ID_FRWR) { | ||
510 | u32 fw_id; | ||
511 | |||
512 | fw_id = get_unaligned_le32(data + FW_CHUNK_PAYLOAD_OFFSET); | ||
513 | if (fw_id != WDT_FIRMWARE_ID) | ||
514 | return -EINVAL; | ||
515 | } | ||
516 | |||
517 | return 0; | ||
518 | } | ||
519 | |||
520 | static int wdt87xx_write_data(struct i2c_client *client, const char *data, | ||
521 | u32 address, int length) | ||
522 | { | ||
523 | u16 packet_size; | ||
524 | int count = 0; | ||
525 | int error; | ||
526 | u8 pkt_buf[PKT_BUF_SIZE]; | ||
527 | |||
528 | /* Address and length should be 4 bytes aligned */ | ||
529 | if ((address & 0x3) != 0 || (length & 0x3) != 0) { | ||
530 | dev_err(&client->dev, | ||
531 | "addr & len must be 4 bytes aligned %x, %x\n", | ||
532 | address, length); | ||
533 | return -EINVAL; | ||
534 | } | ||
535 | |||
536 | while (length) { | ||
537 | packet_size = min(length, PACKET_SIZE); | ||
538 | |||
539 | pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; | ||
540 | pkt_buf[CMD_TYPE_OFFSET] = VND_SET_DATA; | ||
541 | put_unaligned_le16(packet_size, &pkt_buf[CMD_INDEX_OFFSET]); | ||
542 | put_unaligned_le32(address, &pkt_buf[CMD_LENGTH_OFFSET]); | ||
543 | memcpy(&pkt_buf[CMD_DATA_OFFSET], data, packet_size); | ||
544 | |||
545 | error = wdt87xx_set_feature(client, pkt_buf, sizeof(pkt_buf)); | ||
546 | if (error) | ||
547 | return error; | ||
548 | |||
549 | length -= packet_size; | ||
550 | data += packet_size; | ||
551 | address += packet_size; | ||
552 | |||
553 | /* Wait for the controller to finish the write */ | ||
554 | mdelay(WDT_FLASH_WRITE_DELAY_MS); | ||
555 | |||
556 | if ((++count % 32) == 0) { | ||
557 | /* Delay for fw to clear watch dog */ | ||
558 | msleep(20); | ||
559 | } | ||
560 | } | ||
561 | |||
562 | return 0; | ||
563 | } | ||
564 | |||
565 | static u16 misr(u16 cur_value, u8 new_value) | ||
566 | { | ||
567 | u32 a, b; | ||
568 | u32 bit0; | ||
569 | u32 y; | ||
570 | |||
571 | a = cur_value; | ||
572 | b = new_value; | ||
573 | bit0 = a ^ (b & 1); | ||
574 | bit0 ^= a >> 1; | ||
575 | bit0 ^= a >> 2; | ||
576 | bit0 ^= a >> 4; | ||
577 | bit0 ^= a >> 5; | ||
578 | bit0 ^= a >> 7; | ||
579 | bit0 ^= a >> 11; | ||
580 | bit0 ^= a >> 15; | ||
581 | y = (a << 1) ^ b; | ||
582 | y = (y & ~1) | (bit0 & 1); | ||
583 | |||
584 | return (u16)y; | ||
585 | } | ||
586 | |||
587 | static u16 wdt87xx_calculate_checksum(const u8 *data, size_t length) | ||
588 | { | ||
589 | u16 checksum = 0; | ||
590 | size_t i; | ||
591 | |||
592 | for (i = 0; i < length; i++) | ||
593 | checksum = misr(checksum, data[i]); | ||
594 | |||
595 | return checksum; | ||
596 | } | ||
597 | |||
598 | static int wdt87xx_get_checksum(struct i2c_client *client, u16 *checksum, | ||
599 | u32 address, int length) | ||
600 | { | ||
601 | int error; | ||
602 | int time_delay; | ||
603 | u8 pkt_buf[PKT_BUF_SIZE]; | ||
604 | u8 cmd_buf[CMD_BUF_SIZE]; | ||
605 | |||
606 | error = wdt87xx_send_command(client, VND_SET_CHECKSUM_LENGTH, length); | ||
607 | if (error) { | ||
608 | dev_err(&client->dev, "failed to set checksum length\n"); | ||
609 | return error; | ||
610 | } | ||
611 | |||
612 | error = wdt87xx_send_command(client, VND_SET_CHECKSUM_CALC, address); | ||
613 | if (error) { | ||
614 | dev_err(&client->dev, "failed to set checksum address\n"); | ||
615 | return error; | ||
616 | } | ||
617 | |||
618 | /* Wait the operation to complete */ | ||
619 | time_delay = DIV_ROUND_UP(length, 1024); | ||
620 | msleep(time_delay * 30); | ||
621 | |||
622 | memset(cmd_buf, 0, sizeof(cmd_buf)); | ||
623 | cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_READ; | ||
624 | cmd_buf[CMD_TYPE_OFFSET] = VND_GET_CHECKSUM; | ||
625 | error = wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf)); | ||
626 | if (error) { | ||
627 | dev_err(&client->dev, "failed to request checksum\n"); | ||
628 | return error; | ||
629 | } | ||
630 | |||
631 | memset(pkt_buf, 0, sizeof(pkt_buf)); | ||
632 | pkt_buf[CMD_REPORT_ID_OFFSET] = VND_READ_DATA; | ||
633 | error = wdt87xx_get_feature(client, pkt_buf, sizeof(pkt_buf)); | ||
634 | if (error) { | ||
635 | dev_err(&client->dev, "failed to read checksum\n"); | ||
636 | return error; | ||
637 | } | ||
638 | |||
639 | *checksum = get_unaligned_le16(&pkt_buf[CMD_DATA_OFFSET]); | ||
640 | return 0; | ||
641 | } | ||
642 | |||
643 | static int wdt87xx_write_firmware(struct i2c_client *client, const void *chunk) | ||
644 | { | ||
645 | u32 start_addr = get_unaligned_le32(chunk + FW_CHUNK_TGT_START_OFFSET); | ||
646 | u32 size = get_unaligned_le32(chunk + FW_CHUNK_PAYLOAD_LEN_OFFSET); | ||
647 | const void *data = chunk + FW_CHUNK_PAYLOAD_OFFSET; | ||
648 | int error; | ||
649 | int err1; | ||
650 | int page_size; | ||
651 | int retry = 0; | ||
652 | u16 device_checksum, firmware_checksum; | ||
653 | |||
654 | dev_dbg(&client->dev, "start 4k page program\n"); | ||
655 | |||
656 | error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_STOP); | ||
657 | if (error) { | ||
658 | dev_err(&client->dev, "stop report mode failed\n"); | ||
659 | return error; | ||
660 | } | ||
661 | |||
662 | error = wdt87xx_send_command(client, VND_CMD_SFUNL, 0); | ||
663 | if (error) { | ||
664 | dev_err(&client->dev, "unlock failed\n"); | ||
665 | goto out_enable_reporting; | ||
666 | } | ||
667 | |||
668 | mdelay(10); | ||
669 | |||
670 | while (size) { | ||
671 | dev_dbg(&client->dev, "%s: %x, %x\n", __func__, | ||
672 | start_addr, size); | ||
673 | |||
674 | page_size = min_t(u32, size, PG_SIZE); | ||
675 | size -= page_size; | ||
676 | |||
677 | for (retry = 0; retry < MAX_RETRIES; retry++) { | ||
678 | error = wdt87xx_send_command(client, VND_CMD_ERASE, | ||
679 | start_addr); | ||
680 | if (error) { | ||
681 | dev_err(&client->dev, | ||
682 | "erase failed at %#08x\n", start_addr); | ||
683 | break; | ||
684 | } | ||
685 | |||
686 | msleep(50); | ||
687 | |||
688 | error = wdt87xx_write_data(client, data, start_addr, | ||
689 | page_size); | ||
690 | if (error) { | ||
691 | dev_err(&client->dev, | ||
692 | "write failed at %#08x (%d bytes)\n", | ||
693 | start_addr, page_size); | ||
694 | break; | ||
695 | } | ||
696 | |||
697 | error = wdt87xx_get_checksum(client, &device_checksum, | ||
698 | start_addr, page_size); | ||
699 | if (error) { | ||
700 | dev_err(&client->dev, | ||
701 | "failed to retrieve checksum for %#08x (len: %d)\n", | ||
702 | start_addr, page_size); | ||
703 | break; | ||
704 | } | ||
705 | |||
706 | firmware_checksum = | ||
707 | wdt87xx_calculate_checksum(data, page_size); | ||
708 | |||
709 | if (device_checksum == firmware_checksum) | ||
710 | break; | ||
711 | |||
712 | dev_err(&client->dev, | ||
713 | "checksum fail: %d vs %d, retry %d\n", | ||
714 | device_checksum, firmware_checksum, retry); | ||
715 | } | ||
716 | |||
717 | if (retry == MAX_RETRIES) { | ||
718 | dev_err(&client->dev, "page write failed\n"); | ||
719 | error = -EIO; | ||
720 | goto out_lock_device; | ||
721 | } | ||
722 | |||
723 | start_addr = start_addr + page_size; | ||
724 | data = data + page_size; | ||
725 | } | ||
726 | |||
727 | out_lock_device: | ||
728 | err1 = wdt87xx_send_command(client, VND_CMD_SFLCK, 0); | ||
729 | if (err1) | ||
730 | dev_err(&client->dev, "lock failed\n"); | ||
731 | |||
732 | mdelay(10); | ||
733 | |||
734 | out_enable_reporting: | ||
735 | err1 = wdt87xx_send_command(client, VND_CMD_START, 0); | ||
736 | if (err1) | ||
737 | dev_err(&client->dev, "start to report failed\n"); | ||
738 | |||
739 | return error ? error : err1; | ||
740 | } | ||
741 | |||
742 | static int wdt87xx_load_chunk(struct i2c_client *client, | ||
743 | const struct firmware *fw, u32 ck_id) | ||
744 | { | ||
745 | const void *chunk; | ||
746 | int error; | ||
747 | |||
748 | chunk = wdt87xx_get_fw_chunk(fw, ck_id); | ||
749 | if (!chunk) { | ||
750 | dev_err(&client->dev, "unable to locate chunk (type %d)\n", | ||
751 | ck_id); | ||
752 | return -EINVAL; | ||
753 | } | ||
754 | |||
755 | error = wdt87xx_validate_fw_chunk(chunk, ck_id); | ||
756 | if (error) { | ||
757 | dev_err(&client->dev, "invalid chunk (type %d): %d\n", | ||
758 | ck_id, error); | ||
759 | return error; | ||
760 | } | ||
761 | |||
762 | error = wdt87xx_write_firmware(client, chunk); | ||
763 | if (error) { | ||
764 | dev_err(&client->dev, | ||
765 | "failed to write fw chunk (type %d): %d\n", | ||
766 | ck_id, error); | ||
767 | return error; | ||
768 | } | ||
769 | |||
770 | return 0; | ||
771 | } | ||
772 | |||
773 | static int wdt87xx_do_update_firmware(struct i2c_client *client, | ||
774 | const struct firmware *fw, | ||
775 | unsigned int chunk_id) | ||
776 | { | ||
777 | struct wdt87xx_data *wdt = i2c_get_clientdata(client); | ||
778 | int error; | ||
779 | |||
780 | error = wdt87xx_validate_firmware(wdt, fw); | ||
781 | if (error) | ||
782 | return error; | ||
783 | |||
784 | error = mutex_lock_interruptible(&wdt->fw_mutex); | ||
785 | if (error) | ||
786 | return error; | ||
787 | |||
788 | disable_irq(client->irq); | ||
789 | |||
790 | error = wdt87xx_load_chunk(client, fw, chunk_id); | ||
791 | if (error) { | ||
792 | dev_err(&client->dev, | ||
793 | "firmware load failed (type: %d): %d\n", | ||
794 | chunk_id, error); | ||
795 | goto out; | ||
796 | } | ||
797 | |||
798 | error = wdt87xx_sw_reset(client); | ||
799 | if (error) { | ||
800 | dev_err(&client->dev, "soft reset failed: %d\n", error); | ||
801 | goto out; | ||
802 | } | ||
803 | |||
804 | /* Refresh the parameters */ | ||
805 | error = wdt87xx_get_sysparam(client, &wdt->param); | ||
806 | if (error) | ||
807 | dev_err(&client->dev, | ||
808 | "failed to refresh system paramaters: %d\n", error); | ||
809 | out: | ||
810 | enable_irq(client->irq); | ||
811 | mutex_unlock(&wdt->fw_mutex); | ||
812 | |||
813 | return error ? error : 0; | ||
814 | } | ||
815 | |||
816 | static int wdt87xx_update_firmware(struct device *dev, | ||
817 | const char *fw_name, unsigned int chunk_id) | ||
818 | { | ||
819 | struct i2c_client *client = to_i2c_client(dev); | ||
820 | const struct firmware *fw; | ||
821 | int error; | ||
822 | |||
823 | error = request_firmware(&fw, fw_name, dev); | ||
824 | if (error) { | ||
825 | dev_err(&client->dev, "unable to retrieve firmware %s: %d\n", | ||
826 | fw_name, error); | ||
827 | return error; | ||
828 | } | ||
829 | |||
830 | error = wdt87xx_do_update_firmware(client, fw, chunk_id); | ||
831 | |||
832 | release_firmware(fw); | ||
833 | |||
834 | return error ? error : 0; | ||
835 | } | ||
836 | |||
837 | static ssize_t config_csum_show(struct device *dev, | ||
838 | struct device_attribute *attr, char *buf) | ||
839 | { | ||
840 | struct i2c_client *client = to_i2c_client(dev); | ||
841 | struct wdt87xx_data *wdt = i2c_get_clientdata(client); | ||
842 | u32 cfg_csum; | ||
843 | |||
844 | cfg_csum = wdt->param.xmls_id1; | ||
845 | cfg_csum = (cfg_csum << 16) | wdt->param.xmls_id2; | ||
846 | |||
847 | return scnprintf(buf, PAGE_SIZE, "%x\n", cfg_csum); | ||
848 | } | ||
849 | |||
850 | static ssize_t fw_version_show(struct device *dev, | ||
851 | struct device_attribute *attr, char *buf) | ||
852 | { | ||
853 | struct i2c_client *client = to_i2c_client(dev); | ||
854 | struct wdt87xx_data *wdt = i2c_get_clientdata(client); | ||
855 | |||
856 | return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.fw_id); | ||
857 | } | ||
858 | |||
859 | static ssize_t plat_id_show(struct device *dev, | ||
860 | struct device_attribute *attr, char *buf) | ||
861 | { | ||
862 | struct i2c_client *client = to_i2c_client(dev); | ||
863 | struct wdt87xx_data *wdt = i2c_get_clientdata(client); | ||
864 | |||
865 | return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.plat_id); | ||
866 | } | ||
867 | |||
868 | static ssize_t update_config_store(struct device *dev, | ||
869 | struct device_attribute *attr, | ||
870 | const char *buf, size_t count) | ||
871 | { | ||
872 | int error; | ||
873 | |||
874 | error = wdt87xx_update_firmware(dev, WDT87XX_CFG_NAME, CHUNK_ID_CNFG); | ||
875 | |||
876 | return error ? error : count; | ||
877 | } | ||
878 | |||
879 | static ssize_t update_fw_store(struct device *dev, | ||
880 | struct device_attribute *attr, | ||
881 | const char *buf, size_t count) | ||
882 | { | ||
883 | int error; | ||
884 | |||
885 | error = wdt87xx_update_firmware(dev, WDT87XX_FW_NAME, CHUNK_ID_FRWR); | ||
886 | |||
887 | return error ? error : count; | ||
888 | } | ||
889 | |||
890 | static DEVICE_ATTR_RO(config_csum); | ||
891 | static DEVICE_ATTR_RO(fw_version); | ||
892 | static DEVICE_ATTR_RO(plat_id); | ||
893 | static DEVICE_ATTR_WO(update_config); | ||
894 | static DEVICE_ATTR_WO(update_fw); | ||
895 | |||
896 | static struct attribute *wdt87xx_attrs[] = { | ||
897 | &dev_attr_config_csum.attr, | ||
898 | &dev_attr_fw_version.attr, | ||
899 | &dev_attr_plat_id.attr, | ||
900 | &dev_attr_update_config.attr, | ||
901 | &dev_attr_update_fw.attr, | ||
902 | NULL | ||
903 | }; | ||
904 | |||
905 | static const struct attribute_group wdt87xx_attr_group = { | ||
906 | .attrs = wdt87xx_attrs, | ||
907 | }; | ||
908 | |||
909 | static void wdt87xx_report_contact(struct input_dev *input, | ||
910 | struct wdt87xx_sys_param *param, | ||
911 | u8 *buf) | ||
912 | { | ||
913 | int finger_id; | ||
914 | u32 x, y, w; | ||
915 | u8 p; | ||
916 | |||
917 | finger_id = (buf[FINGER_EV_V1_OFFSET_ID] >> 3) - 1; | ||
918 | if (finger_id < 0) | ||
919 | return; | ||
920 | |||
921 | /* Check if this is an active contact */ | ||
922 | if (!(buf[FINGER_EV_V1_OFFSET_ID] & 0x1)) | ||
923 | return; | ||
924 | |||
925 | w = buf[FINGER_EV_V1_OFFSET_W]; | ||
926 | w *= param->scaling_factor; | ||
927 | |||
928 | p = buf[FINGER_EV_V1_OFFSET_P]; | ||
929 | |||
930 | x = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_X); | ||
931 | |||
932 | y = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_Y); | ||
933 | y = DIV_ROUND_CLOSEST(y * param->phy_h, param->phy_w); | ||
934 | |||
935 | /* Refuse incorrect coordinates */ | ||
936 | if (x > param->max_x || y > param->max_y) | ||
937 | return; | ||
938 | |||
939 | dev_dbg(input->dev.parent, "tip on (%d), x(%d), y(%d)\n", | ||
940 | finger_id, x, y); | ||
941 | |||
942 | input_mt_slot(input, finger_id); | ||
943 | input_mt_report_slot_state(input, MT_TOOL_FINGER, 1); | ||
944 | input_report_abs(input, ABS_MT_TOUCH_MAJOR, w); | ||
945 | input_report_abs(input, ABS_MT_PRESSURE, p); | ||
946 | input_report_abs(input, ABS_MT_POSITION_X, x); | ||
947 | input_report_abs(input, ABS_MT_POSITION_Y, y); | ||
948 | } | ||
949 | |||
950 | static irqreturn_t wdt87xx_ts_interrupt(int irq, void *dev_id) | ||
951 | { | ||
952 | struct wdt87xx_data *wdt = dev_id; | ||
953 | struct i2c_client *client = wdt->client; | ||
954 | int i, fingers; | ||
955 | int error; | ||
956 | u8 raw_buf[WDT_V1_RAW_BUF_COUNT] = {0}; | ||
957 | |||
958 | error = i2c_master_recv(client, raw_buf, WDT_V1_RAW_BUF_COUNT); | ||
959 | if (error < 0) { | ||
960 | dev_err(&client->dev, "read v1 raw data failed: %d\n", error); | ||
961 | goto irq_exit; | ||
962 | } | ||
963 | |||
964 | fingers = raw_buf[TOUCH_PK_V1_OFFSET_FNGR_NUM]; | ||
965 | if (!fingers) | ||
966 | goto irq_exit; | ||
967 | |||
968 | for (i = 0; i < WDT_MAX_FINGER; i++) | ||
969 | wdt87xx_report_contact(wdt->input, | ||
970 | &wdt->param, | ||
971 | &raw_buf[TOUCH_PK_V1_OFFSET_EVENT + | ||
972 | i * FINGER_EV_V1_SIZE]); | ||
973 | |||
974 | input_mt_sync_frame(wdt->input); | ||
975 | input_sync(wdt->input); | ||
976 | |||
977 | irq_exit: | ||
978 | return IRQ_HANDLED; | ||
979 | } | ||
980 | |||
981 | static int wdt87xx_ts_create_input_device(struct wdt87xx_data *wdt) | ||
982 | { | ||
983 | struct device *dev = &wdt->client->dev; | ||
984 | struct input_dev *input; | ||
985 | unsigned int res = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS, wdt->param.phy_w); | ||
986 | int error; | ||
987 | |||
988 | input = devm_input_allocate_device(dev); | ||
989 | if (!input) { | ||
990 | dev_err(dev, "failed to allocate input device\n"); | ||
991 | return -ENOMEM; | ||
992 | } | ||
993 | wdt->input = input; | ||
994 | |||
995 | input->name = "WDT87xx Touchscreen"; | ||
996 | input->id.bustype = BUS_I2C; | ||
997 | input->phys = wdt->phys; | ||
998 | |||
999 | input_set_abs_params(input, ABS_MT_POSITION_X, 0, | ||
1000 | wdt->param.max_x, 0, 0); | ||
1001 | input_set_abs_params(input, ABS_MT_POSITION_Y, 0, | ||
1002 | wdt->param.max_y, 0, 0); | ||
1003 | input_abs_set_res(input, ABS_MT_POSITION_X, res); | ||
1004 | input_abs_set_res(input, ABS_MT_POSITION_Y, res); | ||
1005 | |||
1006 | input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, | ||
1007 | 0, wdt->param.max_x, 0, 0); | ||
1008 | input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xFF, 0, 0); | ||
1009 | |||
1010 | input_mt_init_slots(input, WDT_MAX_FINGER, | ||
1011 | INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); | ||
1012 | |||
1013 | error = input_register_device(input); | ||
1014 | if (error) { | ||
1015 | dev_err(dev, "failed to register input device: %d\n", error); | ||
1016 | return error; | ||
1017 | } | ||
1018 | |||
1019 | return 0; | ||
1020 | } | ||
1021 | |||
1022 | static int wdt87xx_ts_probe(struct i2c_client *client, | ||
1023 | const struct i2c_device_id *id) | ||
1024 | { | ||
1025 | struct wdt87xx_data *wdt; | ||
1026 | int error; | ||
1027 | |||
1028 | dev_dbg(&client->dev, "adapter=%d, client irq: %d\n", | ||
1029 | client->adapter->nr, client->irq); | ||
1030 | |||
1031 | /* Check if the I2C function is ok in this adaptor */ | ||
1032 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) | ||
1033 | return -ENXIO; | ||
1034 | |||
1035 | wdt = devm_kzalloc(&client->dev, sizeof(*wdt), GFP_KERNEL); | ||
1036 | if (!wdt) | ||
1037 | return -ENOMEM; | ||
1038 | |||
1039 | wdt->client = client; | ||
1040 | mutex_init(&wdt->fw_mutex); | ||
1041 | i2c_set_clientdata(client, wdt); | ||
1042 | |||
1043 | snprintf(wdt->phys, sizeof(wdt->phys), "i2c-%u-%04x/input0", | ||
1044 | client->adapter->nr, client->addr); | ||
1045 | |||
1046 | error = wdt87xx_get_sysparam(client, &wdt->param); | ||
1047 | if (error) | ||
1048 | return error; | ||
1049 | |||
1050 | error = wdt87xx_ts_create_input_device(wdt); | ||
1051 | if (error) | ||
1052 | return error; | ||
1053 | |||
1054 | error = devm_request_threaded_irq(&client->dev, client->irq, | ||
1055 | NULL, wdt87xx_ts_interrupt, | ||
1056 | IRQF_ONESHOT, | ||
1057 | client->name, wdt); | ||
1058 | if (error) { | ||
1059 | dev_err(&client->dev, "request irq failed: %d\n", error); | ||
1060 | return error; | ||
1061 | } | ||
1062 | |||
1063 | error = sysfs_create_group(&client->dev.kobj, &wdt87xx_attr_group); | ||
1064 | if (error) { | ||
1065 | dev_err(&client->dev, "create sysfs failed: %d\n", error); | ||
1066 | return error; | ||
1067 | } | ||
1068 | |||
1069 | return 0; | ||
1070 | } | ||
1071 | |||
1072 | static int wdt87xx_ts_remove(struct i2c_client *client) | ||
1073 | { | ||
1074 | sysfs_remove_group(&client->dev.kobj, &wdt87xx_attr_group); | ||
1075 | |||
1076 | return 0; | ||
1077 | } | ||
1078 | |||
1079 | static int __maybe_unused wdt87xx_suspend(struct device *dev) | ||
1080 | { | ||
1081 | struct i2c_client *client = to_i2c_client(dev); | ||
1082 | int error; | ||
1083 | |||
1084 | disable_irq(client->irq); | ||
1085 | |||
1086 | error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_IDLE); | ||
1087 | if (error) { | ||
1088 | enable_irq(client->irq); | ||
1089 | dev_err(&client->dev, | ||
1090 | "failed to stop device when suspending: %d\n", | ||
1091 | error); | ||
1092 | return error; | ||
1093 | } | ||
1094 | |||
1095 | return 0; | ||
1096 | } | ||
1097 | |||
1098 | static int __maybe_unused wdt87xx_resume(struct device *dev) | ||
1099 | { | ||
1100 | struct i2c_client *client = to_i2c_client(dev); | ||
1101 | int error; | ||
1102 | |||
1103 | /* | ||
1104 | * The chip may have been reset while system is resuming, | ||
1105 | * give it some time to settle. | ||
1106 | */ | ||
1107 | mdelay(100); | ||
1108 | |||
1109 | error = wdt87xx_send_command(client, VND_CMD_START, 0); | ||
1110 | if (error) | ||
1111 | dev_err(&client->dev, | ||
1112 | "failed to start device when resuming: %d\n", | ||
1113 | error); | ||
1114 | |||
1115 | enable_irq(client->irq); | ||
1116 | |||
1117 | return 0; | ||
1118 | } | ||
1119 | |||
1120 | static SIMPLE_DEV_PM_OPS(wdt87xx_pm_ops, wdt87xx_suspend, wdt87xx_resume); | ||
1121 | |||
1122 | static const struct i2c_device_id wdt87xx_dev_id[] = { | ||
1123 | { WDT87XX_NAME, 0 }, | ||
1124 | { } | ||
1125 | }; | ||
1126 | MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id); | ||
1127 | |||
1128 | static const struct acpi_device_id wdt87xx_acpi_id[] = { | ||
1129 | { "WDHT0001", 0 }, | ||
1130 | { } | ||
1131 | }; | ||
1132 | MODULE_DEVICE_TABLE(acpi, wdt87xx_acpi_id); | ||
1133 | |||
1134 | static struct i2c_driver wdt87xx_driver = { | ||
1135 | .probe = wdt87xx_ts_probe, | ||
1136 | .remove = wdt87xx_ts_remove, | ||
1137 | .id_table = wdt87xx_dev_id, | ||
1138 | .driver = { | ||
1139 | .name = WDT87XX_NAME, | ||
1140 | .pm = &wdt87xx_pm_ops, | ||
1141 | .acpi_match_table = ACPI_PTR(wdt87xx_acpi_id), | ||
1142 | }, | ||
1143 | }; | ||
1144 | module_i2c_driver(wdt87xx_driver); | ||
1145 | |||
1146 | MODULE_AUTHOR("HN Chen <hn.chen@weidahitech.com>"); | ||
1147 | MODULE_DESCRIPTION("WeidaHiTech WDT87XX Touchscreen driver"); | ||
1148 | MODULE_VERSION(WDT87XX_DRV_VER); | ||
1149 | MODULE_LICENSE("GPL"); | ||