Commit | Line | Data |
---|---|---|
c2c06c41 DD |
1 | /* |
2 | * Cypress APA trackpad with I2C interface | |
3 | * | |
4 | * Author: Dudley Du <dudl@cypress.com> | |
5 | * | |
6 | * Copyright (C) 2015 Cypress Semiconductor, Inc. | |
7 | * | |
8 | * This file is subject to the terms and conditions of the GNU General Public | |
9 | * License. See the file COPYING in the main directory of this archive for | |
10 | * more details. | |
11 | */ | |
12 | ||
13 | #include <linux/delay.h> | |
14 | #include <linux/i2c.h> | |
15 | #include <linux/input.h> | |
16 | #include <linux/input/mt.h> | |
17 | #include <linux/mutex.h> | |
18 | #include <linux/completion.h> | |
19 | #include <linux/slab.h> | |
20 | #include <asm/unaligned.h> | |
21 | #include <linux/crc-itu-t.h> | |
22 | #include "cyapa.h" | |
23 | ||
24 | ||
25 | #define GEN6_ENABLE_CMD_IRQ 0x41 | |
26 | #define GEN6_DISABLE_CMD_IRQ 0x42 | |
27 | #define GEN6_ENABLE_DEV_IRQ 0x43 | |
28 | #define GEN6_DISABLE_DEV_IRQ 0x44 | |
29 | ||
30 | #define GEN6_POWER_MODE_ACTIVE 0x01 | |
31 | #define GEN6_POWER_MODE_LP_MODE1 0x02 | |
32 | #define GEN6_POWER_MODE_LP_MODE2 0x03 | |
33 | #define GEN6_POWER_MODE_BTN_ONLY 0x04 | |
34 | ||
35 | #define GEN6_SET_POWER_MODE_INTERVAL 0x47 | |
36 | #define GEN6_GET_POWER_MODE_INTERVAL 0x48 | |
37 | ||
38 | #define GEN6_MAX_RX_NUM 14 | |
39 | #define GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC 0x00 | |
40 | #define GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM 0x12 | |
41 | ||
42 | ||
43 | struct pip_app_cmd_head { | |
44 | __le16 addr; | |
45 | __le16 length; | |
46 | u8 report_id; | |
47 | u8 resv; /* Reserved, must be 0 */ | |
48 | u8 cmd_code; /* bit7: resv, set to 0; bit6~0: command code.*/ | |
49 | } __packed; | |
50 | ||
51 | struct pip_app_resp_head { | |
52 | __le16 length; | |
53 | u8 report_id; | |
54 | u8 resv; /* Reserved, must be 0 */ | |
55 | u8 cmd_code; /* bit7: TGL; bit6~0: command code.*/ | |
56 | /* | |
57 | * The value of data_status can be the first byte of data or | |
58 | * the command status or the unsupported command code depending on the | |
59 | * requested command code. | |
60 | */ | |
61 | u8 data_status; | |
62 | } __packed; | |
63 | ||
64 | struct pip_fixed_info { | |
65 | u8 silicon_id_high; | |
66 | u8 silicon_id_low; | |
67 | u8 family_id; | |
68 | }; | |
69 | ||
70 | static u8 pip_get_bl_info[] = { | |
71 | 0x04, 0x00, 0x0B, 0x00, 0x40, 0x00, 0x01, 0x38, | |
72 | 0x00, 0x00, 0x70, 0x9E, 0x17 | |
73 | }; | |
74 | ||
75 | static bool cyapa_sort_pip_hid_descriptor_data(struct cyapa *cyapa, | |
76 | u8 *buf, int len) | |
77 | { | |
78 | if (len != PIP_HID_DESCRIPTOR_SIZE) | |
79 | return false; | |
80 | ||
81 | if (buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID || | |
82 | buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID) | |
83 | return true; | |
84 | ||
85 | return false; | |
86 | } | |
87 | ||
88 | static int cyapa_get_pip_fixed_info(struct cyapa *cyapa, | |
89 | struct pip_fixed_info *pip_info, bool is_bootloader) | |
90 | { | |
91 | u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; | |
92 | int resp_len; | |
93 | u16 product_family; | |
94 | int error; | |
95 | ||
96 | if (is_bootloader) { | |
97 | /* Read Bootloader Information to determine Gen5 or Gen6. */ | |
98 | resp_len = sizeof(resp_data); | |
99 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
100 | pip_get_bl_info, sizeof(pip_get_bl_info), | |
101 | resp_data, &resp_len, | |
102 | 2000, cyapa_sort_tsg_pip_bl_resp_data, | |
103 | false); | |
104 | if (error || resp_len < PIP_BL_GET_INFO_RESP_LENGTH) | |
105 | return error ? error : -EIO; | |
106 | ||
107 | pip_info->family_id = resp_data[8]; | |
108 | pip_info->silicon_id_low = resp_data[10]; | |
109 | pip_info->silicon_id_high = resp_data[11]; | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | /* Get App System Information to determine Gen5 or Gen6. */ | |
115 | resp_len = sizeof(resp_data); | |
116 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
117 | pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, | |
118 | resp_data, &resp_len, | |
119 | 2000, cyapa_pip_sort_system_info_data, false); | |
120 | if (error || resp_len < PIP_READ_SYS_INFO_RESP_LENGTH) | |
121 | return error ? error : -EIO; | |
122 | ||
123 | product_family = get_unaligned_le16(&resp_data[7]); | |
124 | if ((product_family & PIP_PRODUCT_FAMILY_MASK) != | |
125 | PIP_PRODUCT_FAMILY_TRACKPAD) | |
126 | return -EINVAL; | |
127 | ||
128 | pip_info->family_id = resp_data[19]; | |
129 | pip_info->silicon_id_low = resp_data[21]; | |
130 | pip_info->silicon_id_high = resp_data[22]; | |
131 | ||
132 | return 0; | |
133 | ||
134 | } | |
135 | ||
136 | int cyapa_pip_state_parse(struct cyapa *cyapa, u8 *reg_data, int len) | |
137 | { | |
138 | u8 cmd[] = { 0x01, 0x00}; | |
139 | struct pip_fixed_info pip_info; | |
140 | u8 resp_data[PIP_HID_DESCRIPTOR_SIZE]; | |
141 | int resp_len; | |
142 | bool is_bootloader; | |
143 | int error; | |
144 | ||
145 | cyapa->state = CYAPA_STATE_NO_DEVICE; | |
146 | ||
147 | /* Try to wake from it deep sleep state if it is. */ | |
148 | cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); | |
149 | ||
150 | /* Empty the buffer queue to get fresh data with later commands. */ | |
151 | cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); | |
152 | ||
153 | /* | |
154 | * Read description info from trackpad device to determine running in | |
155 | * APP mode or Bootloader mode. | |
156 | */ | |
157 | resp_len = PIP_HID_DESCRIPTOR_SIZE; | |
158 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
159 | cmd, sizeof(cmd), | |
160 | resp_data, &resp_len, | |
161 | 300, | |
162 | cyapa_sort_pip_hid_descriptor_data, | |
163 | false); | |
164 | if (error) | |
165 | return error; | |
166 | ||
167 | if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID) | |
168 | is_bootloader = true; | |
169 | else if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID) | |
170 | is_bootloader = false; | |
171 | else | |
172 | return -EAGAIN; | |
173 | ||
174 | /* Get PIP fixed information to determine Gen5 or Gen6. */ | |
175 | memset(&pip_info, 0, sizeof(struct pip_fixed_info)); | |
176 | error = cyapa_get_pip_fixed_info(cyapa, &pip_info, is_bootloader); | |
177 | if (error) | |
178 | return error; | |
179 | ||
180 | if (pip_info.family_id == 0x9B && pip_info.silicon_id_high == 0x0B) { | |
181 | cyapa->gen = CYAPA_GEN6; | |
182 | cyapa->state = is_bootloader ? CYAPA_STATE_GEN6_BL | |
183 | : CYAPA_STATE_GEN6_APP; | |
184 | } else if (pip_info.family_id == 0x91 && | |
185 | pip_info.silicon_id_high == 0x02) { | |
186 | cyapa->gen = CYAPA_GEN5; | |
187 | cyapa->state = is_bootloader ? CYAPA_STATE_GEN5_BL | |
188 | : CYAPA_STATE_GEN5_APP; | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static int cyapa_gen6_read_sys_info(struct cyapa *cyapa) | |
195 | { | |
196 | u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; | |
197 | int resp_len; | |
198 | u16 product_family; | |
199 | u8 rotat_align; | |
200 | int error; | |
201 | ||
202 | /* Get App System Information to determine Gen5 or Gen6. */ | |
203 | resp_len = sizeof(resp_data); | |
204 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
205 | pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, | |
206 | resp_data, &resp_len, | |
207 | 2000, cyapa_pip_sort_system_info_data, false); | |
208 | if (error || resp_len < sizeof(resp_data)) | |
209 | return error ? error : -EIO; | |
210 | ||
211 | product_family = get_unaligned_le16(&resp_data[7]); | |
212 | if ((product_family & PIP_PRODUCT_FAMILY_MASK) != | |
213 | PIP_PRODUCT_FAMILY_TRACKPAD) | |
214 | return -EINVAL; | |
215 | ||
216 | cyapa->platform_ver = (resp_data[67] >> PIP_BL_PLATFORM_VER_SHIFT) & | |
217 | PIP_BL_PLATFORM_VER_MASK; | |
218 | cyapa->fw_maj_ver = resp_data[9]; | |
219 | cyapa->fw_min_ver = resp_data[10]; | |
220 | ||
221 | cyapa->electrodes_x = resp_data[33]; | |
222 | cyapa->electrodes_y = resp_data[34]; | |
223 | ||
224 | cyapa->physical_size_x = get_unaligned_le16(&resp_data[35]) / 100; | |
225 | cyapa->physical_size_y = get_unaligned_le16(&resp_data[37]) / 100; | |
226 | ||
227 | cyapa->max_abs_x = get_unaligned_le16(&resp_data[39]); | |
228 | cyapa->max_abs_y = get_unaligned_le16(&resp_data[41]); | |
229 | ||
230 | cyapa->max_z = get_unaligned_le16(&resp_data[43]); | |
231 | ||
232 | cyapa->x_origin = resp_data[45] & 0x01; | |
233 | cyapa->y_origin = resp_data[46] & 0x01; | |
234 | ||
235 | cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK; | |
236 | ||
237 | memcpy(&cyapa->product_id[0], &resp_data[51], 5); | |
238 | cyapa->product_id[5] = '-'; | |
239 | memcpy(&cyapa->product_id[6], &resp_data[56], 6); | |
240 | cyapa->product_id[12] = '-'; | |
241 | memcpy(&cyapa->product_id[13], &resp_data[62], 2); | |
242 | cyapa->product_id[15] = '\0'; | |
243 | ||
244 | rotat_align = resp_data[68]; | |
245 | if (rotat_align) { | |
246 | cyapa->electrodes_rx = cyapa->electrodes_y; | |
247 | cyapa->electrodes_rx = cyapa->electrodes_y; | |
248 | } else { | |
249 | cyapa->electrodes_rx = cyapa->electrodes_x; | |
250 | cyapa->electrodes_rx = cyapa->electrodes_y; | |
251 | } | |
252 | cyapa->aligned_electrodes_rx = (cyapa->electrodes_rx + 3) & ~3u; | |
253 | ||
254 | if (!cyapa->electrodes_x || !cyapa->electrodes_y || | |
255 | !cyapa->physical_size_x || !cyapa->physical_size_y || | |
256 | !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z) | |
257 | return -EINVAL; | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
262 | static int cyapa_gen6_bl_read_app_info(struct cyapa *cyapa) | |
263 | { | |
264 | u8 resp_data[PIP_BL_APP_INFO_RESP_LENGTH]; | |
265 | int resp_len; | |
266 | int error; | |
267 | ||
268 | resp_len = sizeof(resp_data); | |
269 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
270 | pip_bl_read_app_info, PIP_BL_READ_APP_INFO_CMD_LENGTH, | |
271 | resp_data, &resp_len, | |
272 | 500, cyapa_sort_tsg_pip_bl_resp_data, false); | |
273 | if (error || resp_len < PIP_BL_APP_INFO_RESP_LENGTH || | |
274 | !PIP_CMD_COMPLETE_SUCCESS(resp_data)) | |
275 | return error ? error : -EIO; | |
276 | ||
277 | cyapa->fw_maj_ver = resp_data[8]; | |
278 | cyapa->fw_min_ver = resp_data[9]; | |
279 | ||
280 | cyapa->platform_ver = (resp_data[12] >> PIP_BL_PLATFORM_VER_SHIFT) & | |
281 | PIP_BL_PLATFORM_VER_MASK; | |
282 | ||
283 | memcpy(&cyapa->product_id[0], &resp_data[13], 5); | |
284 | cyapa->product_id[5] = '-'; | |
285 | memcpy(&cyapa->product_id[6], &resp_data[18], 6); | |
286 | cyapa->product_id[12] = '-'; | |
287 | memcpy(&cyapa->product_id[13], &resp_data[24], 2); | |
288 | cyapa->product_id[15] = '\0'; | |
289 | ||
290 | return 0; | |
291 | ||
292 | } | |
293 | ||
294 | static int cyapa_gen6_config_dev_irq(struct cyapa *cyapa, u8 cmd_code) | |
295 | { | |
296 | u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, cmd_code }; | |
297 | u8 resp_data[6]; | |
298 | int resp_len; | |
299 | int error; | |
300 | ||
301 | resp_len = sizeof(resp_data); | |
302 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), | |
303 | resp_data, &resp_len, | |
304 | 500, cyapa_sort_tsg_pip_app_resp_data, false); | |
305 | if (error || !VALID_CMD_RESP_HEADER(resp_data, cmd_code) || | |
306 | !PIP_CMD_COMPLETE_SUCCESS(resp_data) | |
307 | ) | |
308 | return error < 0 ? error : -EINVAL; | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
945525ee DD |
313 | static int cyapa_gen6_set_proximity(struct cyapa *cyapa, bool enable) |
314 | { | |
315 | int error; | |
316 | ||
317 | cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); | |
318 | error = cyapa_pip_set_proximity(cyapa, enable); | |
319 | cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); | |
320 | ||
321 | return error; | |
322 | } | |
323 | ||
c2c06c41 DD |
324 | static int cyapa_gen6_change_power_state(struct cyapa *cyapa, u8 power_mode) |
325 | { | |
326 | u8 cmd[] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x46, power_mode }; | |
327 | u8 resp_data[6]; | |
328 | int resp_len; | |
329 | int error; | |
330 | ||
331 | resp_len = sizeof(resp_data); | |
332 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), | |
333 | resp_data, &resp_len, | |
334 | 500, cyapa_sort_tsg_pip_app_resp_data, false); | |
335 | if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x46)) | |
336 | return error < 0 ? error : -EINVAL; | |
337 | ||
338 | /* New power state applied in device not match the set power state. */ | |
339 | if (resp_data[5] != power_mode) | |
340 | return -EAGAIN; | |
341 | ||
342 | return 0; | |
343 | } | |
344 | ||
345 | static int cyapa_gen6_set_interval_setting(struct cyapa *cyapa, | |
346 | struct gen6_interval_setting *interval_setting) | |
347 | { | |
348 | struct gen6_set_interval_cmd { | |
349 | __le16 addr; | |
350 | __le16 length; | |
351 | u8 report_id; | |
352 | u8 rsvd; /* Reserved, must be 0 */ | |
353 | u8 cmd_code; | |
354 | __le16 active_interval; | |
355 | __le16 lp1_interval; | |
356 | __le16 lp2_interval; | |
357 | } __packed set_interval_cmd; | |
358 | u8 resp_data[11]; | |
359 | int resp_len; | |
360 | int error; | |
361 | ||
362 | memset(&set_interval_cmd, 0, sizeof(set_interval_cmd)); | |
363 | put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &set_interval_cmd.addr); | |
364 | put_unaligned_le16(sizeof(set_interval_cmd) - 2, | |
365 | &set_interval_cmd.length); | |
366 | set_interval_cmd.report_id = PIP_APP_CMD_REPORT_ID; | |
367 | set_interval_cmd.cmd_code = GEN6_SET_POWER_MODE_INTERVAL; | |
368 | put_unaligned_le16(interval_setting->active_interval, | |
369 | &set_interval_cmd.active_interval); | |
370 | put_unaligned_le16(interval_setting->lp1_interval, | |
371 | &set_interval_cmd.lp1_interval); | |
372 | put_unaligned_le16(interval_setting->lp2_interval, | |
373 | &set_interval_cmd.lp2_interval); | |
374 | ||
375 | resp_len = sizeof(resp_data); | |
376 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
377 | (u8 *)&set_interval_cmd, sizeof(set_interval_cmd), | |
378 | resp_data, &resp_len, | |
379 | 500, cyapa_sort_tsg_pip_app_resp_data, false); | |
380 | if (error || | |
381 | !VALID_CMD_RESP_HEADER(resp_data, GEN6_SET_POWER_MODE_INTERVAL)) | |
382 | return error < 0 ? error : -EINVAL; | |
383 | ||
384 | /* Get the real set intervals from response. */ | |
385 | interval_setting->active_interval = get_unaligned_le16(&resp_data[5]); | |
386 | interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]); | |
387 | interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]); | |
388 | ||
389 | return 0; | |
390 | } | |
391 | ||
392 | static int cyapa_gen6_get_interval_setting(struct cyapa *cyapa, | |
393 | struct gen6_interval_setting *interval_setting) | |
394 | { | |
395 | u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, | |
396 | GEN6_GET_POWER_MODE_INTERVAL }; | |
397 | u8 resp_data[11]; | |
398 | int resp_len; | |
399 | int error; | |
400 | ||
401 | resp_len = sizeof(resp_data); | |
402 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), | |
403 | resp_data, &resp_len, | |
404 | 500, cyapa_sort_tsg_pip_app_resp_data, false); | |
405 | if (error || | |
406 | !VALID_CMD_RESP_HEADER(resp_data, GEN6_GET_POWER_MODE_INTERVAL)) | |
407 | return error < 0 ? error : -EINVAL; | |
408 | ||
409 | interval_setting->active_interval = get_unaligned_le16(&resp_data[5]); | |
410 | interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]); | |
411 | interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]); | |
412 | ||
413 | return 0; | |
414 | } | |
415 | ||
416 | static int cyapa_gen6_deep_sleep(struct cyapa *cyapa, u8 state) | |
417 | { | |
418 | u8 ping[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x00 }; | |
419 | ||
420 | if (state == PIP_DEEP_SLEEP_STATE_ON) | |
421 | /* | |
422 | * Send ping command to notify device prepare for wake up | |
423 | * when it's in deep sleep mode. At this time, device will | |
424 | * response nothing except an I2C NAK. | |
425 | */ | |
426 | cyapa_i2c_pip_write(cyapa, ping, sizeof(ping)); | |
427 | ||
428 | return cyapa_pip_deep_sleep(cyapa, state); | |
429 | } | |
430 | ||
431 | static int cyapa_gen6_set_power_mode(struct cyapa *cyapa, | |
757cae5a | 432 | u8 power_mode, u16 sleep_time, bool is_suspend) |
c2c06c41 DD |
433 | { |
434 | struct device *dev = &cyapa->client->dev; | |
435 | struct gen6_interval_setting *interval_setting = | |
436 | &cyapa->gen6_interval_setting; | |
437 | u8 lp_mode; | |
438 | int error; | |
439 | ||
440 | if (cyapa->state != CYAPA_STATE_GEN6_APP) | |
441 | return 0; | |
442 | ||
443 | if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) { | |
444 | /* | |
445 | * Assume TP in deep sleep mode when driver is loaded, | |
446 | * avoid driver unload and reload command IO issue caused by TP | |
447 | * has been set into deep sleep mode when unloading. | |
448 | */ | |
449 | PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); | |
450 | } | |
451 | ||
452 | if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) && | |
453 | PIP_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF) | |
454 | PIP_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME); | |
455 | ||
456 | if (PIP_DEV_GET_PWR_STATE(cyapa) == power_mode) { | |
457 | if (power_mode == PWR_MODE_OFF || | |
458 | power_mode == PWR_MODE_FULL_ACTIVE || | |
459 | power_mode == PWR_MODE_BTN_ONLY || | |
460 | PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) { | |
461 | /* Has in correct power mode state, early return. */ | |
462 | return 0; | |
463 | } | |
464 | } | |
465 | ||
466 | if (power_mode == PWR_MODE_OFF) { | |
467 | cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); | |
468 | ||
469 | error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF); | |
470 | if (error) { | |
471 | dev_err(dev, "enter deep sleep fail: %d\n", error); | |
472 | return error; | |
473 | } | |
474 | ||
475 | PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); | |
476 | return 0; | |
477 | } | |
478 | ||
479 | /* | |
480 | * When trackpad in power off mode, it cannot change to other power | |
481 | * state directly, must be wake up from sleep firstly, then | |
482 | * continue to do next power sate change. | |
483 | */ | |
484 | if (PIP_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) { | |
485 | error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); | |
486 | if (error) { | |
487 | dev_err(dev, "deep sleep wake fail: %d\n", error); | |
488 | return error; | |
489 | } | |
490 | } | |
491 | ||
492 | /* | |
493 | * Disable device assert interrupts for command response to avoid | |
494 | * disturbing system suspending or hibernating process. | |
495 | */ | |
496 | cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); | |
497 | ||
498 | if (power_mode == PWR_MODE_FULL_ACTIVE) { | |
499 | error = cyapa_gen6_change_power_state(cyapa, | |
500 | GEN6_POWER_MODE_ACTIVE); | |
501 | if (error) { | |
502 | dev_err(dev, "change to active fail: %d\n", error); | |
503 | goto out; | |
504 | } | |
505 | ||
506 | PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE); | |
507 | ||
508 | /* Sync the interval setting from device. */ | |
509 | cyapa_gen6_get_interval_setting(cyapa, interval_setting); | |
510 | ||
511 | } else if (power_mode == PWR_MODE_BTN_ONLY) { | |
512 | error = cyapa_gen6_change_power_state(cyapa, | |
513 | GEN6_POWER_MODE_BTN_ONLY); | |
514 | if (error) { | |
515 | dev_err(dev, "fail to button only mode: %d\n", error); | |
516 | goto out; | |
517 | } | |
518 | ||
519 | PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY); | |
520 | } else { | |
521 | /* | |
522 | * Gen6 internally supports to 2 low power scan interval time, | |
523 | * so can help to switch power mode quickly. | |
524 | * such as runtime suspend and system suspend. | |
525 | */ | |
526 | if (interval_setting->lp1_interval == sleep_time) { | |
527 | lp_mode = GEN6_POWER_MODE_LP_MODE1; | |
528 | } else if (interval_setting->lp2_interval == sleep_time) { | |
529 | lp_mode = GEN6_POWER_MODE_LP_MODE2; | |
530 | } else { | |
531 | if (interval_setting->lp1_interval == 0) { | |
532 | interval_setting->lp1_interval = sleep_time; | |
533 | lp_mode = GEN6_POWER_MODE_LP_MODE1; | |
534 | } else { | |
535 | interval_setting->lp2_interval = sleep_time; | |
536 | lp_mode = GEN6_POWER_MODE_LP_MODE2; | |
537 | } | |
538 | cyapa_gen6_set_interval_setting(cyapa, | |
539 | interval_setting); | |
540 | } | |
541 | ||
542 | error = cyapa_gen6_change_power_state(cyapa, lp_mode); | |
543 | if (error) { | |
544 | dev_err(dev, "set power state to 0x%02x failed: %d\n", | |
545 | lp_mode, error); | |
546 | goto out; | |
547 | } | |
548 | ||
549 | PIP_DEV_SET_SLEEP_TIME(cyapa, sleep_time); | |
550 | PIP_DEV_SET_PWR_STATE(cyapa, | |
551 | cyapa_sleep_time_to_pwr_cmd(sleep_time)); | |
552 | } | |
553 | ||
554 | out: | |
555 | cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); | |
556 | return error; | |
557 | } | |
558 | ||
559 | static int cyapa_gen6_initialize(struct cyapa *cyapa) | |
560 | { | |
561 | return 0; | |
562 | } | |
563 | ||
564 | static int cyapa_pip_retrieve_data_structure(struct cyapa *cyapa, | |
565 | u16 read_offset, u16 read_len, u8 data_id, | |
566 | u8 *data, int *data_buf_lens) | |
567 | { | |
568 | struct retrieve_data_struct_cmd { | |
569 | struct pip_app_cmd_head head; | |
570 | __le16 read_offset; | |
571 | __le16 read_length; | |
572 | u8 data_id; | |
573 | } __packed cmd; | |
574 | u8 resp_data[GEN6_MAX_RX_NUM + 10]; | |
575 | int resp_len; | |
576 | int error; | |
577 | ||
578 | memset(&cmd, 0, sizeof(cmd)); | |
579 | put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &cmd.head.addr); | |
580 | put_unaligned_le16(sizeof(cmd), &cmd.head.length - 2); | |
581 | cmd.head.report_id = PIP_APP_CMD_REPORT_ID; | |
582 | cmd.head.cmd_code = PIP_RETRIEVE_DATA_STRUCTURE; | |
583 | put_unaligned_le16(read_offset, &cmd.read_offset); | |
584 | put_unaligned_le16(read_len, &cmd.read_length); | |
585 | cmd.data_id = data_id; | |
586 | ||
587 | resp_len = sizeof(resp_data); | |
588 | error = cyapa_i2c_pip_cmd_irq_sync(cyapa, | |
589 | (u8 *)&cmd, sizeof(cmd), | |
590 | resp_data, &resp_len, | |
591 | 500, cyapa_sort_tsg_pip_app_resp_data, | |
592 | true); | |
593 | if (error || !PIP_CMD_COMPLETE_SUCCESS(resp_data) || | |
594 | resp_data[6] != data_id || | |
595 | !VALID_CMD_RESP_HEADER(resp_data, PIP_RETRIEVE_DATA_STRUCTURE)) | |
596 | return (error < 0) ? error : -EAGAIN; | |
597 | ||
598 | read_len = get_unaligned_le16(&resp_data[7]); | |
599 | if (*data_buf_lens < read_len) { | |
600 | *data_buf_lens = read_len; | |
601 | return -ENOBUFS; | |
602 | } | |
603 | ||
604 | memcpy(data, &resp_data[10], read_len); | |
605 | *data_buf_lens = read_len; | |
606 | return 0; | |
607 | } | |
608 | ||
609 | static ssize_t cyapa_gen6_show_baseline(struct device *dev, | |
610 | struct device_attribute *attr, char *buf) | |
611 | { | |
612 | struct cyapa *cyapa = dev_get_drvdata(dev); | |
613 | u8 data[GEN6_MAX_RX_NUM]; | |
614 | int data_len; | |
615 | int size = 0; | |
616 | int i; | |
617 | int error; | |
618 | int resume_error; | |
619 | ||
620 | if (!cyapa_is_pip_app_mode(cyapa)) | |
621 | return -EBUSY; | |
622 | ||
623 | /* 1. Suspend Scanning*/ | |
624 | error = cyapa_pip_suspend_scanning(cyapa); | |
625 | if (error) | |
626 | return error; | |
627 | ||
628 | /* 2. IDAC and RX Attenuator Calibration Data (Center Frequency). */ | |
629 | data_len = sizeof(data); | |
630 | error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len, | |
631 | GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC, | |
632 | data, &data_len); | |
633 | if (error) | |
634 | goto resume_scanning; | |
635 | ||
636 | size = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d ", | |
637 | data[0], /* RX Attenuator Mutual */ | |
638 | data[1], /* IDAC Mutual */ | |
639 | data[2], /* RX Attenuator Self RX */ | |
640 | data[3], /* IDAC Self RX */ | |
641 | data[4], /* RX Attenuator Self TX */ | |
642 | data[5] /* IDAC Self TX */ | |
643 | ); | |
644 | ||
645 | /* 3. Read Attenuator Trim. */ | |
646 | data_len = sizeof(data); | |
647 | error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len, | |
648 | GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM, | |
649 | data, &data_len); | |
650 | if (error) | |
651 | goto resume_scanning; | |
652 | ||
653 | /* set attenuator trim values. */ | |
654 | for (i = 0; i < data_len; i++) | |
655 | size += scnprintf(buf + size, PAGE_SIZE - size, "%d ", data[i]); | |
656 | size += scnprintf(buf + size, PAGE_SIZE - size, "\n"); | |
657 | ||
658 | resume_scanning: | |
659 | /* 4. Resume Scanning*/ | |
660 | resume_error = cyapa_pip_resume_scanning(cyapa); | |
661 | if (resume_error || error) { | |
662 | memset(buf, 0, PAGE_SIZE); | |
663 | return resume_error ? resume_error : error; | |
664 | } | |
665 | ||
666 | return size; | |
667 | } | |
668 | ||
669 | static int cyapa_gen6_operational_check(struct cyapa *cyapa) | |
670 | { | |
671 | struct device *dev = &cyapa->client->dev; | |
672 | int error; | |
673 | ||
674 | if (cyapa->gen != CYAPA_GEN6) | |
675 | return -ENODEV; | |
676 | ||
677 | switch (cyapa->state) { | |
678 | case CYAPA_STATE_GEN6_BL: | |
679 | error = cyapa_pip_bl_exit(cyapa); | |
680 | if (error) { | |
681 | /* Try to update trackpad product information. */ | |
682 | cyapa_gen6_bl_read_app_info(cyapa); | |
683 | goto out; | |
684 | } | |
685 | ||
686 | cyapa->state = CYAPA_STATE_GEN6_APP; | |
687 | ||
688 | case CYAPA_STATE_GEN6_APP: | |
689 | /* | |
690 | * If trackpad device in deep sleep mode, | |
691 | * the app command will fail. | |
692 | * So always try to reset trackpad device to full active when | |
693 | * the device state is required. | |
694 | */ | |
695 | error = cyapa_gen6_set_power_mode(cyapa, | |
757cae5a | 696 | PWR_MODE_FULL_ACTIVE, 0, false); |
c2c06c41 DD |
697 | if (error) |
698 | dev_warn(dev, "%s: failed to set power active mode.\n", | |
699 | __func__); | |
700 | ||
945525ee DD |
701 | /* By default, the trackpad proximity function is enabled. */ |
702 | error = cyapa_pip_set_proximity(cyapa, true); | |
703 | if (error) | |
704 | dev_warn(dev, "%s: failed to enable proximity.\n", | |
705 | __func__); | |
706 | ||
c2c06c41 DD |
707 | /* Get trackpad product information. */ |
708 | error = cyapa_gen6_read_sys_info(cyapa); | |
709 | if (error) | |
710 | goto out; | |
711 | /* Only support product ID starting with CYTRA */ | |
712 | if (memcmp(cyapa->product_id, product_id, | |
713 | strlen(product_id)) != 0) { | |
714 | dev_err(dev, "%s: unknown product ID (%s)\n", | |
715 | __func__, cyapa->product_id); | |
716 | error = -EINVAL; | |
717 | } | |
718 | break; | |
719 | default: | |
720 | error = -EINVAL; | |
721 | } | |
722 | ||
723 | out: | |
724 | return error; | |
725 | } | |
726 | ||
727 | const struct cyapa_dev_ops cyapa_gen6_ops = { | |
728 | .check_fw = cyapa_pip_check_fw, | |
729 | .bl_enter = cyapa_pip_bl_enter, | |
730 | .bl_initiate = cyapa_pip_bl_initiate, | |
731 | .update_fw = cyapa_pip_do_fw_update, | |
732 | .bl_activate = cyapa_pip_bl_activate, | |
733 | .bl_deactivate = cyapa_pip_bl_deactivate, | |
734 | ||
735 | .show_baseline = cyapa_gen6_show_baseline, | |
736 | .calibrate_store = cyapa_pip_do_calibrate, | |
737 | ||
738 | .initialize = cyapa_gen6_initialize, | |
739 | ||
740 | .state_parse = cyapa_pip_state_parse, | |
741 | .operational_check = cyapa_gen6_operational_check, | |
742 | ||
743 | .irq_handler = cyapa_pip_irq_handler, | |
744 | .irq_cmd_handler = cyapa_pip_irq_cmd_handler, | |
745 | .sort_empty_output_data = cyapa_empty_pip_output_data, | |
746 | .set_power_mode = cyapa_gen6_set_power_mode, | |
945525ee DD |
747 | |
748 | .set_proximity = cyapa_gen6_set_proximity, | |
c2c06c41 | 749 | }; |