| /* |
| * cyttsp5_core.c |
| * Parade TrueTouch(TM) Standard Product V5 Core Module. |
| * For use with Parade touchscreen controllers. |
| * Supported parts include: |
| * CYTMA5XX |
| * CYTMA448 |
| * CYTMA445A |
| * CYTT21XXX |
| * CYTT31XXX |
| * |
| * Copyright (c) 2019 The Linux Foundation. All rights reserved. |
| * Copyright (C) 2015 Parade Technologies |
| * Copyright (C) 2012-2015 Cypress Semiconductor |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * version 2, and only version 2, as published by the |
| * Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * Contact Parade Technologies at www.paradetech.com <ttdrivers@paradetech.com> |
| * |
| */ |
| |
| #include <linux/kthread.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/semaphore.h> |
| #include <linux/timer.h> |
| #include <linux/workqueue.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/gpio.h> |
| #include <linux/uaccess.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/input/mt.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_irq.h> |
| #include <linux/io.h> |
| #include <linux/power_supply.h> |
| #include "cyttsp5_regs.h" |
| |
| #define CY_GES_WAKEUP |
| #define CY_CORE_STARTUP_RETRY_COUNT 3 |
| |
| #define PINCTRL_STATE_ACTIVE "pmx_ts_active" |
| #define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" |
| #define PINCTRL_STATE_RELEASE "pmx_ts_release" |
| |
| #define FT_VTG_MIN_UV 2600000 |
| #define FT_VTG_MAX_UV 3300000 |
| #define FT_I2C_VTG_MIN_UV 1800000 |
| #define FT_I2C_VTG_MAX_UV 1800000 |
| |
| #define USE_FB_SUSPEND_RESUME |
| #define PATCH_WAKEUP_NACK_ISSUE |
| |
| MODULE_FIRMWARE(CY_FW_FILE_NAME); |
| |
| static const char *cy_driver_core_name = CYTTSP5_CORE_NAME; |
| static const char *cy_driver_core_version = CY_DRIVER_VERSION; |
| static const char *cy_driver_core_date = CY_DRIVER_DATE; |
| static bool cyttsp5_first_probe = true; |
| static bool is_cyttsp5_probe_success; |
| static const struct cyttsp5_bus_ops *cyttsp5_bus_ops_save; |
| |
| struct cyttsp5_hid_field { |
| int report_count; |
| int report_size; |
| int size; /* report_count * report_size */ |
| int offset; |
| int data_type; |
| int logical_min; |
| int logical_max; |
| /* Usage Page (Hi 16 bit) + Usage (Lo 16 bit) */ |
| u32 usage_page; |
| u32 collection_usage_pages[CY_HID_MAX_COLLECTIONS]; |
| struct cyttsp5_hid_report *report; |
| bool record_field; |
| }; |
| |
| struct cyttsp5_hid_report { |
| u8 id; |
| u8 type; |
| int size; |
| struct cyttsp5_hid_field *fields[CY_HID_MAX_FIELDS]; |
| int num_fields; |
| int record_field_index; |
| int header_size; |
| int record_size; |
| u32 usage_page; |
| }; |
| |
| struct atten_node { |
| struct list_head node; |
| char *id; |
| struct device *dev; |
| |
| int (*func)(struct device *); |
| int mode; |
| }; |
| |
| struct param_node { |
| struct list_head node; |
| u8 id; |
| u32 value; |
| u8 size; |
| }; |
| |
| struct module_node { |
| struct list_head node; |
| struct cyttsp5_module *module; |
| void *data; |
| }; |
| |
| struct cyttsp5_hid_cmd { |
| u8 opcode; |
| u8 report_type; |
| union { |
| u8 report_id; |
| u8 power_state; |
| }; |
| u8 has_data_register; |
| size_t write_length; |
| u8 *write_buf; |
| u8 *read_buf; |
| u8 wait_interrupt; |
| u8 reset_cmd; |
| u16 timeout_ms; |
| }; |
| |
| struct cyttsp5_hid_output { |
| u8 cmd_type; |
| u16 length; |
| u8 command_code; |
| size_t write_length; |
| u8 *write_buf; |
| u8 novalidate; |
| u8 reset_expected; |
| u16 timeout_ms; |
| }; |
| |
| #define SET_CMD_OPCODE(byte, opcode) SET_CMD_LOW(byte, opcode) |
| #define SET_CMD_REPORT_TYPE(byte, type) SET_CMD_HIGH(byte, ((type) << 4)) |
| #define SET_CMD_REPORT_ID(byte, id) SET_CMD_LOW(byte, id) |
| |
| #define HID_OUTPUT_APP_COMMAND(command) \ |
| .cmd_type = HID_OUTPUT_CMD_APP, \ |
| .command_code = command |
| |
| #define HID_OUTPUT_BL_COMMAND(command) \ |
| .cmd_type = HID_OUTPUT_CMD_BL, \ |
| .command_code = command |
| |
| #ifdef VERBOSE_DEBUG |
| void cyttsp5_pr_buf(struct device *dev, u8 *dptr, int size, |
| const char *data_name) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| u8 *pr_buf = cd->pr_buf; |
| int i, k; |
| const char fmt[] = "%02X "; |
| int max; |
| |
| if (!size) |
| return; |
| |
| max = (CY_MAX_PRBUF_SIZE - 1) - sizeof(CY_PR_TRUNCATED); |
| |
| pr_buf[0] = 0; |
| for (i = k = 0; i < size && k < max; i++, k += 3) |
| scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, dptr[i]); |
| |
| if (size) |
| parade_debug(dev, DEBUG_LEVEL_2, "%s: %s[0..%d]=%s%s\n", |
| __func__, data_name, |
| size - 1, pr_buf, size <= max ? "" : CY_PR_TRUNCATED); |
| else |
| parade_debug(dev, DEBUG_LEVEL_2, "%s: %s[]\n", |
| __func__, data_name); |
| |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_pr_buf); |
| #endif |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| static int tthe_print(struct cyttsp5_core_data *cd, u8 *buf, int buf_len, |
| const u8 *data_name) |
| { |
| int len = strlen(data_name); |
| int i, n; |
| u8 *p; |
| int remain; |
| u8 data_name_with_time_stamp[100]; |
| |
| if (cd->show_timestamp) { |
| snprintf(data_name_with_time_stamp, |
| ARRAY_SIZE(data_name_with_time_stamp), "[%u] %s", |
| jiffies_to_msecs(jiffies), data_name); |
| data_name = data_name_with_time_stamp; |
| len = strlen(data_name); |
| } |
| |
| mutex_lock(&cd->tthe_lock); |
| if (!cd->tthe_buf) |
| goto exit; |
| |
| if (cd->tthe_buf_len + (len + buf_len) > CY_MAX_PRBUF_SIZE) |
| goto exit; |
| |
| if (len + buf_len == 0) |
| goto exit; |
| |
| remain = CY_MAX_PRBUF_SIZE - cd->tthe_buf_len; |
| if (remain < len) |
| len = remain; |
| |
| p = cd->tthe_buf + cd->tthe_buf_len; |
| memcpy(p, data_name, len); |
| cd->tthe_buf_len += len; |
| p += len; |
| remain -= len; |
| |
| *p = 0; |
| for (i = 0; i < buf_len; i++) { |
| n = scnprintf(p, remain, "%02X ", buf[i]); |
| if (!n) |
| break; |
| p += n; |
| remain -= n; |
| cd->tthe_buf_len += n; |
| } |
| |
| n = scnprintf(p, remain, "\n"); |
| if (!n) |
| cd->tthe_buf[cd->tthe_buf_len] = 0; |
| cd->tthe_buf_len += n; |
| wake_up(&cd->wait_q); |
| exit: |
| mutex_unlock(&cd->tthe_lock); |
| return 0; |
| } |
| |
| static int _cyttsp5_request_tthe_print(struct device *dev, u8 *buf, |
| int buf_len, const u8 *data_name) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| return tthe_print(cd, buf, buf_len, data_name); |
| } |
| #endif |
| |
| /* |
| * cyttsp5_platform_detect_read() |
| * |
| * This function is passed to platform detect |
| * function to perform a read operation |
| */ |
| static int cyttsp5_platform_detect_read(struct device *dev, void *buf, int size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| return cyttsp5_adap_read_default(cd, buf, size); |
| } |
| |
| /* Must be called with cd->hid_report_lock acquired */ |
| static struct cyttsp5_hid_report *cyttsp5_get_hid_report_( |
| struct cyttsp5_core_data *cd, u8 report_type, u8 report_id, |
| bool create) |
| { |
| struct cyttsp5_hid_report *report = NULL; |
| int i; |
| |
| /* Look for created reports */ |
| for (i = 0; i < cd->num_hid_reports; i++) |
| if (cd->hid_reports[i]->type == report_type |
| && cd->hid_reports[i]->id == report_id) |
| return cd->hid_reports[i]; |
| |
| /* Create a new report */ |
| if (create && cd->num_hid_reports < CY_HID_MAX_REPORTS) { |
| report = kzalloc(sizeof(struct cyttsp5_hid_report), |
| GFP_KERNEL); |
| if (!report) |
| return NULL; |
| |
| report->type = report_type; |
| report->id = report_id; |
| cd->hid_reports[cd->num_hid_reports++] = report; |
| } |
| |
| return report; |
| } |
| |
| /* Must be called with cd->hid_report_lock acquired */ |
| static void cyttsp5_free_hid_reports_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_report *report; |
| int i, j; |
| |
| for (i = 0; i < cd->num_hid_reports; i++) { |
| report = cd->hid_reports[i]; |
| if (!report) |
| continue; |
| for (j = 0; j < report->num_fields; j++) { |
| kfree(report->fields[j]); |
| report->fields[j] = NULL; |
| } |
| kfree(report); |
| cd->hid_reports[i] = NULL; |
| } |
| |
| cd->num_hid_reports = 0; |
| } |
| |
| static void cyttsp5_free_hid_reports(struct cyttsp5_core_data *cd) |
| { |
| mutex_lock(&cd->hid_report_lock); |
| cyttsp5_free_hid_reports_(cd); |
| mutex_unlock(&cd->hid_report_lock); |
| } |
| |
| /* Must be called with cd->hid_report_lock acquired */ |
| static struct cyttsp5_hid_field *cyttsp5_create_hid_field_( |
| struct cyttsp5_hid_report *report) |
| { |
| struct cyttsp5_hid_field *field; |
| |
| if (!report) |
| return NULL; |
| |
| if (report->num_fields == CY_HID_MAX_FIELDS) |
| return NULL; |
| |
| field = kzalloc(sizeof(struct cyttsp5_hid_field), GFP_KERNEL); |
| if (!field) |
| return NULL; |
| |
| field->report = report; |
| |
| report->fields[report->num_fields++] = field; |
| |
| return field; |
| } |
| |
| static int cyttsp5_add_parameter(struct cyttsp5_core_data *cd, |
| u8 param_id, u32 param_value, u8 param_size) |
| { |
| struct param_node *param, *param_new; |
| |
| /* Check if parameter exists */ |
| spin_lock(&cd->spinlock); |
| list_for_each_entry(param, &cd->param_list, node) { |
| if (param->id == param_id) { |
| /* Update parameter */ |
| param->value = param_value; |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: Update parameter id:%d value:%d size:%d\n", |
| __func__, param_id, param_value, param_size); |
| goto exit_unlock; |
| } |
| } |
| spin_unlock(&cd->spinlock); |
| |
| param_new = kzalloc(sizeof(*param_new), GFP_KERNEL); |
| if (!param_new) |
| return -ENOMEM; |
| |
| param_new->id = param_id; |
| param_new->value = param_value; |
| param_new->size = param_size; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: Add parameter id:%d value:%d size:%d\n", |
| __func__, param_id, param_value, param_size); |
| |
| spin_lock(&cd->spinlock); |
| list_add(¶m_new->node, &cd->param_list); |
| exit_unlock: |
| spin_unlock(&cd->spinlock); |
| |
| return 0; |
| } |
| |
| int request_exclusive(struct cyttsp5_core_data *cd, void *ownptr, |
| int timeout_ms) |
| { |
| int t = msecs_to_jiffies(timeout_ms); |
| bool with_timeout = (timeout_ms != 0); |
| |
| mutex_lock(&cd->system_lock); |
| if (!cd->exclusive_dev && cd->exclusive_waits == 0) { |
| cd->exclusive_dev = ownptr; |
| goto exit; |
| } |
| |
| cd->exclusive_waits++; |
| wait: |
| mutex_unlock(&cd->system_lock); |
| if (with_timeout) { |
| t = wait_event_timeout(cd->wait_q, !cd->exclusive_dev, t); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: tmo waiting exclusive access\n", |
| __func__); |
| return -ETIME; |
| } |
| } else |
| wait_event(cd->wait_q, !cd->exclusive_dev); |
| |
| mutex_lock(&cd->system_lock); |
| if (cd->exclusive_dev) |
| goto wait; |
| cd->exclusive_dev = ownptr; |
| cd->exclusive_waits--; |
| exit: |
| mutex_unlock(&cd->system_lock); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: request_exclusive ok=%pK\n", |
| __func__, ownptr); |
| |
| return 0; |
| } |
| |
| static int release_exclusive_(struct cyttsp5_core_data *cd, void *ownptr) |
| { |
| if (cd->exclusive_dev != ownptr) |
| return -EINVAL; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: exclusive_dev %pK freed\n", |
| __func__, cd->exclusive_dev); |
| cd->exclusive_dev = NULL; |
| wake_up(&cd->wait_q); |
| return 0; |
| } |
| |
| /* |
| * returns error if was not owned |
| */ |
| int release_exclusive(struct cyttsp5_core_data *cd, void *ownptr) |
| { |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| rc = release_exclusive_(cd, ownptr); |
| mutex_unlock(&cd->system_lock); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_exec_cmd_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_cmd *hid_cmd) |
| { |
| int rc; |
| u8 *cmd; |
| u8 cmd_length; |
| u8 cmd_offset = 0; |
| |
| cmd_length = 2 /* command register */ |
| + 2 /* command */ |
| + (hid_cmd->report_id >= 0XF ? 1 : 0) /* Report ID */ |
| + (hid_cmd->has_data_register ? 2 : 0) /* Data register */ |
| + hid_cmd->write_length; /* Data length */ |
| |
| cmd = kzalloc(cmd_length, GFP_KERNEL); |
| if (!cmd) |
| return -ENOMEM; |
| |
| /* Set Command register */ |
| memcpy(&cmd[cmd_offset], &cd->hid_desc.command_register, |
| sizeof(cd->hid_desc.command_register)); |
| cmd_offset += sizeof(cd->hid_desc.command_register); |
| |
| /* Set Command */ |
| SET_CMD_REPORT_TYPE(cmd[cmd_offset], hid_cmd->report_type); |
| |
| if (hid_cmd->report_id >= 0XF) |
| SET_CMD_REPORT_ID(cmd[cmd_offset], 0xF); |
| else |
| SET_CMD_REPORT_ID(cmd[cmd_offset], hid_cmd->report_id); |
| cmd_offset++; |
| |
| SET_CMD_OPCODE(cmd[cmd_offset], hid_cmd->opcode); |
| cmd_offset++; |
| |
| if (hid_cmd->report_id >= 0XF) { |
| cmd[cmd_offset] = hid_cmd->report_id; |
| cmd_offset++; |
| } |
| |
| /* Set Data register */ |
| if (hid_cmd->has_data_register) { |
| memcpy(&cmd[cmd_offset], &cd->hid_desc.data_register, |
| sizeof(cd->hid_desc.data_register)); |
| cmd_offset += sizeof(cd->hid_desc.data_register); |
| } |
| |
| /* Set Data */ |
| if (hid_cmd->write_length && hid_cmd->write_buf) { |
| memcpy(&cmd[cmd_offset], hid_cmd->write_buf, |
| hid_cmd->write_length); |
| cmd_offset += hid_cmd->write_length; |
| } |
| |
| rc = cyttsp5_adap_write_read_specific(cd, cmd_length, cmd, |
| hid_cmd->read_buf); |
| if (rc) |
| dev_err(cd->dev, "%s: Fail cyttsp5_adap_transfer\n", __func__); |
| |
| kfree(cmd); |
| return rc; |
| } |
| |
| static int cyttsp5_hid_exec_cmd_and_wait_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_cmd *hid_cmd) |
| { |
| int rc; |
| int t; |
| u16 timeout_ms; |
| int *cmd_state; |
| |
| if (hid_cmd->reset_cmd) |
| cmd_state = &cd->hid_reset_cmd_state; |
| else |
| cmd_state = &cd->hid_cmd_state; |
| |
| if (hid_cmd->wait_interrupt) { |
| mutex_lock(&cd->system_lock); |
| *cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| rc = cyttsp5_hid_exec_cmd_(cd, hid_cmd); |
| if (rc) { |
| if (hid_cmd->wait_interrupt) |
| goto error; |
| |
| goto exit; |
| } |
| |
| if (!hid_cmd->wait_interrupt) |
| goto exit; |
| |
| if (hid_cmd->timeout_ms) |
| timeout_ms = hid_cmd->timeout_ms; |
| else |
| timeout_ms = CY_HID_RESET_TIMEOUT; |
| |
| t = wait_event_timeout(cd->wait_q, (*cmd_state == 0), |
| msecs_to_jiffies(timeout_ms)); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: HID output cmd execution timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| *cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_hid_cmd_reset_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_cmd hid_cmd = { |
| .opcode = HID_CMD_RESET, |
| .wait_interrupt = 1, |
| .reset_cmd = 1, |
| .timeout_ms = CY_HID_RESET_TIMEOUT, |
| }; |
| |
| return cyttsp5_hid_exec_cmd_and_wait_(cd, &hid_cmd); |
| } |
| |
| static int cyttsp5_hid_cmd_reset(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_cmd_reset_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_cmd_set_power_(struct cyttsp5_core_data *cd, |
| u8 power_state) |
| { |
| int rc; |
| struct cyttsp5_hid_cmd hid_cmd = { |
| .opcode = HID_CMD_SET_POWER, |
| .wait_interrupt = 1, |
| .timeout_ms = CY_HID_SET_POWER_TIMEOUT, |
| }; |
| hid_cmd.power_state = power_state; |
| |
| rc = cyttsp5_hid_exec_cmd_and_wait_(cd, &hid_cmd); |
| if (rc) { |
| dev_err(cd->dev, "%s: Failed to set power to state:%d\n", |
| __func__, power_state); |
| return rc; |
| } |
| |
| /* validate */ |
| if ((cd->response_buf[2] != HID_RESPONSE_REPORT_ID) |
| || ((cd->response_buf[3] & 0x3) != power_state) |
| || ((cd->response_buf[4] & 0xF) != HID_CMD_SET_POWER)) |
| rc = -EINVAL; |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_cmd_set_power(struct cyttsp5_core_data *cd, |
| u8 power_state) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_cmd_set_power_(cd, power_state); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static const u16 crc_table[16] = { |
| 0x0000, 0x1021, 0x2042, 0x3063, |
| 0x4084, 0x50a5, 0x60c6, 0x70e7, |
| 0x8108, 0x9129, 0xa14a, 0xb16b, |
| 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, |
| }; |
| |
| static u16 _cyttsp5_compute_crc(u8 *buf, u32 size) |
| { |
| u16 remainder = 0xFFFF; |
| u16 xor_mask = 0x0000; |
| u32 index; |
| u32 byte_value; |
| u32 table_index; |
| u32 crc_bit_width = sizeof(u16) * 8; |
| |
| /* Divide the message by polynomial, via the table. */ |
| for (index = 0; index < size; index++) { |
| byte_value = buf[index]; |
| table_index = ((byte_value >> 4) & 0x0F) |
| ^ (remainder >> (crc_bit_width - 4)); |
| remainder = crc_table[table_index] ^ (remainder << 4); |
| table_index = (byte_value & 0x0F) |
| ^ (remainder >> (crc_bit_width - 4)); |
| remainder = crc_table[table_index] ^ (remainder << 4); |
| } |
| |
| /* Perform the final remainder CRC. */ |
| return remainder ^ xor_mask; |
| } |
| |
| static int cyttsp5_hid_output_validate_bl_response( |
| struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| u16 size; |
| u16 crc; |
| u8 status; |
| |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| |
| if (hid_output->reset_expected && !size) |
| return 0; |
| |
| if (cd->response_buf[HID_OUTPUT_RESPONSE_REPORT_OFFSET] |
| != HID_BL_RESPONSE_REPORT_ID) { |
| dev_err(cd->dev, "%s: HID output response, wrong report_id\n", |
| __func__); |
| return -EPROTO; |
| } |
| |
| if (cd->response_buf[4] != HID_OUTPUT_BL_SOP) { |
| dev_err(cd->dev, "%s: HID output response, wrong SOP\n", |
| __func__); |
| return -EPROTO; |
| } |
| |
| if (cd->response_buf[size - 1] != HID_OUTPUT_BL_EOP) { |
| dev_err(cd->dev, "%s: HID output response, wrong EOP\n", |
| __func__); |
| return -EPROTO; |
| } |
| |
| crc = _cyttsp5_compute_crc(&cd->response_buf[4], size - 7); |
| if (cd->response_buf[size - 3] != LOW_BYTE(crc) |
| || cd->response_buf[size - 2] != HI_BYTE(crc)) { |
| dev_err(cd->dev, "%s: HID output response, wrong CRC 0x%X\n", |
| __func__, crc); |
| return -EPROTO; |
| } |
| |
| status = cd->response_buf[5]; |
| if (status) { |
| dev_err(cd->dev, "%s: HID output response, ERROR:%d\n", |
| __func__, status); |
| return -EPROTO; |
| } |
| |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_validate_app_response( |
| struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| int command_code; |
| u16 size; |
| |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| |
| if (hid_output->reset_expected && !size) |
| return 0; |
| |
| if (cd->response_buf[HID_OUTPUT_RESPONSE_REPORT_OFFSET] |
| != HID_APP_RESPONSE_REPORT_ID) { |
| dev_err(cd->dev, "%s: HID output response, wrong report_id\n", |
| __func__); |
| return -EPROTO; |
| } |
| |
| command_code = cd->response_buf[HID_OUTPUT_RESPONSE_CMD_OFFSET] |
| & HID_OUTPUT_RESPONSE_CMD_MASK; |
| if (command_code != hid_output->command_code) { |
| dev_err(cd->dev, |
| "%s: HID output response, wrong command_code:%X\n", |
| __func__, command_code); |
| return -EPROTO; |
| } |
| |
| return 0; |
| } |
| |
| static void cyttsp5_check_set_parameter(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output, bool raw) |
| { |
| u8 *param_buf; |
| u32 param_value = 0; |
| u8 param_size; |
| u8 param_id; |
| int i = 0; |
| |
| if (!(cd->cpdata->flags & CY_CORE_FLAG_RESTORE_PARAMETERS)) |
| return; |
| |
| /* Check command input for Set Parameter command */ |
| if (raw && hid_output->length >= 10 && hid_output->length <= 13 |
| && !memcmp(&hid_output->write_buf[0], |
| &cd->hid_desc.output_register, |
| sizeof(cd->hid_desc.output_register)) |
| && hid_output->write_buf[4] == |
| HID_APP_OUTPUT_REPORT_ID |
| && hid_output->write_buf[6] == |
| HID_OUTPUT_SET_PARAM) |
| param_buf = &hid_output->write_buf[7]; |
| else if (!raw && hid_output->cmd_type == HID_OUTPUT_CMD_APP |
| && hid_output->command_code == HID_OUTPUT_SET_PARAM |
| && hid_output->write_length >= 3 |
| && hid_output->write_length <= 6) |
| param_buf = &hid_output->write_buf[0]; |
| else |
| return; |
| |
| /* Get parameter ID, size and value */ |
| param_id = param_buf[0]; |
| param_size = param_buf[1]; |
| if (param_size > 4) { |
| dev_err(cd->dev, "%s: Invalid parameter size\n", __func__); |
| return; |
| } |
| |
| param_buf = ¶m_buf[2]; |
| while (i < param_size) |
| param_value += *(param_buf++) << (8 * i++); |
| |
| /* Check command response for Set Parameter command */ |
| if (cd->response_buf[2] != HID_APP_RESPONSE_REPORT_ID |
| || (cd->response_buf[4] & HID_OUTPUT_CMD_MASK) != |
| HID_OUTPUT_SET_PARAM |
| || cd->response_buf[5] != param_id |
| || cd->response_buf[6] != param_size) { |
| dev_err(cd->dev, "%s: Set Parameter command not successful\n", |
| __func__); |
| return; |
| } |
| |
| cyttsp5_add_parameter(cd, param_id, param_value, param_size); |
| } |
| |
| static void cyttsp5_check_command(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output, bool raw) |
| { |
| cyttsp5_check_set_parameter(cd, hid_output, raw); |
| } |
| |
| static int cyttsp5_hid_output_validate_response(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) |
| return cyttsp5_hid_output_validate_bl_response(cd, hid_output); |
| |
| return cyttsp5_hid_output_validate_app_response(cd, hid_output); |
| |
| } |
| |
| static int cyttsp5_hid_send_output_user_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| int rc; |
| |
| if (!hid_output->length || !hid_output->write_buf) |
| return -EINVAL; |
| |
| rc = cyttsp5_adap_write_read_specific(cd, hid_output->length, |
| hid_output->write_buf, NULL); |
| if (rc) |
| dev_err(cd->dev, "%s: Fail cyttsp5_adap_transfer\n", __func__); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_send_output_user_and_wait_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| int rc; |
| int t; |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = HID_OUTPUT_USER_CMD + 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = cyttsp5_hid_send_output_user_(cd, hid_output); |
| if (rc) |
| goto error; |
| |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(CY_HID_OUTPUT_USER_TIMEOUT)); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: HID output cmd execution timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| cyttsp5_check_command(cd, hid_output, true); |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_hid_send_output_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| int rc; |
| u8 *cmd; |
| u16 length; |
| u8 report_id; |
| u8 cmd_offset = 0; |
| u16 crc; |
| u8 cmd_allocated = 0; |
| |
| switch (hid_output->cmd_type) { |
| case HID_OUTPUT_CMD_APP: |
| report_id = HID_APP_OUTPUT_REPORT_ID; |
| length = 5; |
| break; |
| case HID_OUTPUT_CMD_BL: |
| report_id = HID_BL_OUTPUT_REPORT_ID; |
| length = 11 /* 5 + SOP + LEN(2) + CRC(2) + EOP */; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| length += hid_output->write_length; |
| |
| if (length + 2 > CYTTSP5_PREALLOCATED_CMD_BUFFER) { |
| cmd = kzalloc(length + 2, GFP_KERNEL); |
| if (!cmd) |
| return -ENOMEM; |
| cmd_allocated = 1; |
| } else |
| cmd = cd->cmd_buf; |
| |
| /* Set Output register */ |
| memcpy(&cmd[cmd_offset], &cd->hid_desc.output_register, |
| sizeof(cd->hid_desc.output_register)); |
| cmd_offset += sizeof(cd->hid_desc.output_register); |
| |
| cmd[cmd_offset++] = LOW_BYTE(length); |
| cmd[cmd_offset++] = HI_BYTE(length); |
| cmd[cmd_offset++] = report_id; |
| cmd[cmd_offset++] = 0x0; /* reserved */ |
| if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) |
| cmd[cmd_offset++] = HID_OUTPUT_BL_SOP; |
| cmd[cmd_offset++] = hid_output->command_code; |
| |
| /* Set Data Length for bootloader */ |
| if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) { |
| cmd[cmd_offset++] = LOW_BYTE(hid_output->write_length); |
| cmd[cmd_offset++] = HI_BYTE(hid_output->write_length); |
| } |
| /* Set Data */ |
| if (hid_output->write_length && hid_output->write_buf) { |
| memcpy(&cmd[cmd_offset], hid_output->write_buf, |
| hid_output->write_length); |
| cmd_offset += hid_output->write_length; |
| } |
| if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) { |
| crc = _cyttsp5_compute_crc(&cmd[6], |
| hid_output->write_length + 4); |
| cmd[cmd_offset++] = LOW_BYTE(crc); |
| cmd[cmd_offset++] = HI_BYTE(crc); |
| cmd[cmd_offset++] = HID_OUTPUT_BL_EOP; |
| } |
| |
| cyttsp5_pr_buf(cd->dev, cmd, length + 2, "command"); |
| rc = cyttsp5_adap_write_read_specific(cd, length + 2, cmd, NULL); |
| if (rc) |
| dev_err(cd->dev, "%s: Fail cyttsp5_adap_transfer\n", __func__); |
| |
| if (cmd_allocated) |
| kfree(cmd); |
| return rc; |
| } |
| |
| static int cyttsp5_hid_send_output_and_wait_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_output *hid_output) |
| { |
| int rc; |
| int t; |
| #ifdef VERBOSE_DEBUG |
| u16 size; |
| #endif |
| u16 timeout_ms; |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = hid_output->command_code + 1; |
| mutex_unlock(&cd->system_lock); |
| |
| if (hid_output->timeout_ms) |
| timeout_ms = hid_output->timeout_ms; |
| else |
| timeout_ms = CY_HID_OUTPUT_TIMEOUT; |
| |
| rc = cyttsp5_hid_send_output_(cd, hid_output); |
| if (rc) |
| goto error; |
| |
| |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(timeout_ms)); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: HID output cmd execution timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| if (!hid_output->novalidate) |
| rc = cyttsp5_hid_output_validate_response(cd, hid_output); |
| |
| cyttsp5_check_command(cd, hid_output, false); |
| |
| #ifdef VERBOSE_DEBUG |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| cyttsp5_pr_buf(cd->dev, cd->response_buf, size, "return_buf"); |
| #endif |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_null_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_NULL), |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_null(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_null_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_start_bootloader_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_START_BOOTLOADER), |
| .timeout_ms = CY_HID_OUTPUT_START_BOOTLOADER_TIMEOUT, |
| .reset_expected = 1, |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_start_bootloader(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_start_bootloader_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_start_bl(struct device *dev, int protect) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_start_bootloader(cd); |
| |
| return cyttsp5_hid_output_start_bootloader_(cd); |
| } |
| |
| static void cyttsp5_si_get_cydata(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_cydata *cydata = &cd->sysinfo.cydata; |
| struct cyttsp5_cydata_dev *cydata_dev = |
| (struct cyttsp5_cydata_dev *) |
| &cd->response_buf[HID_SYSINFO_CYDATA_OFFSET]; |
| |
| cydata->pip_ver_major = cydata_dev->pip_ver_major; |
| cydata->pip_ver_minor = cydata_dev->pip_ver_minor; |
| cydata->bl_ver_major = cydata_dev->bl_ver_major; |
| cydata->bl_ver_minor = cydata_dev->bl_ver_minor; |
| cydata->fw_ver_major = cydata_dev->fw_ver_major; |
| cydata->fw_ver_minor = cydata_dev->fw_ver_minor; |
| |
| cydata->fw_pid = get_unaligned_le16(&cydata_dev->fw_pid); |
| cydata->fw_ver_conf = get_unaligned_le16(&cydata_dev->fw_ver_conf); |
| cydata->post_code = get_unaligned_le16(&cydata_dev->post_code); |
| cydata->revctrl = get_unaligned_le32(&cydata_dev->revctrl); |
| cydata->jtag_id_l = get_unaligned_le16(&cydata_dev->jtag_si_id_l); |
| cydata->jtag_id_h = get_unaligned_le16(&cydata_dev->jtag_si_id_h); |
| |
| memcpy(cydata->mfg_id, cydata_dev->mfg_id, CY_NUM_MFGID); |
| |
| cyttsp5_pr_buf(cd->dev, (u8 *)cydata_dev, |
| sizeof(struct cyttsp5_cydata_dev), "sysinfo_cydata"); |
| } |
| |
| static void cyttsp5_si_get_sensing_conf_data(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sensing_conf_data *scd = &cd->sysinfo.sensing_conf_data; |
| struct cyttsp5_sensing_conf_data_dev *scd_dev = |
| (struct cyttsp5_sensing_conf_data_dev *) |
| &cd->response_buf[HID_SYSINFO_SENSING_OFFSET]; |
| |
| scd->electrodes_x = scd_dev->electrodes_x; |
| scd->electrodes_y = scd_dev->electrodes_y; |
| scd->origin_x = scd_dev->origin_x; |
| scd->origin_y = scd_dev->origin_y; |
| |
| /* PIP 1.4 (001-82649 *Q) add X_IS_TX bit in X_ORG */ |
| if (scd->origin_x & 0x02) { |
| scd->tx_num = scd->electrodes_x; |
| scd->rx_num = scd->electrodes_y; |
| } else { |
| scd->tx_num = scd->electrodes_y; |
| scd->rx_num = scd->electrodes_x; |
| } |
| |
| scd->panel_id = scd_dev->panel_id; |
| scd->btn = scd_dev->btn; |
| scd->scan_mode = scd_dev->scan_mode; |
| scd->max_tch = scd_dev->max_num_of_tch_per_refresh_cycle; |
| |
| scd->res_x = get_unaligned_le16(&scd_dev->res_x); |
| scd->res_y = get_unaligned_le16(&scd_dev->res_y); |
| scd->max_z = get_unaligned_le16(&scd_dev->max_z); |
| scd->len_x = get_unaligned_le16(&scd_dev->len_x); |
| scd->len_y = get_unaligned_le16(&scd_dev->len_y); |
| |
| cyttsp5_pr_buf(cd->dev, (u8 *)scd_dev, |
| sizeof(struct cyttsp5_sensing_conf_data_dev), |
| "sensing_conf_data"); |
| } |
| |
| static int cyttsp5_si_setup(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| int max_tch = si->sensing_conf_data.max_tch; |
| |
| if (!si->xy_data) |
| si->xy_data = kzalloc(max_tch * si->desc.tch_record_size, |
| GFP_KERNEL); |
| if (!si->xy_data) |
| return -ENOMEM; |
| |
| if (!si->xy_mode) |
| si->xy_mode = kzalloc(si->desc.tch_header_size, GFP_KERNEL); |
| if (!si->xy_mode) { |
| kfree(si->xy_data); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int cyttsp5_si_get_btn_data(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| int num_btns = 0; |
| int num_defined_keys; |
| u16 *key_table; |
| int btn; |
| int i; |
| int rc = 0; |
| unsigned int btns = cd->response_buf[HID_SYSINFO_BTN_OFFSET] |
| & HID_SYSINFO_BTN_MASK; |
| size_t btn_keys_size; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: get btn data\n", __func__); |
| |
| for (i = 0; i < HID_SYSINFO_MAX_BTN; i++) |
| if (btns & (1 << i)) |
| num_btns++; |
| |
| si->num_btns = num_btns; |
| |
| if (num_btns) { |
| btn_keys_size = num_btns * sizeof(struct cyttsp5_btn); |
| if (!si->btn) |
| si->btn = kzalloc(btn_keys_size, GFP_KERNEL); |
| if (!si->btn) |
| return -ENOMEM; |
| |
| if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS] == NULL) |
| num_defined_keys = 0; |
| else if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS]->data == NULL) |
| num_defined_keys = 0; |
| else |
| num_defined_keys = cd->cpdata->sett |
| [CY_IC_GRPNUM_BTN_KEYS]->size; |
| |
| for (btn = 0; btn < num_btns && btn < num_defined_keys; btn++) { |
| key_table = (u16 *)cd->cpdata->sett |
| [CY_IC_GRPNUM_BTN_KEYS]->data; |
| si->btn[btn].key_code = key_table[btn]; |
| si->btn[btn].enabled = true; |
| } |
| for (; btn < num_btns; btn++) { |
| si->btn[btn].key_code = KEY_RESERVED; |
| si->btn[btn].enabled = true; |
| } |
| |
| return rc; |
| } |
| |
| kfree(si->btn); |
| si->btn = NULL; |
| return rc; |
| } |
| |
| static void cyttsp5_si_put_log_data(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| struct cyttsp5_cydata *cydata = &si->cydata; |
| struct cyttsp5_sensing_conf_data *scd = &si->sensing_conf_data; |
| int i; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: pip_ver_major =0x%02X (%d)\n", |
| __func__, cydata->pip_ver_major, cydata->pip_ver_major); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: pip_ver_minor =0x%02X (%d)\n", |
| __func__, cydata->pip_ver_minor, cydata->pip_ver_minor); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: fw_pid =0x%04X (%d)\n", |
| __func__, cydata->fw_pid, cydata->fw_pid); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: fw_ver_major =0x%02X (%d)\n", |
| __func__, cydata->fw_ver_major, cydata->fw_ver_major); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: fw_ver_minor =0x%02X (%d)\n", |
| __func__, cydata->fw_ver_minor, cydata->fw_ver_minor); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: revctrl =0x%08X (%d)\n", |
| __func__, cydata->revctrl, cydata->revctrl); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: fw_ver_conf =0x%04X (%d)\n", |
| __func__, cydata->fw_ver_conf, cydata->fw_ver_conf); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: bl_ver_major =0x%02X (%d)\n", |
| __func__, cydata->bl_ver_major, cydata->bl_ver_major); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: bl_ver_minor =0x%02X (%d)\n", |
| __func__, cydata->bl_ver_minor, cydata->bl_ver_minor); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: jtag_id_h =0x%04X (%d)\n", |
| __func__, cydata->jtag_id_h, cydata->jtag_id_h); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: jtag_id_l =0x%04X (%d)\n", |
| __func__, cydata->jtag_id_l, cydata->jtag_id_l); |
| |
| for (i = 0; i < CY_NUM_MFGID; i++) |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: mfg_id[%d] =0x%02X (%d)\n", |
| __func__, i, cydata->mfg_id[i], cydata->mfg_id[i]); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: post_code =0x%04X (%d)\n", |
| __func__, cydata->post_code, cydata->post_code); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: electrodes_x =0x%02X (%d)\n", |
| __func__, scd->electrodes_x, scd->electrodes_x); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: electrodes_y =0x%02X (%d)\n", |
| __func__, scd->electrodes_y, scd->electrodes_y); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: len_x =0x%04X (%d)\n", |
| __func__, scd->len_x, scd->len_x); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: len_y =0x%04X (%d)\n", |
| __func__, scd->len_y, scd->len_y); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: res_x =0x%04X (%d)\n", |
| __func__, scd->res_x, scd->res_x); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: res_y =0x%04X (%d)\n", |
| __func__, scd->res_y, scd->res_y); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: max_z =0x%04X (%d)\n", |
| __func__, scd->max_z, scd->max_z); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: origin_x =0x%02X (%d)\n", |
| __func__, scd->origin_x, scd->origin_x); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: origin_y =0x%02X (%d)\n", |
| __func__, scd->origin_y, scd->origin_y); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: panel_id =0x%02X (%d)\n", |
| __func__, scd->panel_id, scd->panel_id); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: btn =0x%02X (%d)\n", |
| __func__, scd->btn, scd->btn); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: scan_mode =0x%02X (%d)\n", |
| __func__, scd->scan_mode, scd->scan_mode); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: max_num_of_tch_per_refresh_cycle =0x%02X (%d)\n", |
| __func__, scd->max_tch, scd->max_tch); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: xy_mode =%pK\n", |
| __func__, si->xy_mode); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: xy_data =%pK\n", |
| __func__, si->xy_data); |
| } |
| |
| static int cyttsp5_get_sysinfo_regs(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| int rc; |
| |
| rc = cyttsp5_si_get_btn_data(cd); |
| if (rc < 0) |
| return rc; |
| |
| cyttsp5_si_get_cydata(cd); |
| |
| cyttsp5_si_get_sensing_conf_data(cd); |
| |
| cyttsp5_si_setup(cd); |
| |
| cyttsp5_si_put_log_data(cd); |
| |
| si->ready = true; |
| return rc; |
| } |
| |
| static void cyttsp5_free_si_ptrs(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| |
| kfree(si->btn); |
| kfree(si->xy_mode); |
| kfree(si->xy_data); |
| } |
| |
| static int cyttsp5_hid_output_get_sysinfo_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_SYSINFO), |
| .timeout_ms = CY_HID_OUTPUT_GET_SYSINFO_TIMEOUT, |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| rc = cyttsp5_get_sysinfo_regs(cd); |
| if (rc) |
| cyttsp5_free_si_ptrs(cd); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_get_sysinfo(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_get_sysinfo_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_suspend_scanning_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_SUSPEND_SCANNING), |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_suspend_scanning(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_suspend_scanning_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_suspend_scanning(struct device *dev, |
| int protect) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_suspend_scanning(cd); |
| |
| return cyttsp5_hid_output_suspend_scanning_(cd); |
| } |
| |
| static int cyttsp5_hid_output_resume_scanning_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_RESUME_SCANNING), |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_resume_scanning(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_resume_scanning_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_resume_scanning(struct device *dev, |
| int protect) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_resume_scanning(cd); |
| |
| return cyttsp5_hid_output_resume_scanning_(cd); |
| } |
| |
| static int cyttsp5_hid_output_get_param_(struct cyttsp5_core_data *cd, |
| u8 param_id, u32 *value) |
| { |
| int write_length = 1; |
| u8 param[1] = { param_id }; |
| u8 read_param_id; |
| int param_size; |
| u8 *ptr; |
| int rc; |
| int i; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_PARAM), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| read_param_id = cd->response_buf[5]; |
| if (read_param_id != param_id) |
| return -EPROTO; |
| |
| param_size = cd->response_buf[6]; |
| ptr = &cd->response_buf[7]; |
| *value = 0; |
| for (i = 0; i < param_size; i++) |
| *value += ptr[i] << (i * 8); |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_get_param(struct cyttsp5_core_data *cd, |
| u8 param_id, u32 *value) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_get_param_(cd, param_id, value); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| int _cyttsp5_request_hid_output_get_param(struct device *dev, |
| int protect, u8 param_id, u32 *value) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_get_param(cd, param_id, value); |
| |
| return cyttsp5_hid_output_get_param_(cd, param_id, value); |
| } |
| |
| static int cyttsp5_hid_output_set_param_(struct cyttsp5_core_data *cd, |
| u8 param_id, u32 value, u8 size) |
| { |
| u8 write_buf[6]; |
| u8 *ptr = &write_buf[2]; |
| int rc; |
| int i; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_SET_PARAM), |
| .write_buf = write_buf, |
| }; |
| |
| write_buf[0] = param_id; |
| write_buf[1] = size; |
| for (i = 0; i < size; i++) { |
| ptr[i] = value & 0xFF; |
| value = value >> 8; |
| } |
| |
| hid_output.write_length = 2 + size; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| if (param_id != cd->response_buf[5] || size != cd->response_buf[6]) |
| return -EPROTO; |
| |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_set_param(struct cyttsp5_core_data *cd, |
| u8 param_id, u32 value, u8 size) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_set_param_(cd, param_id, value, size); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| int _cyttsp5_request_hid_output_set_param(struct device *dev, |
| int protect, u8 param_id, u32 value, u8 size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_set_param(cd, param_id, value, size); |
| |
| return cyttsp5_hid_output_set_param_(cd, param_id, value, size); |
| } |
| |
| static int cyttsp5_hid_output_enter_easywake_state_( |
| struct cyttsp5_core_data *cd, u8 data, u8 *return_data) |
| { |
| int write_length = 1; |
| u8 param[1] = { data }; |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_ENTER_EASYWAKE_STATE), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| *return_data = cd->response_buf[5]; |
| return rc; |
| } |
| |
| #ifdef CY_GES_WAKEUP |
| static int cyttsp5_hid_output_exit_easywake_state_( |
| struct cyttsp5_core_data *cd, u8 data, u8 *return_data) |
| { |
| int write_length = 1; |
| u8 param[1] = { data }; |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_EXIT_EASYWAKE_STATE), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| *return_data = cd->response_buf[5]; |
| return rc; |
| } |
| #endif |
| |
| static int cyttsp5_hid_output_verify_config_block_crc_( |
| struct cyttsp5_core_data *cd, u8 ebid, u8 *status, |
| u16 *calculated_crc, u16 *stored_crc) |
| { |
| int write_length = 1; |
| u8 param[1] = { ebid }; |
| u8 *ptr; |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_VERIFY_CONFIG_BLOCK_CRC), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| ptr = &cd->response_buf[5]; |
| *status = ptr[0]; |
| *calculated_crc = get_unaligned_le16(&ptr[1]); |
| *stored_crc = get_unaligned_le16(&ptr[3]); |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_verify_config_block_crc( |
| struct cyttsp5_core_data *cd, u8 ebid, u8 *status, |
| u16 *calculated_crc, u16 *stored_crc) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_verify_config_block_crc_(cd, ebid, status, |
| calculated_crc, stored_crc); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_verify_config_block_crc( |
| struct device *dev, int protect, u8 ebid, u8 *status, |
| u16 *calculated_crc, u16 *stored_crc) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_verify_config_block_crc(cd, ebid, |
| status, calculated_crc, stored_crc); |
| |
| return cyttsp5_hid_output_verify_config_block_crc_(cd, ebid, |
| status, calculated_crc, stored_crc); |
| } |
| |
| static int cyttsp5_hid_output_get_config_row_size_(struct cyttsp5_core_data *cd, |
| u16 *row_size) |
| { |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_CONFIG_ROW_SIZE), |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| *row_size = get_unaligned_le16(&cd->response_buf[5]); |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_get_config_row_size(struct cyttsp5_core_data *cd, |
| u16 *row_size) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_get_config_row_size_(cd, row_size); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_get_config_row_size(struct device *dev, |
| int protect, u16 *row_size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_get_config_row_size(cd, row_size); |
| |
| return cyttsp5_hid_output_get_config_row_size_(cd, row_size); |
| } |
| |
| static int cyttsp5_hid_output_read_conf_block_(struct cyttsp5_core_data *cd, |
| u16 row_number, u16 length, u8 ebid, u8 *read_buf, u16 *crc) |
| { |
| int read_ebid; |
| int read_length; |
| int status; |
| int rc; |
| int write_length = 5; |
| u8 write_buf[5]; |
| u8 cmd_offset = 0; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_READ_CONF_BLOCK), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| }; |
| |
| write_buf[cmd_offset++] = LOW_BYTE(row_number); |
| write_buf[cmd_offset++] = HI_BYTE(row_number); |
| write_buf[cmd_offset++] = LOW_BYTE(length); |
| write_buf[cmd_offset++] = HI_BYTE(length); |
| write_buf[cmd_offset++] = ebid; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| status = cd->response_buf[5]; |
| if (status) |
| return -EINVAL; |
| |
| read_ebid = cd->response_buf[6]; |
| if ((read_ebid != ebid) || (cd->response_buf[9] != 0)) |
| return -EPROTO; |
| |
| read_length = get_unaligned_le16(&cd->response_buf[7]); |
| if (length < read_length) |
| length = read_length; |
| |
| memcpy(read_buf, &cd->response_buf[10], length); |
| *crc = get_unaligned_le16(&cd->response_buf[read_length + 10]); |
| |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_read_conf_ver_(struct cyttsp5_core_data *cd, |
| u16 *config_ver) |
| { |
| int rc; |
| u8 read_buf[CY_TTCONFIG_VERSION_OFFSET + CY_TTCONFIG_VERSION_SIZE]; |
| u16 crc; |
| |
| rc = cyttsp5_hid_output_read_conf_block_(cd, CY_TTCONFIG_VERSION_ROW, |
| CY_TTCONFIG_VERSION_OFFSET + CY_TTCONFIG_VERSION_SIZE, |
| CY_TCH_PARM_EBID, read_buf, &crc); |
| if (rc) |
| return rc; |
| |
| *config_ver = get_unaligned_le16( |
| &read_buf[CY_TTCONFIG_VERSION_OFFSET]); |
| |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_write_conf_block_(struct cyttsp5_core_data *cd, |
| u16 row_number, u16 write_length, u8 ebid, u8 *write_buf, |
| u8 *security_key, u16 *actual_write_len) |
| { |
| /* row_number + write_len + ebid + security_key + crc */ |
| int full_write_length = 2 + 2 + 1 + write_length + 8 + 2; |
| u8 *full_write_buf; |
| u8 cmd_offset = 0; |
| u16 crc; |
| int status; |
| int rc; |
| int read_ebid; |
| u8 *data; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_WRITE_CONF_BLOCK), |
| .write_length = full_write_length, |
| .timeout_ms = CY_HID_OUTPUT_WRITE_CONF_BLOCK_TIMEOUT, |
| }; |
| |
| full_write_buf = kzalloc(full_write_length, GFP_KERNEL); |
| if (!full_write_buf) |
| return -ENOMEM; |
| |
| hid_output.write_buf = full_write_buf; |
| full_write_buf[cmd_offset++] = LOW_BYTE(row_number); |
| full_write_buf[cmd_offset++] = HI_BYTE(row_number); |
| full_write_buf[cmd_offset++] = LOW_BYTE(write_length); |
| full_write_buf[cmd_offset++] = HI_BYTE(write_length); |
| full_write_buf[cmd_offset++] = ebid; |
| data = &full_write_buf[cmd_offset]; |
| memcpy(data, write_buf, write_length); |
| cmd_offset += write_length; |
| memcpy(&full_write_buf[cmd_offset], security_key, 8); |
| cmd_offset += 8; |
| crc = _cyttsp5_compute_crc(data, write_length); |
| full_write_buf[cmd_offset++] = LOW_BYTE(crc); |
| full_write_buf[cmd_offset++] = HI_BYTE(crc); |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| goto exit; |
| |
| status = cd->response_buf[5]; |
| if (status) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| read_ebid = cd->response_buf[6]; |
| if (read_ebid != ebid) { |
| rc = -EPROTO; |
| goto exit; |
| } |
| |
| *actual_write_len = get_unaligned_le16(&cd->response_buf[7]); |
| |
| exit: |
| kfree(full_write_buf); |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_write_conf_block(struct cyttsp5_core_data *cd, |
| u16 row_number, u16 write_length, u8 ebid, u8 *write_buf, |
| u8 *security_key, u16 *actual_write_len) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_write_conf_block_(cd, row_number, write_length, |
| ebid, write_buf, security_key, actual_write_len); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_write_conf_block(struct device *dev, |
| int protect, u16 row_number, u16 write_length, u8 ebid, |
| u8 *write_buf, u8 *security_key, u16 *actual_write_len) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_write_conf_block(cd, row_number, |
| write_length, ebid, write_buf, security_key, |
| actual_write_len); |
| |
| return cyttsp5_hid_output_write_conf_block_(cd, row_number, |
| write_length, ebid, write_buf, security_key, |
| actual_write_len); |
| } |
| |
| static int cyttsp5_hid_output_get_data_structure_( |
| struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, |
| u8 data_id, u8 *status, u8 *data_format, u16 *actual_read_len, |
| u8 *data) |
| { |
| int rc; |
| u16 total_read_len = 0; |
| u16 read_len; |
| u16 off_buf = 0; |
| u8 write_buf[5]; |
| u8 read_data_id; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_DATA_STRUCTURE), |
| .write_length = 5, |
| .write_buf = write_buf, |
| }; |
| |
| again: |
| write_buf[0] = LOW_BYTE(read_offset); |
| write_buf[1] = HI_BYTE(read_offset); |
| write_buf[2] = LOW_BYTE(read_length); |
| write_buf[3] = HI_BYTE(read_length); |
| write_buf[4] = data_id; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| if (cd->response_buf[5] != CY_CMD_STATUS_SUCCESS) |
| goto set_status; |
| |
| read_data_id = cd->response_buf[6]; |
| if (read_data_id != data_id) |
| return -EPROTO; |
| |
| read_len = get_unaligned_le16(&cd->response_buf[7]); |
| if (read_len && data) { |
| memcpy(&data[off_buf], &cd->response_buf[10], read_len); |
| |
| total_read_len += read_len; |
| |
| if (read_len < read_length) { |
| read_offset += read_len; |
| off_buf += read_len; |
| read_length -= read_len; |
| goto again; |
| } |
| } |
| |
| if (data_format) |
| *data_format = cd->response_buf[9]; |
| if (actual_read_len) |
| *actual_read_len = total_read_len; |
| set_status: |
| if (status) |
| *status = cd->response_buf[5]; |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_get_data_structure( |
| struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, |
| u8 data_id, u8 *status, u8 *data_format, u16 *actual_read_len, |
| u8 *data) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_get_data_structure_(cd, read_offset, |
| read_length, data_id, status, data_format, |
| actual_read_len, data); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_get_data_structure(struct device *dev, |
| int protect, u16 read_offset, u16 read_length, u8 data_id, |
| u8 *status, u8 *data_format, u16 *actual_read_len, u8 *data) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_get_data_structure(cd, |
| read_offset, read_length, data_id, status, |
| data_format, actual_read_len, data); |
| |
| return cyttsp5_hid_output_get_data_structure_(cd, |
| read_offset, read_length, data_id, status, |
| data_format, actual_read_len, data); |
| } |
| |
| static int cyttsp5_hid_output_run_selftest_( |
| struct cyttsp5_core_data *cd, u8 test_id, |
| u8 write_idacs_to_flash, u8 *status, u8 *summary_result, |
| u8 *results_available) |
| { |
| int rc; |
| u8 write_buf[2]; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_RUN_SELF_TEST), |
| .write_length = 2, |
| .write_buf = write_buf, |
| .timeout_ms = CY_HID_OUTPUT_RUN_SELF_TEST_TIMEOUT, |
| }; |
| |
| write_buf[0] = test_id; |
| write_buf[1] = write_idacs_to_flash; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| if (status) |
| *status = cd->response_buf[5]; |
| if (summary_result) |
| *summary_result = cd->response_buf[6]; |
| if (results_available) |
| *results_available = cd->response_buf[7]; |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_run_selftest( |
| struct cyttsp5_core_data *cd, u8 test_id, |
| u8 write_idacs_to_flash, u8 *status, u8 *summary_result, |
| u8 *results_available) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_run_selftest_(cd, test_id, |
| write_idacs_to_flash, status, summary_result, |
| results_available); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_run_selftest(struct device *dev, |
| int protect, u8 test_id, u8 write_idacs_to_flash, u8 *status, |
| u8 *summary_result, u8 *results_available) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_run_selftest(cd, test_id, |
| write_idacs_to_flash, status, summary_result, |
| results_available); |
| |
| return cyttsp5_hid_output_run_selftest_(cd, test_id, |
| write_idacs_to_flash, status, summary_result, |
| results_available); |
| } |
| |
| static int cyttsp5_hid_output_get_selftest_result_( |
| struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, |
| u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) |
| { |
| int rc; |
| u16 total_read_len = 0; |
| u16 read_len; |
| u16 off_buf = 0; |
| u8 write_buf[5]; |
| u8 read_test_id; |
| bool repeat; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_SELF_TEST_RESULT), |
| .write_length = 5, |
| .write_buf = write_buf, |
| }; |
| |
| /* |
| * Do not repeat reading for Auto Shorts test |
| * when PIP version < 1.3 |
| */ |
| repeat = IS_PIP_VER_GE(&cd->sysinfo, 1, 3) |
| || test_id != CY_ST_ID_AUTOSHORTS; |
| |
| again: |
| write_buf[0] = LOW_BYTE(read_offset); |
| write_buf[1] = HI_BYTE(read_offset); |
| write_buf[2] = LOW_BYTE(read_length); |
| write_buf[3] = HI_BYTE(read_length); |
| write_buf[4] = test_id; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| if (cd->response_buf[5] != CY_CMD_STATUS_SUCCESS) |
| goto set_status; |
| |
| read_test_id = cd->response_buf[6]; |
| if (read_test_id != test_id) |
| return -EPROTO; |
| |
| read_len = get_unaligned_le16(&cd->response_buf[7]); |
| if (read_len && data) { |
| memcpy(&data[off_buf], &cd->response_buf[10], read_len); |
| |
| total_read_len += read_len; |
| |
| if (repeat && read_len < read_length) { |
| read_offset += read_len; |
| off_buf += read_len; |
| read_length -= read_len; |
| goto again; |
| } |
| } |
| |
| if (actual_read_len) |
| *actual_read_len = total_read_len; |
| set_status: |
| if (status) |
| *status = cd->response_buf[5]; |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_get_selftest_result( |
| struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, |
| u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_get_selftest_result_(cd, read_offset, |
| read_length, test_id, status, actual_read_len, data); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_get_selftest_result(struct device *dev, |
| int protect, u16 read_offset, u16 read_length, u8 test_id, |
| u8 *status, u16 *actual_read_len, u8 *data) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_get_selftest_result(cd, read_offset, |
| read_length, test_id, status, actual_read_len, |
| data); |
| |
| return cyttsp5_hid_output_get_selftest_result_(cd, read_offset, |
| read_length, test_id, status, actual_read_len, |
| data); |
| } |
| |
| static int cyttsp5_hid_output_calibrate_idacs_(struct cyttsp5_core_data *cd, |
| u8 mode, u8 *status) |
| { |
| int rc; |
| int write_length = 1; |
| u8 write_buf[1]; |
| u8 cmd_offset = 0; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_CALIBRATE_IDACS), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| .timeout_ms = CY_HID_OUTPUT_CALIBRATE_IDAC_TIMEOUT, |
| }; |
| |
| write_buf[cmd_offset++] = mode; |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| *status = cd->response_buf[5]; |
| if (*status) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_calibrate_idacs(struct cyttsp5_core_data *cd, |
| u8 mode, u8 *status) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_calibrate_idacs_(cd, mode, status); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_calibrate_idacs(struct device *dev, |
| int protect, u8 mode, u8 *status) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_calibrate_idacs(cd, mode, status); |
| |
| return cyttsp5_hid_output_calibrate_idacs_(cd, mode, status); |
| } |
| |
| static int cyttsp5_hid_output_initialize_baselines_( |
| struct cyttsp5_core_data *cd, u8 test_id, u8 *status) |
| { |
| int rc; |
| int write_length = 1; |
| u8 write_buf[1]; |
| u8 cmd_offset = 0; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_INITIALIZE_BASELINES), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| }; |
| |
| write_buf[cmd_offset++] = test_id; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| *status = cd->response_buf[5]; |
| if (*status) |
| return -EINVAL; |
| |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_initialize_baselines(struct cyttsp5_core_data *cd, |
| u8 test_id, u8 *status) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_initialize_baselines_(cd, test_id, status); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_initialize_baselines(struct device *dev, |
| int protect, u8 test_id, u8 *status) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_initialize_baselines(cd, test_id, |
| status); |
| |
| return cyttsp5_hid_output_initialize_baselines_(cd, test_id, status); |
| } |
| |
| static int cyttsp5_hid_output_exec_panel_scan_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_EXEC_PANEL_SCAN), |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_exec_panel_scan(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_exec_panel_scan_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_exec_panel_scan(struct device *dev, |
| int protect) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_exec_panel_scan(cd); |
| |
| return cyttsp5_hid_output_exec_panel_scan_(cd); |
| } |
| |
| /* @response: set none NULL only if all response required including header */ |
| static int cyttsp5_hid_output_retrieve_panel_scan_( |
| struct cyttsp5_core_data *cd, u16 read_offset, u16 read_count, |
| u8 data_id, u8 *response, u8 *config, u16 *actual_read_len, |
| u8 *read_buf) |
| { |
| int status; |
| u8 read_data_id; |
| int rc; |
| int write_length = 5; |
| u8 write_buf[5]; |
| u8 cmd_offset = 0; |
| u8 data_elem_size; |
| int size; |
| int data_size; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_APP_COMMAND(HID_OUTPUT_RETRIEVE_PANEL_SCAN), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| }; |
| |
| write_buf[cmd_offset++] = LOW_BYTE(read_offset); |
| write_buf[cmd_offset++] = HI_BYTE(read_offset); |
| write_buf[cmd_offset++] = LOW_BYTE(read_count); |
| write_buf[cmd_offset++] = HI_BYTE(read_count); |
| write_buf[cmd_offset++] = data_id; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| status = cd->response_buf[5]; |
| if (status) |
| return -EINVAL; |
| |
| read_data_id = cd->response_buf[6]; |
| if (read_data_id != data_id) |
| return -EPROTO; |
| |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| *actual_read_len = get_unaligned_le16(&cd->response_buf[7]); |
| *config = cd->response_buf[9]; |
| |
| data_elem_size = *config & 0x07; |
| data_size = *actual_read_len * data_elem_size; |
| |
| if (read_buf) |
| memcpy(read_buf, &cd->response_buf[10], data_size); |
| if (response) |
| memcpy(response, cd->response_buf, size); |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_retrieve_panel_scan( |
| struct cyttsp5_core_data *cd, u16 read_offset, u16 read_count, |
| u8 data_id, u8 *response, u8 *config, u16 *actual_read_len, |
| u8 *read_buf) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_retrieve_panel_scan_(cd, read_offset, |
| read_count, data_id, response, config, |
| actual_read_len, read_buf); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_retrieve_panel_scan(struct device *dev, |
| int protect, u16 read_offset, u16 read_count, u8 data_id, |
| u8 *response, u8 *config, u16 *actual_read_len, u8 *read_buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_retrieve_panel_scan(cd, |
| read_offset, read_count, data_id, response, |
| config, actual_read_len, read_buf); |
| |
| return cyttsp5_hid_output_retrieve_panel_scan_(cd, |
| read_offset, read_count, data_id, response, |
| config, actual_read_len, read_buf); |
| } |
| |
| static int cyttsp5_hid_output_user_cmd_(struct cyttsp5_core_data *cd, |
| u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, |
| u16 *actual_read_len) |
| { |
| int rc; |
| u16 size; |
| #ifdef TTHE_TUNER_SUPPORT |
| int command_code = 0; |
| int len; |
| #endif |
| struct cyttsp5_hid_output hid_output = { |
| .length = write_len, |
| .write_buf = write_buf, |
| }; |
| |
| rc = cyttsp5_hid_send_output_user_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| if (size == 0) |
| size = 2; |
| |
| if (size > read_len) { |
| *actual_read_len = 0; |
| return -EINVAL; |
| } |
| |
| memcpy(read_buf, cd->response_buf, size); |
| *actual_read_len = size; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| /* print up to cmd code */ |
| len = HID_OUTPUT_CMD_OFFSET + 1; |
| if (write_len < len) |
| len = write_len; |
| else |
| command_code = write_buf[HID_OUTPUT_CMD_OFFSET] |
| & HID_OUTPUT_CMD_MASK; |
| |
| /* Do not print for EXEC_PANEL_SCAN & RETRIEVE_PANEL_SCAN commands */ |
| if (command_code != HID_OUTPUT_EXEC_PANEL_SCAN |
| && command_code != HID_OUTPUT_RETRIEVE_PANEL_SCAN) |
| tthe_print(cd, write_buf, len, "CMD="); |
| #endif |
| |
| return 0; |
| } |
| |
| static int cyttsp5_get_config_ver_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| int rc; |
| u16 config_ver = 0; |
| |
| rc = cyttsp5_hid_output_suspend_scanning_(cd); |
| if (rc) |
| goto error; |
| |
| rc = cyttsp5_hid_output_read_conf_ver_(cd, &config_ver); |
| if (rc) |
| goto exit; |
| |
| si->cydata.fw_ver_conf = config_ver; |
| |
| exit: |
| cyttsp5_hid_output_resume_scanning_(cd); |
| error: |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: CONFIG_VER:%04X\n", |
| __func__, config_ver); |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_user_cmd(struct cyttsp5_core_data *cd, |
| u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, |
| u16 *actual_read_len) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_user_cmd_(cd, read_len, read_buf, |
| write_len, write_buf, actual_read_len); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_user_cmd(struct device *dev, |
| int protect, u16 read_len, u8 *read_buf, u16 write_len, |
| u8 *write_buf, u16 *actual_read_len) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_user_cmd(cd, read_len, read_buf, |
| write_len, write_buf, actual_read_len); |
| |
| return cyttsp5_hid_output_user_cmd_(cd, read_len, read_buf, |
| write_len, write_buf, actual_read_len); |
| } |
| |
| static int cyttsp5_hid_output_bl_get_information_(struct cyttsp5_core_data *cd, |
| u8 *return_data) |
| { |
| int rc; |
| int data_len; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_GET_INFO), |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) |
| return rc; |
| |
| data_len = get_unaligned_le16(&cd->input_buf[6]); |
| if (!data_len) |
| return -EPROTO; |
| |
| memcpy(return_data, &cd->response_buf[8], data_len); |
| |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_bl_get_information(struct cyttsp5_core_data *cd, |
| u8 *return_data) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_bl_get_information_(cd, return_data); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_bl_get_information(struct device *dev, |
| int protect, u8 *return_data) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_bl_get_information(cd, return_data); |
| |
| return cyttsp5_hid_output_bl_get_information_(cd, return_data); |
| } |
| |
| static int cyttsp5_hid_output_bl_initiate_bl_(struct cyttsp5_core_data *cd, |
| u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) |
| { |
| u16 write_length = key_size + row_size; |
| u8 *write_buf; |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_INITIATE_BL), |
| .write_length = write_length, |
| .timeout_ms = CY_HID_OUTPUT_BL_INITIATE_BL_TIMEOUT, |
| }; |
| |
| write_buf = kzalloc(write_length, GFP_KERNEL); |
| if (!write_buf) |
| return -ENOMEM; |
| |
| hid_output.write_buf = write_buf; |
| |
| if (key_size) |
| memcpy(write_buf, key_buf, key_size); |
| |
| if (row_size) |
| memcpy(&write_buf[key_size], metadata_row_buf, row_size); |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| |
| kfree(write_buf); |
| return rc; |
| } |
| |
| static int cyttsp5_hid_output_bl_initiate_bl(struct cyttsp5_core_data *cd, |
| u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_bl_initiate_bl_(cd, key_size, key_buf, |
| row_size, metadata_row_buf); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_bl_initiate_bl(struct device *dev, |
| int protect, u16 key_size, u8 *key_buf, u16 row_size, |
| u8 *metadata_row_buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_bl_initiate_bl(cd, key_size, key_buf, |
| row_size, metadata_row_buf); |
| |
| return cyttsp5_hid_output_bl_initiate_bl_(cd, key_size, key_buf, |
| row_size, metadata_row_buf); |
| } |
| |
| static int cyttsp5_hid_output_bl_program_and_verify_( |
| struct cyttsp5_core_data *cd, u16 data_len, u8 *data_buf) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_PROGRAM_AND_VERIFY), |
| .write_length = data_len, |
| .write_buf = data_buf, |
| .timeout_ms = CY_HID_OUTPUT_BL_PROGRAM_AND_VERIFY_TIMEOUT, |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_bl_program_and_verify( |
| struct cyttsp5_core_data *cd, u16 data_len, u8 *data_buf) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_bl_program_and_verify_(cd, data_len, data_buf); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_bl_program_and_verify( |
| struct device *dev, int protect, u16 data_len, u8 *data_buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_bl_program_and_verify(cd, data_len, |
| data_buf); |
| |
| return cyttsp5_hid_output_bl_program_and_verify_(cd, data_len, |
| data_buf); |
| } |
| |
| static int cyttsp5_hid_output_bl_verify_app_integrity_( |
| struct cyttsp5_core_data *cd, u8 *result) |
| { |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_VERIFY_APP_INTEGRITY), |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc) { |
| *result = 0; |
| return rc; |
| } |
| |
| *result = cd->response_buf[8]; |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_bl_verify_app_integrity( |
| struct cyttsp5_core_data *cd, u8 *result) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_bl_verify_app_integrity_(cd, result); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_bl_verify_app_integrity( |
| struct device *dev, int protect, u8 *result) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_bl_verify_app_integrity(cd, result); |
| |
| return cyttsp5_hid_output_bl_verify_app_integrity_(cd, result); |
| } |
| |
| static int cyttsp5_hid_output_bl_launch_app_(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_LAUNCH_APP), |
| .reset_expected = 1, |
| }; |
| |
| return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| } |
| |
| static int cyttsp5_hid_output_bl_launch_app(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_bl_launch_app_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_launch_app(struct device *dev, |
| int protect) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_bl_launch_app(cd); |
| |
| return cyttsp5_hid_output_bl_launch_app_(cd); |
| } |
| |
| static int cyttsp5_hid_output_bl_get_panel_id_( |
| struct cyttsp5_core_data *cd, u8 *panel_id) |
| { |
| int rc; |
| struct cyttsp5_hid_output hid_output = { |
| HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_GET_PANEL_ID), |
| }; |
| |
| rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); |
| if (rc == -EPROTO && cd->response_buf[5] == ERROR_COMMAND) { |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: Get Panel ID command not supported\n", |
| __func__); |
| *panel_id = PANEL_ID_NOT_ENABLED; |
| return 0; |
| } else if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on Get Panel ID command\n", |
| __func__); |
| return rc; |
| } |
| |
| *panel_id = cd->response_buf[8]; |
| return 0; |
| } |
| |
| static int cyttsp5_hid_output_bl_get_panel_id( |
| struct cyttsp5_core_data *cd, u8 *panel_id) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_output_bl_get_panel_id_(cd, panel_id); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_hid_output_bl_get_panel_id( |
| struct device *dev, int protect, u8 *panel_id) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_hid_output_bl_get_panel_id(cd, panel_id); |
| |
| return cyttsp5_hid_output_bl_get_panel_id_(cd, panel_id); |
| } |
| |
| static int cyttsp5_get_hid_descriptor_(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_desc *desc) |
| { |
| struct device *dev = cd->dev; |
| int rc; |
| int t; |
| u8 cmd[2]; |
| |
| /* Read HID descriptor length and version */ |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| |
| /* Set HID descriptor register */ |
| memcpy(cmd, &cd->hid_core.hid_desc_register, |
| sizeof(cd->hid_core.hid_desc_register)); |
| |
| rc = cyttsp5_adap_write_read_specific(cd, 2, cmd, NULL); |
| if (rc) { |
| dev_err(dev, |
| "%s: get HID descriptor length and ver failed, rc=%d\n", |
| __func__, rc); |
| goto error; |
| } |
| |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(CY_HID_GET_HID_DESCRIPTOR_TIMEOUT)); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: HID get descriptor timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| memcpy((u8 *)desc, cd->response_buf, sizeof(struct cyttsp5_hid_desc)); |
| |
| /* Check HID descriptor length and version */ |
| parade_debug(dev, DEBUG_LEVEL_2, "%s: HID len:%X HID ver:%X\n", |
| __func__, |
| le16_to_cpu(desc->hid_desc_len), |
| le16_to_cpu(desc->bcd_version)); |
| |
| if (le16_to_cpu(desc->hid_desc_len) != sizeof(*desc) || |
| le16_to_cpu(desc->bcd_version) != CY_HID_VERSION) { |
| dev_err(dev, "%s: Unsupported HID version\n", __func__); |
| return -ENODEV; |
| } |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_get_hid_descriptor(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_desc *desc) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_get_hid_descriptor_(cd, desc); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int _cyttsp5_request_get_hid_desc(struct device *dev, int protect) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return cyttsp5_get_hid_descriptor(cd, &cd->hid_desc); |
| |
| return cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| } |
| |
| static int cyttsp5_hw_soft_reset(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| if (cd->hid_desc.hid_desc_len == 0) { |
| rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| if (rc < 0) |
| return rc; |
| } |
| |
| rc = cyttsp5_hid_cmd_reset_(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: FAILED to execute SOFT reset\n", |
| __func__); |
| return rc; |
| } |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: execute SOFT reset\n", |
| __func__); |
| return 0; |
| } |
| |
| static int cyttsp5_hw_hard_reset(struct cyttsp5_core_data *cd) |
| { |
| if (cd->cpdata->xres) { |
| cd->cpdata->xres(cd->cpdata, cd->dev); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: execute HARD reset\n", |
| __func__); |
| return 0; |
| } |
| dev_err(cd->dev, "%s: FAILED to execute HARD reset\n", __func__); |
| return -ENODEV; |
| } |
| |
| static int cyttsp5_hw_reset(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| rc = cyttsp5_hw_hard_reset(cd); |
| mutex_unlock(&cd->system_lock); |
| if (rc == -ENODEV) |
| rc = cyttsp5_hw_soft_reset(cd); |
| return rc; |
| } |
| |
| static inline int get_hid_item_data(u8 *data, int item_size) |
| { |
| if (item_size == 1) |
| return (int)*data; |
| else if (item_size == 2) |
| return (int)get_unaligned_le16(data); |
| else if (item_size == 4) |
| return (int)get_unaligned_le32(data); |
| return 0; |
| } |
| |
| static int parse_report_descriptor(struct cyttsp5_core_data *cd, |
| u8 *report_desc, size_t len) |
| { |
| struct cyttsp5_hid_report *report; |
| struct cyttsp5_hid_field *field; |
| u8 *buf = report_desc; |
| u8 *end = buf + len; |
| int rc = 0; |
| int offset = 0; |
| int i; |
| u8 report_type; |
| u32 up_usage; |
| /* Global items */ |
| u8 report_id = 0; |
| u16 usage_page = 0; |
| int report_count = 0; |
| int report_size = 0; |
| int logical_min = 0; |
| int logical_max = 0; |
| /* Local items */ |
| u16 usage = 0; |
| /* Main items - Collection stack */ |
| u32 collection_usages[CY_HID_MAX_NESTED_COLLECTIONS] = {0}; |
| u8 collection_types[CY_HID_MAX_NESTED_COLLECTIONS] = {0}; |
| /* First collection for header, second for report */ |
| int logical_collection_count = 0; |
| int collection_nest = 0; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: Report descriptor length: %u\n", |
| __func__, (u32)len); |
| |
| mutex_lock(&cd->hid_report_lock); |
| cyttsp5_free_hid_reports_(cd); |
| |
| while (buf < end) { |
| int item_type; |
| int item_size; |
| int item_tag; |
| u8 *data; |
| |
| /* Get Item */ |
| item_size = HID_GET_ITEM_SIZE(buf[0]); |
| if (item_size == 3) |
| item_size = 4; |
| item_type = HID_GET_ITEM_TYPE(buf[0]); |
| item_tag = HID_GET_ITEM_TAG(buf[0]); |
| |
| data = ++buf; |
| buf += item_size; |
| |
| /* Process current item */ |
| switch (item_type) { |
| case HID_ITEM_TYPE_GLOBAL: |
| switch (item_tag) { |
| case HID_GLOBAL_ITEM_TAG_REPORT_ID: |
| if (item_size != 1) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| report_id = get_hid_item_data(data, item_size); |
| offset = 0; |
| logical_collection_count = 0; |
| break; |
| case HID_GLOBAL_ITEM_TAG_USAGE_PAGE: |
| if (item_size == 0 || item_size == 4) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| usage_page = (u16)get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM: |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| logical_min = get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM: |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| logical_max = get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_REPORT_COUNT: |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| report_count = get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| report_size = get_hid_item_data(data, |
| item_size); |
| break; |
| default: |
| dev_info(cd->dev, |
| "%s: Unrecognized Global tag %d\n", |
| __func__, item_tag); |
| } |
| break; |
| case HID_ITEM_TYPE_LOCAL: |
| switch (item_tag) { |
| case HID_LOCAL_ITEM_TAG_USAGE: |
| if (item_size == 0 || item_size == 4) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| usage = (u16)get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM: |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| /* usage_min = */ |
| get_hid_item_data(data, item_size); |
| break; |
| case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM: |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| /* usage_max = */ |
| get_hid_item_data(data, item_size); |
| break; |
| default: |
| dev_info(cd->dev, |
| "%s: Unrecognized Local tag %d\n", |
| __func__, item_tag); |
| } |
| break; |
| case HID_ITEM_TYPE_MAIN: |
| switch (item_tag) { |
| case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION: |
| if (item_size != 1) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| if (CY_HID_MAX_NESTED_COLLECTIONS == |
| collection_nest) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| up_usage = usage_page << 16 | usage; |
| |
| /* Update collection stack */ |
| collection_usages[collection_nest] = up_usage; |
| collection_types[collection_nest] = |
| get_hid_item_data(data, item_size); |
| |
| if (collection_types[collection_nest] == |
| HID_COLLECTION_LOGICAL) |
| logical_collection_count++; |
| |
| collection_nest++; |
| break; |
| case HID_MAIN_ITEM_TAG_END_COLLECTION: |
| if (item_size != 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| if (--collection_nest < 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| break; |
| case HID_MAIN_ITEM_TAG_INPUT: |
| report_type = HID_INPUT_REPORT; |
| goto continue_main_item; |
| case HID_MAIN_ITEM_TAG_OUTPUT: |
| report_type = HID_OUTPUT_REPORT; |
| goto continue_main_item; |
| case HID_MAIN_ITEM_TAG_FEATURE: |
| report_type = HID_FEATURE_REPORT; |
| continue_main_item: |
| if (item_size != 1) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| up_usage = usage_page << 16 | usage; |
| |
| /* Get or create report */ |
| report = cyttsp5_get_hid_report_(cd, |
| report_type, report_id, true); |
| if (!report) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| if (!report->usage_page && collection_nest > 0) |
| report->usage_page = |
| collection_usages |
| [collection_nest - 1]; |
| |
| /* Create field */ |
| field = cyttsp5_create_hid_field_(report); |
| if (!field) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| field->report_count = report_count; |
| field->report_size = report_size; |
| field->size = report_count * report_size; |
| field->offset = offset; |
| field->data_type = |
| get_hid_item_data(data, item_size); |
| field->logical_min = logical_min; |
| field->logical_max = logical_max; |
| field->usage_page = up_usage; |
| |
| for (i = 0; i < collection_nest; i++) |
| field->collection_usage_pages |
| [collection_types[i]] = |
| collection_usages[i]; |
| |
| /* Update report's header or record size */ |
| if (logical_collection_count == 1) |
| report->header_size += field->size; |
| else if (logical_collection_count == 2) { |
| field->record_field = true; |
| field->offset -= report->header_size; |
| /* Set record field index */ |
| if (report->record_field_index == 0) |
| report->record_field_index = |
| report->num_fields - 1; |
| report->record_size += field->size; |
| } |
| |
| report->size += field->size; |
| |
| offset += field->size; |
| break; |
| default: |
| dev_info(cd->dev, |
| "%s: Unrecognized Main tag %d\n", |
| __func__, item_tag); |
| } |
| |
| /* Reset all local items */ |
| usage = 0; |
| break; |
| } |
| } |
| |
| if (buf != end) { |
| dev_err(cd->dev, "%s: Report descriptor length invalid\n", |
| __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| if (collection_nest) { |
| dev_err(cd->dev, "%s: Unbalanced collection items (%d)\n", |
| __func__, collection_nest); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| exit: |
| if (rc) |
| cyttsp5_free_hid_reports_(cd); |
| mutex_unlock(&cd->hid_report_lock); |
| return rc; |
| } |
| |
| static struct cyttsp5_hid_field *find_report_desc_field( |
| struct cyttsp5_core_data *cd, u32 usage_page, |
| u32 collection_usage_page) |
| { |
| struct cyttsp5_hid_report *report = NULL; |
| struct cyttsp5_hid_field *field = NULL; |
| int i; |
| int j; |
| u32 field_cup; |
| u32 field_up; |
| |
| for (i = 0; i < cd->num_hid_reports; i++) { |
| report = cd->hid_reports[i]; |
| for (j = 0; j < report->num_fields; j++) { |
| field = report->fields[j]; |
| field_cup = field->collection_usage_pages |
| [HID_COLLECTION_LOGICAL]; |
| field_up = field->usage_page; |
| if (field_cup == collection_usage_page |
| && field_up == usage_page) |
| return field; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int fill_tch_abs(struct cyttsp5_tch_abs_params *tch_abs, |
| struct cyttsp5_hid_field *field) |
| { |
| tch_abs->ofs = field->offset / 8; |
| tch_abs->size = field->report_size / 8; |
| if (field->report_size % 8) |
| tch_abs->size += 1; |
| tch_abs->min = 0; |
| tch_abs->max = 1 << field->report_size; |
| tch_abs->bofs = field->offset - (tch_abs->ofs << 3); |
| |
| return 0; |
| } |
| |
| static struct cyttsp5_hid_report *find_report_desc(struct cyttsp5_core_data *cd, |
| u32 usage_page) |
| { |
| struct cyttsp5_hid_report *report = NULL; |
| int i; |
| |
| for (i = 0; i < cd->num_hid_reports; i++) |
| if (cd->hid_reports[i]->usage_page == usage_page) { |
| report = cd->hid_reports[i]; |
| break; |
| } |
| |
| return report; |
| } |
| |
| static int setup_report_descriptor(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| struct cyttsp5_hid_report *report; |
| struct cyttsp5_hid_field *field; |
| int i; |
| u32 tch_collection_usage_page = HID_CY_TCH_COL_USAGE_PG; |
| u32 btn_collection_usage_page = HID_CY_BTN_COL_USAGE_PG; |
| |
| for (i = CY_TCH_X; i < CY_TCH_NUM_ABS; i++) { |
| field = find_report_desc_field(cd, |
| cyttsp5_tch_abs_field_map[i], |
| tch_collection_usage_page); |
| if (field) { |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " Field %pK: rep_cnt:%d rep_sz:%d off:%d", |
| field, field->report_count, field->report_size, |
| field->offset); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " data:%02X min:%d max:%d usage_page:%08X\n", |
| field->data_type, field->logical_min, |
| field->logical_max, field->usage_page); |
| fill_tch_abs(&si->tch_abs[i], field); |
| si->tch_abs[i].report = 1; |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: ofs:%u size:%u min:%u", |
| cyttsp5_tch_abs_string[i], |
| (u32)si->tch_abs[i].ofs, |
| (u32)si->tch_abs[i].size, |
| (u32)si->tch_abs[i].min); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " max:%u bofs:%u report:%d", |
| (u32)si->tch_abs[i].max, |
| (u32)si->tch_abs[i].bofs, |
| si->tch_abs[i].report); |
| |
| } else |
| si->tch_abs[i].report = 0; |
| } |
| for (i = CY_TCH_TIME; i < CY_TCH_NUM_HDR; i++) { |
| field = find_report_desc_field(cd, |
| cyttsp5_tch_hdr_field_map[i], |
| tch_collection_usage_page); |
| if (field) { |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " Field %pK: rep_cnt:%d rep_sz:%d off:%d", |
| field, field->report_count, field->report_size, |
| field->offset); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " data:%02X min:%d max:%d usage_page:%08X\n", |
| field->data_type, field->logical_min, |
| field->logical_max, field->usage_page); |
| fill_tch_abs(&si->tch_hdr[i], field); |
| si->tch_hdr[i].report = 1; |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: ofs:%u size:%u min:%u", |
| cyttsp5_tch_hdr_string[i], |
| (u32)si->tch_hdr[i].ofs, |
| (u32)si->tch_hdr[i].size, |
| (u32)si->tch_hdr[i].min); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " max:%u bofs:%u report:%d", |
| (u32)si->tch_hdr[i].max, |
| (u32)si->tch_hdr[i].bofs, |
| si->tch_hdr[i].report); |
| |
| } else |
| si->tch_hdr[i].report = 0; |
| } |
| |
| report = find_report_desc(cd, tch_collection_usage_page); |
| if (report) { |
| si->desc.tch_report_id = report->id; |
| si->desc.tch_record_size = report->record_size / 8; |
| si->desc.tch_header_size = (report->header_size / 8) + 3; |
| } else { |
| si->desc.tch_report_id = HID_TOUCH_REPORT_ID; |
| si->desc.tch_record_size = TOUCH_REPORT_SIZE; |
| si->desc.tch_header_size = TOUCH_INPUT_HEADER_SIZE; |
| } |
| |
| report = find_report_desc(cd, btn_collection_usage_page); |
| if (report) |
| si->desc.btn_report_id = report->id; |
| else |
| si->desc.btn_report_id = HID_BTN_REPORT_ID; |
| |
| for (i = 0; i < cd->num_hid_reports; i++) { |
| struct cyttsp5_hid_report *report = cd->hid_reports[i]; |
| |
| switch (report->id) { |
| case HID_WAKEUP_REPORT_ID: |
| cd->features.easywake = 1; |
| break; |
| case HID_NOISE_METRIC_REPORT_ID: |
| cd->features.noise_metric = 1; |
| break; |
| case HID_TRACKING_HEATMAP_REPOR_ID: |
| cd->features.tracking_heatmap = 1; |
| break; |
| case HID_SENSOR_DATA_REPORT_ID: |
| cd->features.sensor_data = 1; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "Features: easywake:%d noise_metric:%d", |
| cd->features.easywake, cd->features.noise_metric); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| " tracking_heatmap:%d sensor_data:%d\n", |
| cd->features.tracking_heatmap, cd->features.sensor_data); |
| return 0; |
| } |
| |
| static int cyttsp5_get_report_descriptor_(struct cyttsp5_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| u8 cmd[2]; |
| int rc; |
| int t; |
| |
| /* Read report descriptor length and version */ |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| |
| /* Set report descriptor register */ |
| memcpy(cmd, &cd->hid_desc.report_desc_register, |
| sizeof(cd->hid_desc.report_desc_register)); |
| |
| rc = cyttsp5_adap_write_read_specific(cd, 2, cmd, NULL); |
| if (rc) { |
| dev_err(dev, |
| "%s: get HID descriptor len and ver failed, rc=%d\n", |
| __func__, rc); |
| goto error; |
| } |
| |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(CY_HID_GET_REPORT_DESCRIPTOR_TIMEOUT)); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: HID get descriptor timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| cyttsp5_pr_buf(cd->dev, cd->response_buf, |
| cd->hid_core.hid_report_desc_len, "Report Desc"); |
| |
| rc = parse_report_descriptor(cd, cd->response_buf + 3, |
| get_unaligned_le16(&cd->response_buf[0]) - 3); |
| if (rc) |
| dev_err(cd->dev, "%s: Error parsing report descriptor r=%d\n", |
| __func__, rc); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: %d reports found in descriptor\n", |
| __func__, cd->num_hid_reports); |
| |
| for (t = 0; t < cd->num_hid_reports; t++) { |
| struct cyttsp5_hid_report *report = cd->hid_reports[t]; |
| int j; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "Report %d: type:%d id:%02X size:%d fields:%d", |
| t, report->type, report->id, |
| report->size, report->num_fields); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "rec_fld_index:%d hdr_sz:%d rec_sz:%d usage_page:%X\n", |
| report->record_field_index, report->header_size, |
| report->record_size, report->usage_page); |
| |
| for (j = 0; j < report->num_fields; j++) { |
| struct cyttsp5_hid_field *field = report->fields[j]; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " Field %d: rep_cnt:%d rep_sz:%d off:%d", |
| j, field->report_count, field->report_size, |
| field->offset); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " data:%02X min:%d max:%d usage_page:%08X\n", |
| field->data_type, field->logical_min, |
| field->logical_max, field->usage_page); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| " Collections Phys:%08X App:%08X Log:%08X\n", |
| field->collection_usage_pages |
| [HID_COLLECTION_PHYSICAL], |
| field->collection_usage_pages |
| [HID_COLLECTION_APPLICATION], |
| field->collection_usage_pages |
| [HID_COLLECTION_LOGICAL]); |
| } |
| } |
| |
| rc = setup_report_descriptor(cd); |
| |
| /* Free it for now */ |
| cyttsp5_free_hid_reports_(cd); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: %d reports found in descriptor\n", |
| __func__, cd->num_hid_reports); |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_get_mode(struct cyttsp5_core_data *cd, |
| struct cyttsp5_hid_desc *desc) |
| { |
| if (desc->packet_id == CY_HID_APP_REPORT_ID) |
| return CY_MODE_OPERATIONAL; |
| else if (desc->packet_id == CY_HID_BL_REPORT_ID) |
| return CY_MODE_BOOTLOADER; |
| |
| return CY_MODE_UNKNOWN; |
| } |
| |
| static int _cyttsp5_request_get_mode(struct device *dev, int protect, u8 *mode) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| int rc; |
| |
| if (protect) |
| rc = cyttsp5_get_hid_descriptor(cd, &cd->hid_desc); |
| else |
| rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| |
| if (rc) |
| *mode = CY_MODE_UNKNOWN; |
| else |
| *mode = cyttsp5_get_mode(cd, &cd->hid_desc); |
| |
| return rc; |
| } |
| |
| static void cyttsp5_queue_startup_(struct cyttsp5_core_data *cd) |
| { |
| if (cd->startup_state == STARTUP_NONE) { |
| cd->startup_state = STARTUP_QUEUED; |
| schedule_work(&cd->startup_work); |
| dev_info(cd->dev, "%s: cyttsp5_startup queued\n", __func__); |
| } else |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: startup_state = %d\n", |
| __func__, cd->startup_state); |
| |
| } |
| |
| static void cyttsp5_queue_startup(struct cyttsp5_core_data *cd) |
| { |
| mutex_lock(&cd->system_lock); |
| cyttsp5_queue_startup_(cd); |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| static void call_atten_cb(struct cyttsp5_core_data *cd, |
| enum cyttsp5_atten_type type, int mode) |
| { |
| struct atten_node *atten, *atten_n; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: check list type=%d mode=%d\n", |
| __func__, type, mode); |
| spin_lock(&cd->spinlock); |
| list_for_each_entry_safe(atten, atten_n, |
| &cd->atten_list[type], node) |
| if (!mode || atten->mode & mode) { |
| spin_unlock(&cd->spinlock); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: attention for '%s'", |
| __func__, dev_name(atten->dev)); |
| atten->func(atten->dev); |
| spin_lock(&cd->spinlock); |
| } |
| |
| spin_unlock(&cd->spinlock); |
| } |
| |
| static void cyttsp5_start_wd_timer(struct cyttsp5_core_data *cd) |
| { |
| if (!cd->watchdog_interval) |
| return; |
| |
| mod_timer(&cd->watchdog_timer, jiffies + |
| msecs_to_jiffies(cd->watchdog_interval)); |
| } |
| |
| static void cyttsp5_stop_wd_timer(struct cyttsp5_core_data *cd) |
| { |
| if (!cd->watchdog_interval) |
| return; |
| |
| /* |
| * Ensure we wait until the watchdog timer |
| * running on a different CPU finishes |
| */ |
| del_timer_sync(&cd->watchdog_timer); |
| cancel_work_sync(&cd->watchdog_work); |
| del_timer_sync(&cd->watchdog_timer); |
| } |
| |
| static int start_fw_upgrade(void *data) |
| { |
| struct cyttsp5_core_data *cd = (struct cyttsp5_core_data *)data; |
| |
| call_atten_cb(cd, CY_ATTEN_LOADER, 0); |
| return 0; |
| } |
| |
| static void cyttsp5_watchdog_work(struct work_struct *work) |
| { |
| struct cyttsp5_core_data *cd = |
| container_of(work, struct cyttsp5_core_data, |
| watchdog_work); |
| int rc = 0; |
| |
| /*fix CDT207254 |
| *if found the current sleep_state is SS_SLEEPING |
| *then no need to request_exclusive, directly return |
| */ |
| if (cd->sleep_state == SS_SLEEPING) |
| return; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| goto queue_startup; |
| } |
| |
| rc = cyttsp5_hid_output_null_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| |
| queue_startup: |
| if (rc) { |
| dev_err(cd->dev, |
| "%s: failed to access device in watchdog timer r=%d\n", |
| __func__, rc); |
| |
| /* Already tried FW upgrade because of watchdog but failed */ |
| if (cd->startup_retry_count > CY_WATCHDOG_RETRY_COUNT) |
| return; |
| |
| if (cd->startup_retry_count++ < CY_WATCHDOG_RETRY_COUNT) |
| cyttsp5_queue_startup(cd); |
| else |
| kthread_run(start_fw_upgrade, cd, "cyttp5_loader"); |
| |
| return; |
| } |
| |
| cyttsp5_start_wd_timer(cd); |
| } |
| |
| static void cyttsp5_watchdog_timer(unsigned long handle) |
| { |
| struct cyttsp5_core_data *cd = (struct cyttsp5_core_data *)handle; |
| |
| if (!cd) |
| return; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: Watchdog timer triggered\n", |
| __func__); |
| |
| if (!work_pending(&cd->watchdog_work)) |
| schedule_work(&cd->watchdog_work); |
| } |
| |
| static int cyttsp5_put_device_into_easy_wakeup_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| u8 status = 0; |
| |
| mutex_lock(&cd->system_lock); |
| cd->wait_until_wake = 0; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = cyttsp5_hid_output_enter_easywake_state_(cd, |
| cd->easy_wakeup_gesture, &status); |
| if (rc || status == 0) |
| return -EBUSY; |
| |
| return rc; |
| } |
| |
| #ifdef CY_GES_WAKEUP |
| static int cyttsp5_core_wake_device_from_easy_wakeup_( |
| struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| u8 status = 0; |
| |
| rc = cyttsp5_hid_output_exit_easywake_state_(cd, |
| cd->easy_wakeup_gesture, &status); |
| if (rc || status == 0) { |
| dev_dbg(cd->dev, "%s: failed, rc=%d, status=%d\n", |
| __func__, rc, status); |
| return -EBUSY; |
| } |
| |
| return rc; |
| } |
| #endif |
| |
| static int cyttsp5_put_device_into_deep_sleep_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = cyttsp5_hid_cmd_set_power_(cd, HID_POWER_SLEEP); |
| if (rc) |
| rc = -EBUSY; |
| return rc; |
| } |
| |
| static int cyttsp5_put_device_into_sleep_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| if (IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) |
| rc = cyttsp5_put_device_into_deep_sleep_(cd); |
| else |
| rc = cyttsp5_put_device_into_easy_wakeup_(cd); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_core_poweroff_device_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| if (cd->irq_enabled) { |
| cd->irq_enabled = false; |
| disable_irq_nosync(cd->irq); |
| } |
| |
| rc = cd->cpdata->power(cd->cpdata, 0, cd->dev, NULL); |
| if (rc < 0) |
| dev_err(cd->dev, "%s: HW Power down fails r=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| static int cyttsp5_core_sleep_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| if (cd->sleep_state == SS_SLEEP_OFF) { |
| cd->sleep_state = SS_SLEEPING; |
| } else { |
| mutex_unlock(&cd->system_lock); |
| return 1; |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| /* Ensure watchdog and startup works stopped */ |
| cyttsp5_stop_wd_timer(cd); |
| cancel_work_sync(&cd->startup_work); |
| cyttsp5_stop_wd_timer(cd); |
| |
| if (cd->cpdata->flags & CY_CORE_FLAG_POWEROFF_ON_SLEEP) |
| rc = cyttsp5_core_poweroff_device_(cd); |
| else |
| rc = cyttsp5_put_device_into_sleep_(cd); |
| |
| mutex_lock(&cd->system_lock); |
| cd->sleep_state = SS_SLEEP_ON; |
| cd->large_power_state = 0; |
| mutex_unlock(&cd->system_lock); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_core_sleep(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_core_sleep_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| else |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: pass release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_wakeup_host(struct cyttsp5_core_data *cd) |
| { |
| #ifndef EASYWAKE_TSG6 |
| /* TSG5 EasyWake */ |
| int rc = 0; |
| int event_id; |
| |
| /* Validate report */ |
| if (cd->input_buf[2] != 4) |
| rc = -EINVAL; |
| |
| event_id = cd->input_buf[3]; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: e=%d, rc=%d\n", |
| __func__, event_id, rc); |
| |
| if (rc) { |
| cyttsp5_core_sleep_(cd); |
| goto exit; |
| } |
| |
| if (event_id != 3 && event_id != 0xAA) { |
| dev_err(cd->dev, "do not wake up by filter\n"); |
| if (event_id == GESTURE_SINGLE_QUICK_CALL) |
| goto callcb; |
| else |
| goto exit; |
| } |
| |
| if (cd->wake_initiated_by_device == 1) { |
| dev_dbg(cd->dev, "%s: try to re-send power key\n", __func__); |
| goto exit; |
| } |
| cd->wake_initiated_by_device = 1; |
| |
| callcb: |
| /* attention WAKE */ |
| call_atten_cb(cd, CY_ATTEN_WAKE, 0); |
| exit: |
| return rc; |
| #else |
| /* TSG6 FW1.3 EasyWake */ |
| int rc = 0; |
| int i = 0; |
| int report_length; |
| |
| /* Validate report */ |
| if (cd->input_buf[2] != 4) |
| rc = -EINVAL; |
| |
| cd->wake_initiated_by_device = 1; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: rc=%d\n", __func__, rc); |
| |
| if (rc) { |
| cyttsp5_core_sleep_(cd); |
| goto exit; |
| } |
| |
| /* Get gesture id and gesture data length */ |
| cd->gesture_id = cd->input_buf[3]; |
| report_length = (cd->input_buf[1] << 8) | (cd->input_buf[0]); |
| cd->gesture_data_length = report_length - 4; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: gesture_id = %d, gesture_data_length = %d\n", |
| __func__, cd->gesture_id, cd->gesture_data_length); |
| |
| for (i = 0; i < cd->gesture_data_length; i++) |
| cd->gesture_data[i] = cd->input_buf[4 + i]; |
| |
| /* attention WAKE */ |
| call_atten_cb(cd, CY_ATTEN_WAKE, 0); |
| exit: |
| return rc; |
| #endif |
| } |
| |
| static void cyttsp5_get_touch_axis(struct cyttsp5_core_data *cd, |
| int *axis, int size, int max, u8 *data, int bofs) |
| { |
| int nbyte; |
| int next; |
| |
| for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { |
| *axis = *axis + ((data[next] >> bofs) << (nbyte * 8)); |
| next++; |
| } |
| |
| *axis &= max - 1; |
| } |
| |
| static int move_tracking_hetmap_data(struct cyttsp5_core_data *cd, |
| struct cyttsp5_sysinfo *si) |
| { |
| #ifdef TTHE_TUNER_SUPPORT |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| if (size) |
| tthe_print(cd, cd->input_buf, size, "THM="); |
| #endif |
| memcpy(si->xy_mode, cd->input_buf, SENSOR_HEADER_SIZE); |
| return 0; |
| } |
| |
| static int move_sensor_data(struct cyttsp5_core_data *cd, |
| struct cyttsp5_sysinfo *si) |
| { |
| #ifdef TTHE_TUNER_SUPPORT |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| if (size) |
| tthe_print(cd, cd->input_buf, size, "sensor_monitor="); |
| #endif |
| memcpy(si->xy_mode, cd->input_buf, SENSOR_HEADER_SIZE); |
| return 0; |
| } |
| |
| static int move_button_data(struct cyttsp5_core_data *cd, |
| struct cyttsp5_sysinfo *si) |
| { |
| #ifdef TTHE_TUNER_SUPPORT |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| if (size) |
| tthe_print(cd, cd->input_buf, size, "OpModeData="); |
| #endif |
| memcpy(si->xy_mode, cd->input_buf, BTN_INPUT_HEADER_SIZE); |
| cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_mode, BTN_INPUT_HEADER_SIZE, |
| "xy_mode"); |
| |
| memcpy(si->xy_data, &cd->input_buf[BTN_INPUT_HEADER_SIZE], |
| BTN_REPORT_SIZE); |
| cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_data, BTN_REPORT_SIZE, "xy_data"); |
| return 0; |
| } |
| |
| static int move_touch_data(struct cyttsp5_core_data *cd, |
| struct cyttsp5_sysinfo *si) |
| { |
| int max_tch = si->sensing_conf_data.max_tch; |
| int num_cur_tch; |
| int length; |
| struct cyttsp5_tch_abs_params *tch = &si->tch_hdr[CY_TCH_NUM]; |
| #ifdef TTHE_TUNER_SUPPORT |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| if (size) |
| tthe_print(cd, cd->input_buf, size, "OpModeData="); |
| #endif |
| |
| memcpy(si->xy_mode, cd->input_buf, si->desc.tch_header_size); |
| cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_mode, si->desc.tch_header_size, |
| "xy_mode"); |
| |
| cyttsp5_get_touch_axis(cd, &num_cur_tch, tch->size, |
| tch->max, si->xy_mode + 3 + tch->ofs, tch->bofs); |
| if (unlikely(num_cur_tch > max_tch)) |
| num_cur_tch = max_tch; |
| |
| length = num_cur_tch * si->desc.tch_record_size; |
| |
| memcpy(si->xy_data, &cd->input_buf[si->desc.tch_header_size], length); |
| cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_data, length, "xy_data"); |
| return 0; |
| } |
| |
| static int parse_touch_input(struct cyttsp5_core_data *cd, int size) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| int report_id = cd->input_buf[2]; |
| int rc = -EINVAL; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: Received touch report\n", |
| __func__); |
| if (!si->ready) { |
| dev_err(cd->dev, |
| "%s: Need system information to parse touches\n", |
| __func__); |
| return 0; |
| } |
| |
| if (!si->xy_mode || !si->xy_data) |
| return rc; |
| |
| if (report_id == si->desc.tch_report_id) |
| rc = move_touch_data(cd, si); |
| else if (report_id == si->desc.btn_report_id) |
| rc = move_button_data(cd, si); |
| else if (report_id == HID_SENSOR_DATA_REPORT_ID) |
| rc = move_sensor_data(cd, si); |
| else if (report_id == HID_TRACKING_HEATMAP_REPOR_ID) |
| rc = move_tracking_hetmap_data(cd, si); |
| |
| if (rc) |
| return rc; |
| |
| /* attention IRQ */ |
| call_atten_cb(cd, CY_ATTEN_IRQ, cd->mode); |
| |
| return 0; |
| } |
| |
| static int parse_command_input(struct cyttsp5_core_data *cd, int size) |
| { |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: Received cmd interrupt\n", |
| __func__); |
| |
| memcpy(cd->response_buf, cd->input_buf, size); |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| wake_up(&cd->wait_q); |
| |
| return 0; |
| } |
| |
| static int cyttsp5_parse_input(struct cyttsp5_core_data *cd) |
| { |
| int report_id; |
| int is_command = 0; |
| int size; |
| |
| size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| /* check reset */ |
| if (size == 0) { |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: Reset complete\n", |
| __func__); |
| memcpy(cd->response_buf, cd->input_buf, 2); |
| mutex_lock(&cd->system_lock); |
| if (!cd->hid_reset_cmd_state && !cd->hid_cmd_state) { |
| mutex_unlock(&cd->system_lock); |
| parade_debug(cd->dev, DEBUG_LEVEL_1, |
| "%s: Device Initiated Reset\n", __func__); |
| return 0; |
| } |
| |
| cd->hid_reset_cmd_state = 0; |
| if (cd->hid_cmd_state == HID_OUTPUT_START_BOOTLOADER + 1 |
| || cd->hid_cmd_state == HID_OUTPUT_BL_LAUNCH_APP + 1 |
| || cd->hid_cmd_state == HID_OUTPUT_USER_CMD + 1) |
| cd->hid_cmd_state = 0; |
| wake_up(&cd->wait_q); |
| mutex_unlock(&cd->system_lock); |
| return 0; |
| } else if (size == 2 || size >= CY_PIP_1P7_EMPTY_BUF) |
| /* |
| * Before PIP 1.7, empty buffer is 0x0002; |
| * From PIP 1.7, empty buffer is 0xFFXX |
| */ |
| return 0; |
| |
| report_id = cd->input_buf[2]; |
| parade_debug(cd->dev, DEBUG_LEVEL_2, "%s: report_id:%X\n", |
| __func__, report_id); |
| |
| /* Check wake-up report */ |
| if (report_id == HID_WAKEUP_REPORT_ID) { |
| cyttsp5_wakeup_host(cd); |
| return 0; |
| } |
| |
| /* update watchdog expire time */ |
| mod_timer_pending(&cd->watchdog_timer, jiffies + |
| msecs_to_jiffies(cd->watchdog_interval)); |
| |
| if (report_id != cd->sysinfo.desc.tch_report_id |
| && report_id != cd->sysinfo.desc.btn_report_id |
| && report_id != HID_SENSOR_DATA_REPORT_ID |
| && report_id != HID_TRACKING_HEATMAP_REPOR_ID) |
| is_command = 1; |
| |
| if (size > CY_MAX_INPUT) |
| size = CY_MAX_INPUT; |
| |
| if (unlikely(is_command)) { |
| parse_command_input(cd, size); |
| return 0; |
| } |
| parse_touch_input(cd, size); |
| return 0; |
| } |
| |
| static int cyttsp5_read_input(struct cyttsp5_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| int rc; |
| int t; |
| |
| /* added as workaround to CDT170960: easywake failure */ |
| /* Interrupt for easywake, wait for bus controller to wake */ |
| mutex_lock(&cd->system_lock); |
| if (!IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) { |
| if (cd->sleep_state == SS_SLEEP_ON) { |
| mutex_unlock(&cd->system_lock); |
| if (!dev->power.is_suspended) |
| goto read; |
| t = wait_event_timeout(cd->wait_q, |
| (cd->wait_until_wake == 1), |
| msecs_to_jiffies(20)); |
| if (IS_TMO(t)) |
| cyttsp5_queue_startup(cd); |
| goto read; |
| } |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| read: |
| rc = cyttsp5_adap_read_default_nosize(cd, cd->input_buf, CY_MAX_INPUT); |
| if (rc) { |
| dev_err(dev, "%s: Error getting report, r=%d\n", __func__, rc); |
| #ifdef PATCH_WAKEUP_NACK_ISSUE |
| if (cd->sleep_state == SS_SLEEP_ON) { |
| cd->input_buf[0] = 0x07; |
| cd->input_buf[1] = 0x00; |
| cd->input_buf[2] = 0x04; |
| cd->input_buf[3] = 0xAA; |
| cd->input_buf[4] = 0x00; |
| cd->input_buf[5] = 0x00; |
| cd->input_buf[6] = 0x00; |
| dev_info(dev, "Self recover for I2C Bus err\n"); |
| rc = 0; |
| } else |
| return rc; |
| |
| #else |
| return rc; |
| #endif |
| } |
| parade_debug(dev, DEBUG_LEVEL_2, "%s: Read input successfully\n", |
| __func__); |
| return rc; |
| } |
| |
| static bool cyttsp5_check_irq_asserted(struct cyttsp5_core_data *cd) |
| { |
| #ifdef ENABLE_WORKAROUND_FOR_GLITCH_AFTER_BL_LAUNCH_APP |
| /* |
| * Workaround for FW defect, CDT165308 |
| * bl_launch app creates a glitch in IRQ line |
| */ |
| if (cd->hid_cmd_state == HID_OUTPUT_BL_LAUNCH_APP + 1 |
| && cd->cpdata->irq_stat){ |
| /* |
| * in X1S panel and GC1546 panel, the width for the INT |
| * glitch is about 4us,the normal INT width of response |
| * will last more than 200us, so use 10us delay |
| * for distinguish the glitch the normal INT is enough. |
| */ |
| udelay(10); |
| if (cd->cpdata->irq_stat(cd->cpdata, cd->dev) |
| != CY_IRQ_ASSERTED_VALUE) |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| |
| static irqreturn_t cyttsp5_irq(int irq, void *handle) |
| { |
| struct cyttsp5_core_data *cd = handle; |
| int rc; |
| |
| if (!cyttsp5_check_irq_asserted(cd)) |
| return IRQ_HANDLED; |
| |
| rc = cyttsp5_read_input(cd); |
| if (!rc) |
| cyttsp5_parse_input(cd); |
| |
| return IRQ_HANDLED; |
| } |
| |
| int _cyttsp5_subscribe_attention(struct device *dev, |
| enum cyttsp5_atten_type type, char *id, |
| int (*func)(struct device *), int mode) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| struct atten_node *atten, *atten_new; |
| |
| atten_new = kzalloc(sizeof(*atten_new), GFP_KERNEL); |
| if (!atten_new) |
| return -ENOMEM; |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s from '%s'\n", __func__, |
| dev_name(cd->dev)); |
| |
| spin_lock(&cd->spinlock); |
| list_for_each_entry(atten, &cd->atten_list[type], node) |
| if (atten->id == id && atten->mode == mode) { |
| spin_unlock(&cd->spinlock); |
| kfree(atten_new); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: %s=%pK %s=%d\n", |
| __func__, "already subscribed attention", |
| dev, "mode", mode); |
| |
| return 0; |
| } |
| |
| atten_new->id = id; |
| atten_new->dev = dev; |
| atten_new->mode = mode; |
| atten_new->func = func; |
| |
| list_add(&atten_new->node, &cd->atten_list[type]); |
| spin_unlock(&cd->spinlock); |
| |
| return 0; |
| } |
| |
| int _cyttsp5_unsubscribe_attention(struct device *dev, |
| enum cyttsp5_atten_type type, char *id, |
| int (*func)(struct device *), int mode) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| struct atten_node *atten, *atten_n; |
| |
| spin_lock(&cd->spinlock); |
| list_for_each_entry_safe(atten, atten_n, &cd->atten_list[type], node) |
| if (atten->id == id && atten->mode == mode) { |
| list_del(&atten->node); |
| spin_unlock(&cd->spinlock); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: %s=%pK %s=%d\n", |
| __func__, "unsub for atten->dev", |
| atten->dev, "atten->mode", atten->mode); |
| kfree(atten); |
| return 0; |
| } |
| |
| spin_unlock(&cd->spinlock); |
| |
| return -ENODEV; |
| } |
| |
| static int _cyttsp5_request_exclusive(struct device *dev, |
| int timeout_ms) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| return request_exclusive(cd, (void *)dev, timeout_ms); |
| } |
| |
| static int _cyttsp5_release_exclusive(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| return release_exclusive(cd, (void *)dev); |
| } |
| |
| static int cyttsp5_reset(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| /* reset hardware */ |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: reset hw...\n", __func__); |
| rc = cyttsp5_hw_reset(cd); |
| if (rc < 0) |
| dev_err(cd->dev, "%s: %s dev='%s' r=%d\n", __func__, |
| "Fail hw reset", dev_name(cd->dev), rc); |
| return rc; |
| } |
| |
| static int cyttsp5_reset_and_wait(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| int t; |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_reset_cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = cyttsp5_reset(cd); |
| if (rc < 0) |
| goto error; |
| |
| t = wait_event_timeout(cd->wait_q, (cd->hid_reset_cmd_state == 0), |
| msecs_to_jiffies(CY_HID_RESET_TIMEOUT)); |
| if (IS_TMO(t)) { |
| dev_err(cd->dev, "%s: reset timed out\n", __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_reset_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| /* |
| * returns err if refused or timeout(core uses fixed timeout period) occurs; |
| * blocks until ISR occurs |
| */ |
| static int _cyttsp5_request_reset(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_reset_cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = cyttsp5_reset(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on h/w reset r=%d\n", |
| __func__, rc); |
| mutex_lock(&cd->system_lock); |
| cd->hid_reset_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * returns err if refused ; if no error then restart has completed |
| * and system is in normal operating mode |
| */ |
| static int _cyttsp5_request_restart(struct device *dev, bool wait) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| cyttsp5_queue_startup(cd); |
| |
| if (wait) |
| wait_event(cd->wait_q, cd->startup_state == STARTUP_NONE); |
| |
| return 0; |
| } |
| |
| /* |
| * returns NULL if sysinfo has not been acquired from the device yet |
| */ |
| struct cyttsp5_sysinfo *_cyttsp5_request_sysinfo(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (cd->sysinfo.ready) |
| return &cd->sysinfo; |
| |
| return NULL; |
| } |
| |
| static struct cyttsp5_loader_platform_data *_cyttsp5_request_loader_pdata( |
| struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| return cd->pdata->loader_pdata; |
| } |
| |
| static int _cyttsp5_request_start_wd(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| cyttsp5_start_wd_timer(cd); |
| return 0; |
| } |
| |
| static int _cyttsp5_request_stop_wd(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| cyttsp5_stop_wd_timer(cd); |
| return 0; |
| } |
| |
| static int cyttsp5_core_wake_device_from_deep_sleep_( |
| struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = cyttsp5_hid_cmd_set_power_(cd, HID_POWER_ON); |
| if (rc) |
| rc = -EAGAIN; |
| |
| /* Prevent failure on sequential wake/sleep requests from OS */ |
| msleep(20); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_core_wake_device_(struct cyttsp5_core_data *cd) |
| { |
| if (!IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) { |
| |
| #ifdef CY_GES_WAKEUP |
| if (!cyttsp5_core_wake_device_from_easy_wakeup_(cd)) |
| return 0; |
| #endif |
| } |
| |
| return cyttsp5_core_wake_device_from_deep_sleep_(cd); |
| } |
| |
| static int cyttsp5_restore_parameters_(struct cyttsp5_core_data *cd) |
| { |
| struct param_node *param; |
| int rc = 0; |
| |
| if (!(cd->cpdata->flags & CY_CORE_FLAG_RESTORE_PARAMETERS)) |
| goto exit; |
| |
| spin_lock(&cd->spinlock); |
| list_for_each_entry(param, &cd->param_list, node) { |
| spin_unlock(&cd->spinlock); |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: Parameter id:%d value:%d\n", |
| __func__, param->id, param->value); |
| rc = cyttsp5_hid_output_set_param_(cd, param->id, |
| param->value, param->size); |
| if (rc) |
| goto exit; |
| spin_lock(&cd->spinlock); |
| } |
| spin_unlock(&cd->spinlock); |
| exit: |
| return rc; |
| } |
| |
| static int _fast_startup(struct cyttsp5_core_data *cd) |
| { |
| int retry = CY_CORE_STARTUP_RETRY_COUNT; |
| int rc; |
| |
| reset: |
| if (retry != CY_CORE_STARTUP_RETRY_COUNT) |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: Retry %d\n", |
| __func__, CY_CORE_STARTUP_RETRY_COUNT - retry); |
| |
| rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on getting HID descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); |
| |
| if (cd->mode == CY_MODE_BOOTLOADER) { |
| dev_info(cd->dev, "%s: Bootloader mode\n", __func__); |
| rc = cyttsp5_hid_output_bl_launch_app_(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on launch app r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| if (rc < 0) { |
| dev_err(cd->dev, |
| "%s: Error on getting HID descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); |
| if (cd->mode == CY_MODE_BOOTLOADER) { |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| } |
| |
| rc = cyttsp5_restore_parameters_(cd); |
| if (rc) |
| dev_err(cd->dev, "%s: failed to restore parameters rc=%d\n", |
| __func__, rc); |
| |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_core_poweron_device_(struct cyttsp5_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| int rc; |
| |
| rc = cd->cpdata->power(cd->cpdata, 1, dev, NULL); |
| if (rc < 0) { |
| dev_err(dev, "%s: HW Power up fails r=%d\n", __func__, rc); |
| goto exit; |
| } |
| |
| if (!cd->irq_enabled) { |
| cd->irq_enabled = true; |
| enable_irq(cd->irq); |
| } |
| |
| rc = _fast_startup(cd); |
| exit: |
| return rc; |
| } |
| |
| static int cyttsp5_core_wake_(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| if (cd->sleep_state == SS_SLEEP_ON) |
| cd->sleep_state = SS_WAKING; |
| else { |
| mutex_unlock(&cd->system_lock); |
| return 1; |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| if (cd->cpdata->flags & CY_CORE_FLAG_POWEROFF_ON_SLEEP) |
| rc = cyttsp5_core_poweron_device_(cd); |
| else |
| rc = cyttsp5_core_wake_device_(cd); |
| |
| mutex_lock(&cd->system_lock); |
| cd->sleep_state = SS_SLEEP_OFF; |
| mutex_unlock(&cd->system_lock); |
| |
| cyttsp5_start_wd_timer(cd); |
| return rc; |
| } |
| |
| static int cyttsp5_core_wake(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = cyttsp5_core_wake_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| else |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: pass release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| static int cyttsp5_get_ic_crc_(struct cyttsp5_core_data *cd, u8 ebid) |
| { |
| struct cyttsp5_sysinfo *si = &cd->sysinfo; |
| int rc; |
| u8 status; |
| u16 calculated_crc = 0; |
| u16 stored_crc = 0; |
| |
| rc = cyttsp5_hid_output_suspend_scanning_(cd); |
| if (rc) |
| goto error; |
| |
| rc = cyttsp5_hid_output_verify_config_block_crc_(cd, ebid, &status, |
| &calculated_crc, &stored_crc); |
| if (rc) |
| goto exit; |
| |
| if (status) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| si->ttconfig.crc = stored_crc; |
| |
| exit: |
| cyttsp5_hid_output_resume_scanning_(cd); |
| error: |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: CRC: ebid:%d, crc:0x%04X\n", |
| __func__, ebid, si->ttconfig.crc); |
| return rc; |
| } |
| |
| static int cyttsp5_check_and_deassert_int(struct cyttsp5_core_data *cd) |
| { |
| u16 size; |
| u8 buf[2]; |
| u8 *p; |
| u8 retry = 3; |
| int rc; |
| |
| do { |
| rc = cyttsp5_adap_read_default(cd, buf, 2); |
| if (rc < 0) |
| return rc; |
| size = get_unaligned_le16(&buf[0]); |
| |
| if (size == 2 || size == 0 || size >= CY_PIP_1P7_EMPTY_BUF) |
| return 0; |
| |
| p = kzalloc(size, GFP_KERNEL); |
| if (!p) |
| return -ENOMEM; |
| |
| rc = cyttsp5_adap_read_default(cd, p, size); |
| kfree(p); |
| if (rc < 0) |
| return rc; |
| } while (retry--); |
| |
| return -EINVAL; |
| } |
| |
| static int cyttsp5_startup_(struct cyttsp5_core_data *cd, bool reset) |
| { |
| int retry = CY_CORE_STARTUP_RETRY_COUNT; |
| int rc; |
| bool detected = false; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, NULL, 0, "enter startup"); |
| #endif |
| |
| cyttsp5_stop_wd_timer(cd); |
| |
| reset: |
| if (retry != CY_CORE_STARTUP_RETRY_COUNT) |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: Retry %d\n", |
| __func__, CY_CORE_STARTUP_RETRY_COUNT - retry); |
| |
| rc = cyttsp5_check_and_deassert_int(cd); |
| |
| if (rc || retry != CY_CORE_STARTUP_RETRY_COUNT) { |
| /* reset hardware */ |
| rc = cyttsp5_reset_and_wait(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on h/w reset r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| } |
| |
| rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on getting HID descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); |
| |
| detected = true; |
| |
| /* Switch to bootloader mode to get Panel ID */ |
| if (cd->mode == CY_MODE_OPERATIONAL) { |
| rc = cyttsp5_hid_output_start_bootloader_(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on start bootloader r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| dev_info(cd->dev, "%s: Bootloader mode\n", __func__); |
| } |
| |
| cyttsp5_hid_output_bl_get_panel_id_(cd, &cd->panel_id); |
| |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: Panel ID: 0x%02X\n", |
| __func__, cd->panel_id); |
| |
| rc = cyttsp5_hid_output_bl_launch_app_(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: Error on launch app r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| |
| rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); |
| if (rc < 0) { |
| dev_err(cd->dev, |
| "%s: Error on getting HID descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); |
| if (cd->mode == CY_MODE_BOOTLOADER) { |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| |
| mutex_lock(&cd->system_lock); |
| /* Read descriptor lengths */ |
| cd->hid_core.hid_report_desc_len = |
| le16_to_cpu(cd->hid_desc.report_desc_len); |
| cd->hid_core.hid_max_input_len = |
| le16_to_cpu(cd->hid_desc.max_input_len); |
| cd->hid_core.hid_max_output_len = |
| le16_to_cpu(cd->hid_desc.max_output_len); |
| |
| cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); |
| if (cd->mode == CY_MODE_OPERATIONAL) |
| dev_dbg(cd->dev, "%s: Operational mode\n", __func__); |
| else if (cd->mode == CY_MODE_BOOTLOADER) |
| dev_dbg(cd->dev, "%s: Bootloader mode\n", __func__); |
| else if (cd->mode == CY_MODE_UNKNOWN) { |
| dev_err(cd->dev, "%s: Unknown mode\n", __func__); |
| rc = -ENODEV; |
| mutex_unlock(&cd->system_lock); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| dev_dbg(cd->dev, "%s: Reading report descriptor\n", __func__); |
| rc = cyttsp5_get_report_descriptor_(cd); |
| if (rc < 0) { |
| dev_err(cd->dev, |
| "%s: Error on getting report descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| |
| if (!cd->features.easywake) |
| cd->easy_wakeup_gesture = CY_CORE_EWG_NONE; |
| |
| rc = cyttsp5_hid_output_get_sysinfo_(cd); |
| if (rc) { |
| dev_err(cd->dev, "%s: Error on getting sysinfo r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| |
| dev_info(cd->dev, "cyttsp5 Protocol Version: %d.%d\n", |
| cd->sysinfo.cydata.pip_ver_major, |
| cd->sysinfo.cydata.pip_ver_minor); |
| |
| /* Read config version directly if PIP version < 1.2 */ |
| if (!IS_PIP_VER_GE(&cd->sysinfo, 1, 2)) { |
| rc = cyttsp5_get_config_ver_(cd); |
| if (rc) |
| dev_err(cd->dev, |
| "%s: failed to read config version rc=%d\n", |
| __func__, rc); |
| } |
| |
| rc = cyttsp5_get_ic_crc_(cd, CY_TCH_PARM_EBID); |
| if (rc) |
| dev_err(cd->dev, "%s: failed to crc data rc=%d\n", |
| __func__, rc); |
| |
| rc = cyttsp5_restore_parameters_(cd); |
| if (rc) |
| dev_err(cd->dev, "%s: failed to restore parameters rc=%d\n", |
| __func__, rc); |
| |
| /* attention startup */ |
| call_atten_cb(cd, CY_ATTEN_STARTUP, 0); |
| |
| cyttsp5_start_wd_timer(cd); |
| exit: |
| if (!rc) |
| cd->startup_retry_count = 0; |
| |
| if (!detected) |
| rc = -ENODEV; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, NULL, 0, "exit startup"); |
| #endif |
| |
| return rc; |
| } |
| |
| static int cyttsp5_startup(struct cyttsp5_core_data *cd, bool reset) |
| { |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| cd->startup_state = STARTUP_RUNNING; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| dev_err(cd->dev, "%s: fail get exclusive ex=%pK own=%pK\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| goto exit; |
| } |
| |
| rc = cyttsp5_startup_(cd, reset); |
| |
| /* Wake the waiters for end of startup */ |
| if (!rc) |
| wake_up(&cd->wait_q); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| /* Don't return fail code, mode is already changed. */ |
| dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); |
| else |
| parade_debug(cd->dev, DEBUG_LEVEL_2, |
| "%s: pass release exclusive\n", __func__); |
| |
| exit: |
| mutex_lock(&cd->system_lock); |
| cd->startup_state = STARTUP_NONE; |
| mutex_unlock(&cd->system_lock); |
| |
| return rc; |
| } |
| |
| static void cyttsp5_startup_work_function(struct work_struct *work) |
| { |
| struct cyttsp5_core_data *cd = container_of(work, |
| struct cyttsp5_core_data, startup_work); |
| int rc; |
| |
| rc = cyttsp5_startup(cd, true); |
| if (rc < 0) |
| dev_err(cd->dev, "%s: Fail queued startup r=%d\n", |
| __func__, rc); |
| } |
| |
| #if defined(CONFIG_PM_SLEEP) |
| static int cyttsp5_core_suspend(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| cyttsp5_core_sleep(cd); |
| |
| if (IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) |
| return 0; |
| |
| /* |
| * This will not prevent resume |
| * Required to prevent interrupts before i2c awake |
| */ |
| |
| /* |
| * disable_irq(cd->irq); |
| * cd->irq_disabled = 1; |
| */ |
| |
| if (device_may_wakeup(dev)) { |
| parade_debug(dev, DEBUG_LEVEL_2, "%s Device MAY wakeup\n", |
| __func__); |
| if (!enable_irq_wake(cd->irq)) { |
| cd->irq_wake = 1; |
| dev_info(dev, "suspend irq - cd->irq_wake = 1\n"); |
| } |
| } else |
| parade_debug(dev, DEBUG_LEVEL_1, "%s Device MAY NOT wakeup\n", |
| __func__); |
| |
| cd->forbit_bigobject = 0; |
| return 0; |
| } |
| |
| static int cyttsp5_core_resume(struct device *dev) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| if (IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) |
| goto exit; |
| |
| /* |
| * I2C bus pm does not call suspend if device runtime suspended |
| * This flag is cover that case |
| */ |
| |
| /* |
| * if (cd->irq_disabled) { |
| * enable_irq(cd->irq); |
| * cd->irq_disabled = 0; |
| * } |
| */ |
| |
| if (device_may_wakeup(dev)) { |
| parade_debug(dev, DEBUG_LEVEL_2, "%s Device MAY wakeup\n", |
| __func__); |
| if (cd->irq_wake) { |
| disable_irq_wake(cd->irq); |
| cd->irq_wake = 0; |
| } |
| } else |
| parade_debug(dev, DEBUG_LEVEL_1, "%s Device MAY NOT wakeup\n", |
| __func__); |
| |
| exit: |
| cyttsp5_core_wake(cd); |
| |
| return 0; |
| } |
| #endif |
| |
| #ifdef NEED_SUSPEND_NOTIFIER |
| static int cyttsp5_pm_notifier(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct cyttsp5_core_data *cd = container_of(nb, |
| struct cyttsp5_core_data, pm_notifier); |
| |
| if (action == PM_SUSPEND_PREPARE) { |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: Suspend prepare\n", |
| __func__); |
| |
| /* |
| * If not runtime PM suspended, either call runtime |
| * PM suspend callback or wait until it finishes |
| */ |
| if (!pm_runtime_suspended(cd->dev)) |
| pm_runtime_suspend(cd->dev); |
| |
| (void) cyttsp5_core_suspend(cd->dev); |
| } |
| |
| return NOTIFY_DONE; |
| } |
| #endif |
| |
| #ifdef USE_FB_SUSPEND_RESUME |
| static int cyttsp5_temp_suspend(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int cyttsp5_temp_resume(struct device *dev) |
| { |
| return 0; |
| } |
| #endif |
| |
| const struct dev_pm_ops cyttsp5_pm_ops = { |
| #ifdef USE_FB_SUSPEND_RESUME |
| //SET_SYSTEM_SLEEP_PM_OPS(cyttsp5_core_suspend, cyttsp5_core_resume) |
| SET_SYSTEM_SLEEP_PM_OPS(cyttsp5_temp_suspend, cyttsp5_temp_resume) |
| #else |
| SET_SYSTEM_SLEEP_PM_OPS(cyttsp5_core_suspend, cyttsp5_core_resume) |
| #endif |
| }; |
| EXPORT_SYMBOL_GPL(cyttsp5_pm_ops); |
| |
| /* |
| * Show Firmware version via sysfs |
| */ |
| static ssize_t cyttsp5_ic_ver_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| struct cyttsp5_cydata *cydata = &cd->sysinfo.cydata; |
| |
| return snprintf(buf, PAGE_SIZE, |
| "%s: 0x%02X\n" |
| "%s: 0x%02X\n" |
| "%s: 0x%08X\n" |
| "%s: 0x%04X\n" |
| "%s: 0x%02X\n" |
| "%s: 0x%02X\n" |
| "%s: 0x%02X\n" |
| "%s: 0x%02X\n", |
| "Firmware Major Version", cydata->fw_ver_major, |
| "Firmware Minor Version", cydata->fw_ver_minor, |
| "Revision Control Number", cydata->revctrl, |
| "Firmware Configuration Version", cydata->fw_ver_conf, |
| "Bootloader Major Version", cydata->bl_ver_major, |
| "Bootloader Minor Version", cydata->bl_ver_minor, |
| "Protocol Major Version", cydata->pip_ver_major, |
| "Protocol Minor Version", cydata->pip_ver_minor); |
| } |
| |
| /* |
| * Show Driver version via sysfs |
| */ |
| static ssize_t cyttsp5_drv_ver_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Driver: %s\nVersion: %s\nDate: %s\n", |
| cy_driver_core_name, cy_driver_core_version, |
| cy_driver_core_date); |
| } |
| |
| /* |
| * HW reset via sysfs |
| */ |
| static ssize_t cyttsp5_hw_reset_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| int rc; |
| |
| rc = cyttsp5_startup(cd, true); |
| if (rc < 0) |
| dev_err(dev, "%s: HW reset failed r=%d\n", __func__, rc); |
| |
| return size; |
| } |
| |
| /* |
| * Show IRQ status via sysfs |
| */ |
| static ssize_t cyttsp5_hw_irq_stat_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| int retval; |
| |
| if (cd->cpdata->irq_stat) { |
| retval = cd->cpdata->irq_stat(cd->cpdata, dev); |
| switch (retval) { |
| case 0: |
| return snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Interrupt line is LOW.\n"); |
| case 1: |
| return snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Interrupt line is HIGH.\n"); |
| default: |
| return snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Function irq_stat() returned %d.\n", retval); |
| } |
| } |
| |
| return snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Function irq_stat() undefined.\n"); |
| } |
| |
| /* |
| * Show IRQ enable/disable status via sysfs |
| */ |
| static ssize_t cyttsp5_drv_irq_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| mutex_lock(&cd->system_lock); |
| if (cd->irq_enabled) |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Driver interrupt is ENABLED\n"); |
| else |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "Driver interrupt is DISABLED\n"); |
| mutex_unlock(&cd->system_lock); |
| |
| return ret; |
| } |
| |
| /* |
| * Enable/disable IRQ via sysfs |
| */ |
| static ssize_t cyttsp5_drv_irq_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| unsigned long value; |
| int retval = 0; |
| |
| retval = kstrtoul(buf, 10, &value); |
| if (retval < 0) { |
| dev_err(dev, "%s: Invalid value\n", __func__); |
| goto cyttsp5_drv_irq_store_error_exit; |
| } |
| |
| mutex_lock(&cd->system_lock); |
| switch (value) { |
| case 0: |
| if (cd->irq_enabled) { |
| cd->irq_enabled = false; |
| /* Disable IRQ */ |
| disable_irq_nosync(cd->irq); |
| dev_info(dev, "%s: Driver IRQ now disabled\n", |
| __func__); |
| } else |
| dev_info(dev, "%s: Driver IRQ already disabled\n", |
| __func__); |
| break; |
| |
| case 1: |
| if (cd->irq_enabled == false) { |
| cd->irq_enabled = true; |
| /* Enable IRQ */ |
| enable_irq(cd->irq); |
| dev_info(dev, "%s: Driver IRQ now enabled\n", |
| __func__); |
| } else |
| dev_info(dev, "%s: Driver IRQ already enabled\n", |
| __func__); |
| break; |
| |
| default: |
| dev_err(dev, "%s: Invalid value\n", __func__); |
| } |
| mutex_unlock(&(cd->system_lock)); |
| |
| cyttsp5_drv_irq_store_error_exit: |
| |
| return size; |
| } |
| |
| /* |
| * Gets user input from sysfs and parse it |
| * return size of parsed output buffer |
| */ |
| |
| #define CY_MAX_CONFIG_BYTES_DEC 256 |
| #define CYTTSP5_INPUT_ELEM_SZ_DEC 10 |
| |
| static int cyttsp5_ic_parse_input_dec(struct device *dev, const char *buf, |
| size_t buf_size, u32 *ic_buf, size_t ic_buf_size) |
| { |
| const char *pbuf = buf; |
| unsigned long value; |
| char scan_buf[CYTTSP5_INPUT_ELEM_SZ_DEC]; |
| u32 i = 0; |
| u32 j; |
| int last = 0; |
| int ret; |
| |
| parade_debug(dev, DEBUG_LEVEL_1, |
| "%s: pbuf=%pK buf=%pK size=%zu %s=%zu buf=%s\n", |
| __func__, pbuf, buf, buf_size, "scan buf size", |
| (size_t)CYTTSP5_INPUT_ELEM_SZ_DEC, buf); |
| |
| while (pbuf <= (buf + buf_size)) { |
| if (i >= CY_MAX_CONFIG_BYTES_DEC) { |
| dev_err(dev, "%s: %s size=%d max=%d\n", __func__, |
| "Max cmd size exceeded", i, |
| CY_MAX_CONFIG_BYTES_DEC); |
| return -EINVAL; |
| } |
| if (i >= ic_buf_size) { |
| dev_err(dev, "%s: %s size=%d buf_size=%zu\n", __func__, |
| "Buffer size exceeded", i, ic_buf_size); |
| return -EINVAL; |
| } |
| while (((*pbuf == ' ') || (*pbuf == ',')) |
| && (pbuf < (buf + buf_size))) { |
| last = *pbuf; |
| pbuf++; |
| } |
| |
| if (pbuf >= (buf + buf_size)) |
| break; |
| |
| memset(scan_buf, 0, CYTTSP5_INPUT_ELEM_SZ_DEC); |
| if ((last == ',') && (*pbuf == ',')) { |
| dev_err(dev, "%s: %s \",,\" not allowed.\n", __func__, |
| "Invalid data format."); |
| return -EINVAL; |
| } |
| for (j = 0; j < (CYTTSP5_INPUT_ELEM_SZ_DEC - 1) |
| && (pbuf < (buf + buf_size)) |
| && (*pbuf != ' ') |
| && (*pbuf != ','); j++) { |
| last = *pbuf; |
| scan_buf[j] = *pbuf++; |
| } |
| ret = kstrtoul(scan_buf, 10, &value); |
| if (ret < 0) { |
| dev_err(dev, "%s: Invalid data format.\n", __func__); |
| return ret; |
| } |
| |
| ic_buf[i] = value; |
| i++; |
| } |
| |
| return i; |
| } |
| |
| /* |
| * Debugging options via sysfs |
| */ |
| static ssize_t cyttsp5_drv_debug_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| unsigned long value; |
| int rc; |
| u8 return_data[8]; |
| static u8 wd_disabled; |
| u32 input_data[2]; |
| int length; |
| |
| /*maximal input two data*/ |
| length = cyttsp5_ic_parse_input_dec(dev, buf, size, |
| input_data, ARRAY_SIZE(input_data)); |
| if (length <= 0) { |
| dev_err(dev, "%s: %s failed\n", __func__, |
| "cyttsp5_ic_parse_input_dec"); |
| goto cyttsp5_drv_debug_store_exit; |
| } |
| value = input_data[0]; |
| |
| |
| /* Start watchdog timer command */ |
| if (value == CY_DBG_HID_START_WD) { |
| dev_info(dev, "%s: start watchdog (cd=%pK)\n", __func__, cd); |
| wd_disabled = 0; |
| cyttsp5_start_wd_timer(cd); |
| goto cyttsp5_drv_debug_store_exit; |
| } |
| |
| /* Stop watchdog timer temporarily */ |
| cyttsp5_stop_wd_timer(cd); |
| |
| if (value == CY_DBG_HID_STOP_WD) { |
| dev_info(dev, "%s: stop watchdog (cd=%pK)\n", __func__, cd); |
| wd_disabled = 1; |
| goto cyttsp5_drv_debug_store_exit; |
| } |
| |
| switch (value) { |
| case CY_DBG_SUSPEND: |
| dev_info(dev, "%s: SUSPEND (cd=%pK)\n", __func__, cd); |
| rc = cyttsp5_core_sleep(cd); |
| if (rc) |
| dev_err(dev, "%s: Suspend failed rc=%d\n", |
| __func__, rc); |
| else |
| dev_info(dev, "%s: Suspend succeeded\n", __func__); |
| break; |
| |
| case CY_DBG_RESUME: |
| dev_info(dev, "%s: RESUME (cd=%pK)\n", __func__, cd); |
| rc = cyttsp5_core_wake(cd); |
| if (rc) |
| dev_err(dev, "%s: Resume failed rc=%d\n", |
| __func__, rc); |
| else |
| dev_info(dev, "%s: Resume succeeded\n", __func__); |
| break; |
| case CY_DBG_SOFT_RESET: |
| dev_info(dev, "%s: SOFT RESET (cd=%pK)\n", __func__, cd); |
| rc = cyttsp5_hw_soft_reset(cd); |
| break; |
| case CY_DBG_RESET: |
| dev_info(dev, "%s: HARD RESET (cd=%pK)\n", __func__, cd); |
| rc = cyttsp5_hw_hard_reset(cd); |
| break; |
| case CY_DBG_HID_RESET: |
| dev_info(dev, "%s: hid_reset (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_cmd_reset(cd); |
| break; |
| case CY_DBG_HID_SET_POWER_ON: |
| dev_info(dev, "%s: hid_set_power_on (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_cmd_set_power(cd, HID_POWER_ON); |
| wd_disabled = 0; |
| break; |
| case CY_DBG_HID_SET_POWER_SLEEP: |
| dev_info(dev, "%s: hid_set_power_off (cd=%pK)\n", __func__, cd); |
| wd_disabled = 1; |
| cyttsp5_hid_cmd_set_power(cd, HID_POWER_SLEEP); |
| break; |
| case CY_DBG_HID_NULL: |
| dev_info(dev, "%s: hid_null (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_null(cd); |
| break; |
| case CY_DBG_HID_ENTER_BL: |
| dev_info(dev, "%s: start_bootloader (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_start_bootloader(cd); |
| break; |
| case CY_DBG_HID_SYSINFO: |
| dev_info(dev, "%s: get_sysinfo (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_get_sysinfo(cd); |
| break; |
| case CY_DBG_HID_SUSPEND_SCAN: |
| dev_info(dev, "%s: suspend_scanning (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_suspend_scanning(cd); |
| break; |
| case CY_DBG_HID_RESUME_SCAN: |
| dev_info(dev, "%s: resume_scanning (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_resume_scanning(cd); |
| break; |
| case HID_OUTPUT_BL_VERIFY_APP_INTEGRITY: |
| dev_info(dev, "%s: verify app integ (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_bl_verify_app_integrity(cd, &return_data[0]); |
| break; |
| case HID_OUTPUT_BL_GET_INFO: |
| dev_info(dev, "%s: bl get info (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_bl_get_information(cd, return_data); |
| break; |
| case HID_OUTPUT_BL_PROGRAM_AND_VERIFY: |
| dev_info(dev, "%s: program & verify (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_bl_program_and_verify(cd, 0, NULL); |
| break; |
| case HID_OUTPUT_BL_LAUNCH_APP: |
| dev_info(dev, "%s: launch app (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_bl_launch_app(cd); |
| break; |
| case HID_OUTPUT_BL_INITIATE_BL: |
| dev_info(dev, "%s: initiate bl (cd=%pK)\n", __func__, cd); |
| cyttsp5_hid_output_bl_initiate_bl(cd, 0, NULL, 0, NULL); |
| break; |
| #ifdef TTHE_TUNER_SUPPORT |
| case CY_TTHE_TUNER_EXIT: |
| cd->tthe_exit = 1; |
| wake_up(&cd->wait_q); |
| kfree(cd->tthe_buf); |
| cd->tthe_buf = NULL; |
| cd->tthe_exit = 0; |
| break; |
| case CY_TTHE_BUF_CLEAN: |
| if (cd->tthe_buf) |
| memset(cd->tthe_buf, 0, CY_MAX_PRBUF_SIZE); |
| else |
| dev_info(dev, "%s : tthe_buf not existed\n", __func__); |
| break; |
| #endif |
| case CY_DBG_REPORT_LEVEL: |
| mutex_lock(&cd->system_lock); |
| cd->debug_level = input_data[1]; |
| dev_info(dev, "%s: Set debug_level: %d\n", |
| __func__, cd->debug_level); |
| mutex_unlock(&(cd->system_lock)); |
| break; |
| case CY_DBG_WATCHDOG_INTERVAL: |
| mutex_lock(&cd->system_lock); |
| if (input_data[1] > 0) |
| cd->watchdog_interval = input_data[1]; |
| dev_info(dev, "%s: Set watchdog_interval: %d\n", |
| __func__, cd->watchdog_interval); |
| mutex_unlock(&(cd->system_lock)); |
| break; |
| case CY_DBG_SHOW_TIMESTAMP: |
| mutex_lock(&cd->system_lock); |
| cd->show_timestamp = input_data[1]; |
| dev_info(dev, "%s: Set show_timestamp: %d\n", |
| __func__, cd->show_timestamp); |
| mutex_unlock(&(cd->system_lock)); |
| break; |
| |
| default: |
| dev_err(dev, "%s: Invalid value\n", __func__); |
| } |
| |
| /* Enable watchdog timer */ |
| if (!wd_disabled) |
| cyttsp5_start_wd_timer(cd); |
| |
| cyttsp5_drv_debug_store_exit: |
| return size; |
| } |
| |
| /* |
| * Show system status on deep sleep status via sysfs |
| */ |
| static ssize_t cyttsp5_sleep_status_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| mutex_lock(&cd->system_lock); |
| if (cd->sleep_state == SS_SLEEP_ON) |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "off\n"); |
| else |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "on\n"); |
| mutex_unlock(&cd->system_lock); |
| |
| return ret; |
| } |
| |
| static ssize_t cyttsp5_easy_wakeup_gesture_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| mutex_lock(&cd->system_lock); |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "0x%02X\n", |
| cd->easy_wakeup_gesture); |
| mutex_unlock(&cd->system_lock); |
| return ret; |
| } |
| |
| static ssize_t cyttsp5_easy_wakeup_gesture_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| unsigned long value; |
| int ret; |
| |
| if (!cd->features.easywake) |
| return -EINVAL; |
| |
| ret = kstrtoul(buf, 10, &value); |
| if (ret < 0) |
| return ret; |
| |
| if (value > 0xFF) |
| return -EINVAL; |
| |
| pm_runtime_get_sync(dev); |
| |
| mutex_lock(&cd->system_lock); |
| if (cd->sysinfo.ready && IS_PIP_VER_GE(&cd->sysinfo, 1, 2)) |
| cd->easy_wakeup_gesture = (u8)value; |
| else |
| ret = -ENODEV; |
| mutex_unlock(&cd->system_lock); |
| |
| pm_runtime_put(dev); |
| |
| if (ret) |
| return ret; |
| |
| return size; |
| } |
| |
| #ifdef EASYWAKE_TSG6 |
| /* |
| * Show easywake gesture id via sysfs |
| */ |
| static ssize_t cyttsp5_easy_wakeup_gesture_id_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| mutex_lock(&cd->system_lock); |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "0x%02X\n", cd->gesture_id); |
| mutex_unlock(&cd->system_lock); |
| return ret; |
| } |
| |
| /* |
| * Show easywake gesture data via sysfs |
| * The format: |
| * x1(LSB), x1(MSB),y1(LSB), y1(MSB),x2(LSB), x2(MSB),y2(LSB), y2(MSB),... |
| */ |
| static ssize_t cyttsp5_easy_wakeup_gesture_data_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret = 0; |
| int i; |
| |
| mutex_lock(&cd->system_lock); |
| |
| for (i = 0; i < cd->gesture_data_length; i++) |
| ret += snprintf(buf + ret, CY_MAX_PRBUF_SIZE - ret, |
| "0x%02X\n", cd->gesture_data[i]); |
| |
| ret += snprintf(buf + ret, CY_MAX_PRBUF_SIZE - ret, |
| "(%d bytes)\n", cd->gesture_data_length); |
| |
| mutex_unlock(&cd->system_lock); |
| return ret; |
| } |
| #endif |
| |
| /* Show Panel ID via sysfs */ |
| static ssize_t cyttsp5_panel_id_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "0x%02X\n", |
| cd->panel_id); |
| return ret; |
| } |
| |
| /* Show platform data via sysfs */ |
| static ssize_t cyttsp5_platform_data_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); |
| ssize_t ret; |
| |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n" |
| "%s: %d\n", |
| "Interrupt GPIO", pdata->core_pdata->irq_gpio, |
| "Reset GPIO", pdata->core_pdata->rst_gpio, |
| "Level trigger delay (us)", pdata->core_pdata->level_irq_udelay, |
| "HID descriptor register", pdata->core_pdata->hid_desc_register, |
| "Vendor ID", pdata->core_pdata->vendor_id, |
| "Product ID", pdata->core_pdata->product_id, |
| "Easy wakeup gesture", pdata->core_pdata->easy_wakeup_gesture, |
| "Vkeys x", pdata->mt_pdata->vkeys_x, |
| "Vkeys y", pdata->mt_pdata->vkeys_y, |
| "Core data flags", pdata->core_pdata->flags, |
| "MT data flags", pdata->mt_pdata->flags, |
| "Loader data flags", pdata->loader_pdata->flags); |
| return ret; |
| } |
| |
| static ssize_t cyttsp5_bigobjectoff_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| unsigned int data; |
| int error; |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| error = kstrtoint(buf, 10, &data); |
| if (error < 0) |
| return error; |
| |
| if (data != 0) |
| cd->forbit_bigobject = 1; |
| else |
| cd->forbit_bigobject = 0; |
| |
| pr_info("cyttsp5_bigobjectoff_store forbitobject:%d\n", |
| cd->forbit_bigobject); |
| return count; |
| } |
| |
| static ssize_t cyttsp5_bigobjectoff_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "%d\n", cd->forbit_bigobject); |
| return ret; |
| } |
| |
| static struct device_attribute attributes[] = { |
| __ATTR(ic_ver, 0444, cyttsp5_ic_ver_show, NULL), |
| __ATTR(drv_ver, 0444, cyttsp5_drv_ver_show, NULL), |
| __ATTR(hw_reset, 0200, NULL, cyttsp5_hw_reset_store), |
| __ATTR(hw_irq_stat, 0400, cyttsp5_hw_irq_stat_show, NULL), |
| __ATTR(drv_irq, 0600, cyttsp5_drv_irq_show, |
| cyttsp5_drv_irq_store), |
| __ATTR(drv_debug, 0200, NULL, cyttsp5_drv_debug_store), |
| __ATTR(sleep_status, 0400, cyttsp5_sleep_status_show, NULL), |
| __ATTR(easy_wakeup_gesture, 0600, |
| cyttsp5_easy_wakeup_gesture_show, |
| cyttsp5_easy_wakeup_gesture_store), |
| #ifdef EASYWAKE_TSG6 |
| __ATTR(easy_wakeup_gesture_id, 0400, |
| cyttsp5_easy_wakeup_gesture_id_show, NULL), |
| __ATTR(easy_wakeup_gesture_data, 0400, |
| cyttsp5_easy_wakeup_gesture_data_show, NULL), |
| #endif |
| __ATTR(panel_id, 0444, cyttsp5_panel_id_show, NULL), |
| __ATTR(platform_data, 0444, cyttsp5_platform_data_show, NULL), |
| __ATTR(bigobject_off, 0600, |
| cyttsp5_bigobjectoff_show, |
| cyttsp5_bigobjectoff_store), |
| }; |
| |
| static int add_sysfs_interfaces(struct device *dev) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(attributes); i++) |
| if (device_create_file(dev, attributes + i)) |
| goto undo; |
| return 0; |
| undo: |
| for (i--; i >= 0; i--) |
| device_remove_file(dev, attributes + i); |
| dev_err(dev, "%s: failed to create sysfs interface\n", __func__); |
| return -ENODEV; |
| } |
| |
| static void remove_sysfs_interfaces(struct device *dev) |
| { |
| u32 i; |
| |
| for (i = 0; i < ARRAY_SIZE(attributes); i++) |
| device_remove_file(dev, attributes + i); |
| } |
| |
| /* |
| * ttdl_restart via sysfs |
| */ |
| static ssize_t cyttsp5_ttdl_restart_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| int rc; |
| struct i2c_client *client = |
| (struct i2c_client *)container_of(dev, struct i2c_client, dev); |
| |
| if (is_cyttsp5_probe_success) { |
| dev_err(dev, |
| "%s: previous cyttsp5_probe successful, do nothing\n", |
| __func__); |
| return size; |
| } |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(dev, "%s I2C functionality not Supported\n", __func__); |
| return -EIO; |
| } |
| |
| #ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT |
| rc = cyttsp5_devtree_create_and_get_pdata(dev); |
| if (rc < 0) |
| return rc; |
| #endif |
| |
| rc = cyttsp5_probe(cyttsp5_bus_ops_save, &client->dev, client->irq, |
| 512); |
| |
| if (!rc) { |
| is_cyttsp5_probe_success = true; |
| dev_err(dev, "%s restart successful\n", __func__); |
| } else { |
| is_cyttsp5_probe_success = false; |
| dev_err(dev, "%s: ttdl restart failed r=%d\n", __func__, rc); |
| } |
| |
| #ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT |
| if (rc) |
| cyttsp5_devtree_clean_pdata(dev); |
| #endif |
| |
| return size; |
| |
| } |
| static DEVICE_ATTR(ttdl_restart, 0200, NULL, cyttsp5_ttdl_restart_store); |
| |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| static int tthe_debugfs_open(struct inode *inode, struct file *filp) |
| { |
| struct cyttsp5_core_data *cd = inode->i_private; |
| |
| filp->private_data = inode->i_private; |
| |
| if (cd->tthe_buf) |
| return -EBUSY; |
| |
| cd->tthe_buf = kzalloc(CY_MAX_PRBUF_SIZE, GFP_KERNEL); |
| if (!cd->tthe_buf) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static int tthe_debugfs_close(struct inode *inode, struct file *filp) |
| { |
| struct cyttsp5_core_data *cd = filp->private_data; |
| |
| filp->private_data = NULL; |
| |
| kfree(cd->tthe_buf); |
| cd->tthe_buf = NULL; |
| |
| return 0; |
| } |
| |
| static ssize_t tthe_debugfs_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct cyttsp5_core_data *cd = filp->private_data; |
| int size; |
| int ret; |
| |
| wait_event_interruptible(cd->wait_q, |
| cd->tthe_buf_len != 0 || cd->tthe_exit); |
| mutex_lock(&cd->tthe_lock); |
| if (cd->tthe_exit) { |
| mutex_unlock(&cd->tthe_lock); |
| return 0; |
| } |
| if (count > cd->tthe_buf_len) |
| size = cd->tthe_buf_len; |
| else |
| size = count; |
| if (!size) { |
| mutex_unlock(&cd->tthe_lock); |
| return 0; |
| } |
| |
| ret = copy_to_user(buf, cd->tthe_buf, cd->tthe_buf_len); |
| if (ret == size) |
| return -EFAULT; |
| size -= ret; |
| cd->tthe_buf_len -= size; |
| mutex_unlock(&cd->tthe_lock); |
| *ppos += size; |
| return size; |
| } |
| |
| static const struct file_operations tthe_debugfs_fops = { |
| .open = tthe_debugfs_open, |
| .release = tthe_debugfs_close, |
| .read = tthe_debugfs_read, |
| }; |
| #endif |
| |
| static struct cyttsp5_core_nonhid_cmd _cyttsp5_core_nonhid_cmd = { |
| .start_bl = _cyttsp5_request_hid_output_start_bl, |
| .suspend_scanning = _cyttsp5_request_hid_output_suspend_scanning, |
| .resume_scanning = _cyttsp5_request_hid_output_resume_scanning, |
| .get_param = _cyttsp5_request_hid_output_get_param, |
| .set_param = _cyttsp5_request_hid_output_set_param, |
| .verify_config_block_crc = |
| _cyttsp5_request_hid_output_verify_config_block_crc, |
| .get_config_row_size = _cyttsp5_request_hid_output_get_config_row_size, |
| .get_data_structure = _cyttsp5_request_hid_output_get_data_structure, |
| .run_selftest = _cyttsp5_request_hid_output_run_selftest, |
| .get_selftest_result = _cyttsp5_request_hid_output_get_selftest_result, |
| .calibrate_idacs = _cyttsp5_request_hid_output_calibrate_idacs, |
| .initialize_baselines = |
| _cyttsp5_request_hid_output_initialize_baselines, |
| .exec_panel_scan = _cyttsp5_request_hid_output_exec_panel_scan, |
| .retrieve_panel_scan = _cyttsp5_request_hid_output_retrieve_panel_scan, |
| .write_conf_block = _cyttsp5_request_hid_output_write_conf_block, |
| .user_cmd = _cyttsp5_request_hid_output_user_cmd, |
| .get_bl_info = _cyttsp5_request_hid_output_bl_get_information, |
| .initiate_bl = _cyttsp5_request_hid_output_bl_initiate_bl, |
| .launch_app = _cyttsp5_request_hid_output_launch_app, |
| .prog_and_verify = _cyttsp5_request_hid_output_bl_program_and_verify, |
| .verify_app_integrity = |
| _cyttsp5_request_hid_output_bl_verify_app_integrity, |
| .get_panel_id = _cyttsp5_request_hid_output_bl_get_panel_id, |
| }; |
| |
| static struct cyttsp5_core_commands _cyttsp5_core_commands = { |
| .subscribe_attention = _cyttsp5_subscribe_attention, |
| .unsubscribe_attention = _cyttsp5_unsubscribe_attention, |
| .request_exclusive = _cyttsp5_request_exclusive, |
| .release_exclusive = _cyttsp5_release_exclusive, |
| .request_reset = _cyttsp5_request_reset, |
| .request_restart = _cyttsp5_request_restart, |
| .request_sysinfo = _cyttsp5_request_sysinfo, |
| .request_loader_pdata = _cyttsp5_request_loader_pdata, |
| .request_stop_wd = _cyttsp5_request_stop_wd, |
| .request_start_wd = _cyttsp5_request_start_wd, |
| .request_get_hid_desc = _cyttsp5_request_get_hid_desc, |
| .request_get_mode = _cyttsp5_request_get_mode, |
| #ifdef TTHE_TUNER_SUPPORT |
| .request_tthe_print = _cyttsp5_request_tthe_print, |
| #endif |
| .nonhid_cmd = &_cyttsp5_core_nonhid_cmd, |
| }; |
| |
| struct cyttsp5_core_commands *cyttsp5_get_commands(void) |
| { |
| if (!is_cyttsp5_probe_success) |
| return NULL; |
| return &_cyttsp5_core_commands; |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_get_commands); |
| |
| static DEFINE_MUTEX(core_list_lock); |
| static LIST_HEAD(core_list); |
| static DEFINE_MUTEX(module_list_lock); |
| static LIST_HEAD(module_list); |
| static int core_number; |
| |
| static int cyttsp5_probe_module(struct cyttsp5_core_data *cd, |
| struct cyttsp5_module *module) |
| { |
| struct module_node *module_node; |
| int rc = 0; |
| |
| module_node = kzalloc(sizeof(*module_node), GFP_KERNEL); |
| if (!module_node) |
| return -ENOMEM; |
| |
| module_node->module = module; |
| |
| mutex_lock(&cd->module_list_lock); |
| list_add(&module_node->node, &cd->module_list); |
| mutex_unlock(&cd->module_list_lock); |
| |
| rc = module->probe(cd->dev, &module_node->data); |
| if (rc) { |
| /* |
| * Remove from the list when probe fails |
| * in order not to call release |
| */ |
| mutex_lock(&cd->module_list_lock); |
| list_del(&module_node->node); |
| mutex_unlock(&cd->module_list_lock); |
| kfree(module_node); |
| goto exit; |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| static void cyttsp5_release_module(struct cyttsp5_core_data *cd, |
| struct cyttsp5_module *module) |
| { |
| struct module_node *m, *m_n; |
| |
| mutex_lock(&cd->module_list_lock); |
| list_for_each_entry_safe(m, m_n, &cd->module_list, node) |
| if (m->module == module) { |
| module->release(cd->dev, m->data); |
| list_del(&m->node); |
| kfree(m); |
| break; |
| } |
| mutex_unlock(&cd->module_list_lock); |
| } |
| |
| static void cyttsp5_probe_modules(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_module *m; |
| int rc = 0; |
| |
| mutex_lock(&module_list_lock); |
| list_for_each_entry(m, &module_list, node) { |
| rc = cyttsp5_probe_module(cd, m); |
| if (rc) |
| dev_err(cd->dev, "%s: Probe fails for module %s\n", |
| __func__, m->name); |
| } |
| mutex_unlock(&module_list_lock); |
| } |
| |
| static void cyttsp5_release_modules(struct cyttsp5_core_data *cd) |
| { |
| struct cyttsp5_module *m; |
| |
| mutex_lock(&module_list_lock); |
| list_for_each_entry(m, &module_list, node) |
| cyttsp5_release_module(cd, m); |
| mutex_unlock(&module_list_lock); |
| } |
| |
| struct cyttsp5_core_data *cyttsp5_get_core_data(char *id) |
| { |
| struct cyttsp5_core_data *d; |
| |
| list_for_each_entry(d, &core_list, node) |
| if (!strncmp(d->core_id, id, 20)) |
| return d; |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_get_core_data); |
| |
| static void cyttsp5_add_core(struct device *dev) |
| { |
| struct cyttsp5_core_data *d; |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| |
| mutex_lock(&core_list_lock); |
| list_for_each_entry(d, &core_list, node) |
| if (d->dev == dev) |
| goto unlock; |
| |
| list_add(&cd->node, &core_list); |
| unlock: |
| mutex_unlock(&core_list_lock); |
| } |
| |
| static void cyttsp5_del_core(struct device *dev) |
| { |
| struct cyttsp5_core_data *d, *d_n; |
| |
| mutex_lock(&core_list_lock); |
| list_for_each_entry_safe(d, d_n, &core_list, node) |
| if (d->dev == dev) { |
| list_del(&d->node); |
| goto unlock; |
| } |
| unlock: |
| mutex_unlock(&core_list_lock); |
| } |
| |
| int cyttsp5_register_module(struct cyttsp5_module *module) |
| { |
| struct cyttsp5_module *m; |
| struct cyttsp5_core_data *cd; |
| |
| int rc = 0; |
| |
| if (!module || !module->probe || !module->release) |
| return -EINVAL; |
| |
| mutex_lock(&module_list_lock); |
| list_for_each_entry(m, &module_list, node) |
| if (m == module) { |
| rc = -EEXIST; |
| goto unlock; |
| } |
| |
| list_add(&module->node, &module_list); |
| |
| /* Probe the module for each core */ |
| mutex_lock(&core_list_lock); |
| list_for_each_entry(cd, &core_list, node) |
| cyttsp5_probe_module(cd, module); |
| mutex_unlock(&core_list_lock); |
| |
| unlock: |
| mutex_unlock(&module_list_lock); |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_register_module); |
| |
| void cyttsp5_unregister_module(struct cyttsp5_module *module) |
| { |
| struct cyttsp5_module *m, *m_n; |
| struct cyttsp5_core_data *cd; |
| |
| if (!module) |
| return; |
| |
| mutex_lock(&module_list_lock); |
| |
| /* Release the module for each core */ |
| mutex_lock(&core_list_lock); |
| list_for_each_entry(cd, &core_list, node) |
| cyttsp5_release_module(cd, module); |
| mutex_unlock(&core_list_lock); |
| |
| list_for_each_entry_safe(m, m_n, &module_list, node) |
| if (m == module) { |
| list_del(&m->node); |
| break; |
| } |
| |
| mutex_unlock(&module_list_lock); |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_unregister_module); |
| |
| void *cyttsp5_get_module_data(struct device *dev, struct cyttsp5_module *module) |
| { |
| struct cyttsp5_core_data *cd = dev_get_drvdata(dev); |
| struct module_node *m; |
| void *data = NULL; |
| |
| mutex_lock(&cd->module_list_lock); |
| list_for_each_entry(m, &cd->module_list, node) |
| if (m->module == module) { |
| data = m->data; |
| break; |
| } |
| mutex_unlock(&cd->module_list_lock); |
| |
| return data; |
| } |
| EXPORT_SYMBOL(cyttsp5_get_module_data); |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void cyttsp5_early_suspend(struct early_suspend *h) |
| { |
| struct cyttsp5_core_data *cd = |
| container_of(h, struct cyttsp5_core_data, es); |
| |
| call_atten_cb(cd, CY_ATTEN_SUSPEND, 0); |
| } |
| |
| static void cyttsp5_late_resume(struct early_suspend *h) |
| { |
| struct cyttsp5_core_data *cd = |
| container_of(h, struct cyttsp5_core_data, es); |
| |
| call_atten_cb(cd, CY_ATTEN_RESUME, 0); |
| } |
| |
| static void cyttsp5_setup_early_suspend(struct cyttsp5_core_data *cd) |
| { |
| cd->es.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; |
| cd->es.suspend = cyttsp5_early_suspend; |
| cd->es.resume = cyttsp5_late_resume; |
| |
| register_early_suspend(&cd->es); |
| } |
| #elif defined(CONFIG_FB) |
| static int fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct cyttsp5_core_data *cd = |
| container_of(self, struct cyttsp5_core_data, fb_notifier); |
| struct fb_event *evdata = data; |
| int *blank; |
| |
| if (event != FB_EVENT_BLANK || !evdata) |
| goto exit; |
| |
| blank = evdata->data; |
| |
| switch (*blank) { |
| case FB_BLANK_UNBLANK: |
| dev_dbg(cd->dev, "%s: UNBLANK!\n", __func__); |
| if (cd->fb_state != FB_ON) { |
| #ifdef USE_FB_SUSPEND_RESUME |
| cyttsp5_core_resume(cd->dev); |
| cd->wake_initiated_by_device = 0; |
| #endif |
| call_atten_cb(cd, CY_ATTEN_RESUME, 0); |
| cd->fb_state = FB_ON; |
| } |
| break; |
| |
| case FB_BLANK_POWERDOWN: |
| case FB_BLANK_HSYNC_SUSPEND: |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| dev_dbg(cd->dev, "%s: POWERDOWN!\n", __func__); |
| if (cd->fb_state != FB_OFF) { |
| #ifdef USE_FB_SUSPEND_RESUME |
| cyttsp5_core_suspend(cd->dev); |
| #endif |
| call_atten_cb(cd, CY_ATTEN_SUSPEND, 0); |
| cd->fb_state = FB_OFF; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| exit: |
| return 0; |
| } |
| |
| static void cyttsp5_setup_fb_notifier(struct cyttsp5_core_data *cd) |
| { |
| int rc; |
| |
| cd->fb_state = FB_ON; |
| |
| cd->fb_notifier.notifier_call = fb_notifier_callback; |
| |
| rc = fb_register_client(&cd->fb_notifier); |
| if (rc) |
| dev_err(cd->dev, "Unable to register fb_notifier: %d\n", rc); |
| } |
| #endif |
| |
| static int cyttsp5_setup_irq_gpio(struct cyttsp5_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| unsigned long irq_flags; |
| int rc; |
| |
| /* Initialize IRQ */ |
| cd->irq = gpio_to_irq(cd->cpdata->irq_gpio); |
| if (cd->irq < 0) |
| return -EINVAL; |
| |
| cd->irq_enabled = true; |
| |
| parade_debug(dev, DEBUG_LEVEL_1, "%s: initialize threaded irq=%d\n", |
| __func__, cd->irq); |
| if (cd->cpdata->level_irq_udelay > 0) |
| /* use level triggered interrupts */ |
| irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT; |
| else |
| /* use edge triggered interrupts */ |
| irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; |
| |
| rc = request_threaded_irq(cd->irq, NULL, cyttsp5_irq, irq_flags, |
| dev_name(dev), cd); |
| if (rc < 0) |
| dev_err(dev, "%s: Error, could not request irq\n", __func__); |
| |
| return rc; |
| } |
| |
| |
| static int cyttsp5_power_init(struct cyttsp5_core_data *cd, bool on) |
| { |
| int rc; |
| |
| if (!on) |
| goto pwr_deinit; |
| |
| cd->vdd = regulator_get(cd->dev, "vdd"); |
| if (IS_ERR(cd->vdd)) { |
| rc = PTR_ERR(cd->vdd); |
| dev_err(cd->dev, |
| "Regulator get failed vdd rc=%d\n", rc); |
| return rc; |
| } |
| |
| if (regulator_count_voltages(cd->vdd) > 0) { |
| rc = regulator_set_voltage(cd->vdd, FT_VTG_MIN_UV, |
| FT_VTG_MAX_UV); |
| if (rc) { |
| dev_err(cd->dev, |
| "Regulator set_vtg failed vdd rc=%d\n", rc); |
| goto reg_vdd_put; |
| } |
| } |
| |
| cd->vcc_i2c = regulator_get(cd->dev, "vcc_i2c"); |
| if (IS_ERR(cd->vcc_i2c)) { |
| rc = PTR_ERR(cd->vcc_i2c); |
| dev_err(cd->dev, |
| "Regulator get failed vcc_i2c rc=%d\n", rc); |
| goto reg_vdd_set_vtg; |
| } |
| |
| if (regulator_count_voltages(cd->vcc_i2c) > 0) { |
| rc = regulator_set_voltage(cd->vcc_i2c, FT_I2C_VTG_MIN_UV, |
| FT_I2C_VTG_MAX_UV); |
| if (rc) { |
| dev_err(cd->dev, |
| "Regulator set_vtg failed vcc_i2c rc=%d\n", rc); |
| goto reg_vcc_i2c_put; |
| } |
| } |
| |
| return 0; |
| |
| reg_vcc_i2c_put: |
| regulator_put(cd->vcc_i2c); |
| reg_vdd_set_vtg: |
| if (regulator_count_voltages(cd->vdd) > 0) |
| regulator_set_voltage(cd->vdd, 0, FT_VTG_MAX_UV); |
| reg_vdd_put: |
| regulator_put(cd->vdd); |
| return rc; |
| |
| pwr_deinit: |
| if (regulator_count_voltages(cd->vdd) > 0) |
| regulator_set_voltage(cd->vdd, 0, FT_VTG_MAX_UV); |
| |
| regulator_put(cd->vdd); |
| |
| if (regulator_count_voltages(cd->vcc_i2c) > 0) |
| regulator_set_voltage(cd->vcc_i2c, 0, FT_I2C_VTG_MAX_UV); |
| |
| regulator_put(cd->vcc_i2c); |
| return 0; |
| } |
| |
| static int cyttsp5_ts_pinctrl_init(struct cyttsp5_core_data *cd) |
| { |
| int retval; |
| |
| /* Get pinctrl if target uses pinctrl */ |
| cd->ts_pinctrl = devm_pinctrl_get(cd->dev); |
| if (IS_ERR_OR_NULL(cd->ts_pinctrl)) { |
| retval = PTR_ERR(cd->ts_pinctrl); |
| dev_dbg(cd->dev, |
| "Target does not use pinctrl %d\n", retval); |
| goto err_pinctrl_get; |
| } |
| |
| cd->pinctrl_state_active |
| = pinctrl_lookup_state(cd->ts_pinctrl, |
| PINCTRL_STATE_ACTIVE); |
| if (IS_ERR_OR_NULL(cd->pinctrl_state_active)) { |
| retval = PTR_ERR(cd->pinctrl_state_active); |
| dev_err(cd->dev, |
| "Can not lookup %s pinstate %d\n", |
| PINCTRL_STATE_ACTIVE, retval); |
| goto err_pinctrl_lookup; |
| } |
| |
| cd->pinctrl_state_suspend |
| = pinctrl_lookup_state(cd->ts_pinctrl, |
| PINCTRL_STATE_SUSPEND); |
| if (IS_ERR_OR_NULL(cd->pinctrl_state_suspend)) { |
| retval = PTR_ERR(cd->pinctrl_state_suspend); |
| dev_err(cd->dev, |
| "Can not lookup %s pinstate %d\n", |
| PINCTRL_STATE_SUSPEND, retval); |
| goto err_pinctrl_lookup; |
| } |
| |
| cd->pinctrl_state_release |
| = pinctrl_lookup_state(cd->ts_pinctrl, |
| PINCTRL_STATE_RELEASE); |
| if (IS_ERR_OR_NULL(cd->pinctrl_state_release)) { |
| retval = PTR_ERR(cd->pinctrl_state_release); |
| dev_dbg(cd->dev, |
| "Can not lookup %s pinstate %d\n", |
| PINCTRL_STATE_RELEASE, retval); |
| } |
| |
| return 0; |
| |
| err_pinctrl_lookup: |
| devm_pinctrl_put(cd->ts_pinctrl); |
| err_pinctrl_get: |
| cd->ts_pinctrl = NULL; |
| return retval; |
| } |
| |
| int cyttsp5_probe(const struct cyttsp5_bus_ops *ops, struct device *dev, |
| u16 irq, size_t xfer_buf_size) |
| { |
| struct cyttsp5_core_data *cd; |
| struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); |
| enum cyttsp5_atten_type type; |
| struct i2c_client *client = to_i2c_client(dev); |
| int rc = 0; |
| |
| /* Set default values on first probe */ |
| if (cyttsp5_first_probe) { |
| cyttsp5_first_probe = false; |
| is_cyttsp5_probe_success = false; |
| cyttsp5_bus_ops_save = NULL; |
| } |
| |
| if (!pdata || !pdata->core_pdata || !pdata->mt_pdata) { |
| dev_err(dev, "%s: Missing platform data\n", __func__); |
| rc = -ENODEV; |
| goto error_no_pdata; |
| } |
| |
| if (pdata->core_pdata->flags & CY_CORE_FLAG_POWEROFF_ON_SLEEP) { |
| if (!pdata->core_pdata->power) { |
| dev_err(dev, "%s: Missing platform data function\n", |
| __func__); |
| rc = -ENODEV; |
| goto error_no_pdata; |
| } |
| } |
| |
| /* get context and debug print buffers */ |
| cd = devm_kzalloc(dev, sizeof(*cd), GFP_KERNEL); |
| if (!cd) { |
| rc = -ENOMEM; |
| goto error_alloc_data; |
| } |
| |
| /* Initialize device info */ |
| cd->dev = dev; |
| cd->pdata = pdata; |
| cd->cpdata = pdata->core_pdata; |
| cd->bus_ops = ops; |
| cd->debug_level = CY_INITIAL_DEBUG_LEVEL; |
| cd->watchdog_interval = CY_WATCHDOG_TIMEOUT; |
| cd->show_timestamp = CY_INITIAL_SHOW_TIME_STAMP; |
| scnprintf(cd->core_id, 20, "%s%d", CYTTSP5_CORE_NAME, core_number++); |
| |
| /* Initialize mutexes and spinlocks */ |
| mutex_init(&cd->module_list_lock); |
| mutex_init(&cd->system_lock); |
| mutex_init(&cd->adap_lock); |
| mutex_init(&cd->hid_report_lock); |
| spin_lock_init(&cd->spinlock); |
| |
| /* Initialize module list */ |
| INIT_LIST_HEAD(&cd->module_list); |
| |
| /* Initialize attention lists */ |
| for (type = 0; type < CY_ATTEN_NUM_ATTEN; type++) |
| INIT_LIST_HEAD(&cd->atten_list[type]); |
| |
| /* Initialize parameter list */ |
| INIT_LIST_HEAD(&cd->param_list); |
| |
| /* Initialize wait queue */ |
| init_waitqueue_head(&cd->wait_q); |
| |
| rc = cyttsp5_ts_pinctrl_init(cd); |
| if (!rc && cd->ts_pinctrl) { |
| /* |
| * Pinctrl handle is optional. If pinctrl handle is found |
| * let pins to be configured in active state. If not |
| * found continue further without error. |
| */ |
| rc = pinctrl_select_state(cd->ts_pinctrl, |
| cd->pinctrl_state_active); |
| if (rc < 0) |
| dev_err(&client->dev, |
| "failed to select pin to active state"); |
| } |
| |
| rc = cyttsp5_power_init(cd, true); |
| if (rc < 0) |
| dev_err(&client->dev, "failed to cyttsp5_power_init"); |
| |
| rc = regulator_enable(cd->vdd); |
| if (rc) { |
| dev_err(dev, "Regulator vdd enable failed rc=%d\n", rc); |
| goto error_power; |
| } |
| |
| rc = regulator_enable(cd->vcc_i2c); |
| if (rc) { |
| dev_err(dev, "Regulator vcc_i2c enable failed rc=%d\n", rc); |
| regulator_disable(cd->vdd); |
| goto error_power; |
| } |
| |
| /* Initialize works */ |
| INIT_WORK(&cd->startup_work, cyttsp5_startup_work_function); |
| INIT_WORK(&cd->watchdog_work, cyttsp5_watchdog_work); |
| |
| /* Initialize HID specific data */ |
| cd->hid_core.hid_vendor_id = (cd->cpdata->vendor_id) ? |
| cd->cpdata->vendor_id : CY_HID_VENDOR_ID; |
| cd->hid_core.hid_product_id = (cd->cpdata->product_id) ? |
| cd->cpdata->product_id : CY_HID_APP_PRODUCT_ID; |
| cd->hid_core.hid_desc_register = |
| cpu_to_le16(cd->cpdata->hid_desc_register); |
| |
| /* Set platform easywake value */ |
| cd->easy_wakeup_gesture = cd->cpdata->easy_wakeup_gesture; |
| |
| /* Set Panel ID to Not Enabled */ |
| cd->panel_id = PANEL_ID_NOT_ENABLED; |
| |
| dev_set_drvdata(dev, cd); |
| cyttsp5_add_core(dev); |
| |
| /*create ttdl_restart sysfs node is probe failed*/ |
| if (!is_cyttsp5_probe_success) |
| device_create_file(dev, &dev_attr_ttdl_restart); |
| |
| /* |
| * Save the pointer to a global value, which will be used |
| * in ttdl_restart function |
| */ |
| cyttsp5_bus_ops_save = ops; |
| |
| /* Call platform init function */ |
| if (cd->cpdata->init) { |
| parade_debug(cd->dev, DEBUG_LEVEL_1, "%s: Init HW\n", __func__); |
| rc = cd->cpdata->init(cd->cpdata, 1, cd->dev); |
| } else { |
| dev_info(cd->dev, "%s: No HW INIT function\n", __func__); |
| rc = 0; |
| } |
| if (rc < 0) |
| dev_err(cd->dev, "%s: HW Init fail r=%d\n", __func__, rc); |
| |
| /* Call platform detect function */ |
| if (cd->cpdata->detect) { |
| dev_info(cd->dev, "%s: Detect HW\n", __func__); |
| rc = cd->cpdata->detect(cd->cpdata, cd->dev, |
| cyttsp5_platform_detect_read); |
| if (rc) { |
| dev_info(cd->dev, "%s: No HW detected\n", __func__); |
| rc = -ENODEV; |
| goto error_detect; |
| } |
| } |
| |
| /* Setup watchdog timer */ |
| setup_timer(&cd->watchdog_timer, cyttsp5_watchdog_timer, |
| (unsigned long)cd); |
| |
| rc = cyttsp5_setup_irq_gpio(cd); |
| if (rc < 0) { |
| dev_err(dev, "%s: Error, could not setup IRQ\n", __func__); |
| goto error_setup_irq; |
| } |
| |
| parade_debug(dev, DEBUG_LEVEL_1, "%s: add sysfs interfaces\n", |
| __func__); |
| rc = add_sysfs_interfaces(dev); |
| if (rc < 0) { |
| dev_err(dev, "%s: Error, fail sysfs init\n", __func__); |
| goto error_attr_create; |
| } |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| mutex_init(&cd->tthe_lock); |
| cd->tthe_debugfs = debugfs_create_file(CYTTSP5_TTHE_TUNER_FILE_NAME, |
| 0644, NULL, cd, &tthe_debugfs_fops); |
| #endif |
| rc = device_init_wakeup(dev, 1); |
| if (rc < 0) |
| dev_err(dev, "%s: Error, device_init_wakeup rc:%d\n", |
| __func__, rc); |
| |
| pm_runtime_get_noresume(dev); |
| pm_runtime_set_active(dev); |
| pm_runtime_enable(dev); |
| |
| /* |
| * call startup directly to ensure that the device |
| * is tested before leaving the probe |
| */ |
| parade_debug(dev, DEBUG_LEVEL_1, "%s: call startup\n", __func__); |
| rc = cyttsp5_startup(cd, false); |
| |
| pm_runtime_put_sync(dev); |
| |
| /* Do not fail probe if startup fails but the device is detected */ |
| if (rc == -ENODEV) { |
| dev_err(cd->dev, "%s: Fail initial startup r=%d\n", |
| __func__, rc); |
| goto error_startup; |
| } |
| |
| rc = cyttsp5_mt_probe(dev); |
| if (rc < 0) { |
| dev_err(dev, "%s: Error, fail mt probe\n", __func__); |
| goto error_startup; |
| } |
| |
| rc = cyttsp5_btn_probe(dev); |
| if (rc < 0) { |
| dev_err(dev, "%s: Error, fail btn probe\n", __func__); |
| goto error_startup_mt; |
| } |
| |
| rc = cyttsp5_proximity_probe(dev); |
| if (rc < 0) { |
| dev_err(dev, "%s: Error, fail proximity probe\n", __func__); |
| goto error_startup_btn; |
| } |
| |
| /* Probe registered modules */ |
| cyttsp5_probe_modules(cd); |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| cyttsp5_setup_early_suspend(cd); |
| #elif defined(CONFIG_FB) |
| cyttsp5_setup_fb_notifier(cd); |
| #endif |
| |
| #ifdef NEED_SUSPEND_NOTIFIER |
| cd->pm_notifier.notifier_call = cyttsp5_pm_notifier; |
| register_pm_notifier(&cd->pm_notifier); |
| #endif |
| is_cyttsp5_probe_success = true; |
| return 0; |
| |
| error_startup_btn: |
| cyttsp5_btn_release(dev); |
| error_startup_mt: |
| cyttsp5_mt_release(dev); |
| cyttsp5_free_si_ptrs(cd); |
| error_startup: |
| pm_runtime_disable(dev); |
| device_init_wakeup(dev, 0); |
| cancel_work_sync(&cd->startup_work); |
| cyttsp5_stop_wd_timer(cd); |
| remove_sysfs_interfaces(dev); |
| error_attr_create: |
| free_irq(cd->irq, cd); |
| del_timer(&cd->watchdog_timer); |
| error_setup_irq: |
| error_detect: |
| if (cd->cpdata->init) |
| cd->cpdata->init(cd->cpdata, 0, dev); |
| cyttsp5_del_core(dev); |
| dev_set_drvdata(dev, NULL); |
| error_power: |
| cyttsp5_power_init(cd, false); |
| error_alloc_data: |
| error_no_pdata: |
| dev_err(dev, "%s failed.\n", __func__); |
| is_cyttsp5_probe_success = false; |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_probe); |
| |
| int cyttsp5_release(struct cyttsp5_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| |
| /* Release successfully probed modules */ |
| cyttsp5_release_modules(cd); |
| |
| cyttsp5_proximity_release(dev); |
| cyttsp5_btn_release(dev); |
| cyttsp5_mt_release(dev); |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| unregister_early_suspend(&cd->es); |
| #elif defined(CONFIG_FB) |
| fb_unregister_client(&cd->fb_notifier); |
| #endif |
| |
| #ifdef NEED_SUSPEND_NOTIFIER |
| unregister_pm_notifier(&cd->pm_notifier); |
| #endif |
| |
| /* |
| * Suspend the device before freeing the startup_work and stopping |
| * the watchdog since sleep function restarts watchdog on failure |
| */ |
| pm_runtime_suspend(dev); |
| pm_runtime_disable(dev); |
| |
| cancel_work_sync(&cd->startup_work); |
| |
| cyttsp5_stop_wd_timer(cd); |
| |
| device_init_wakeup(dev, 0); |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| mutex_lock(&cd->tthe_lock); |
| cd->tthe_exit = 1; |
| wake_up(&cd->wait_q); |
| mutex_unlock(&cd->tthe_lock); |
| debugfs_remove(cd->tthe_debugfs); |
| #endif |
| remove_sysfs_interfaces(dev); |
| free_irq(cd->irq, cd); |
| if (cd->cpdata->init) |
| cd->cpdata->init(cd->cpdata, 0, dev); |
| dev_set_drvdata(dev, NULL); |
| cyttsp5_del_core(dev); |
| cyttsp5_free_si_ptrs(cd); |
| cyttsp5_free_hid_reports(cd); |
| kfree(cd); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(cyttsp5_release); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Core Driver"); |
| MODULE_AUTHOR("Parade Technologies <ttdrivers@paradetech.com>"); |