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