| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * pt_core.c |
| * Parade TrueTouch(TM) Standard Product Core Module. |
| * For use with Parade touchscreen controllers. |
| * Supported parts include: |
| * TMA5XX |
| * TMA448 |
| * TMA445A |
| * TT21XXX |
| * TT31XXX |
| * TT4XXXX |
| * TT7XXX |
| * TC3XXX |
| * |
| * Copyright (C) 2015-2020 Parade Technologies |
| * |
| * 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 "pt_regs.h" |
| #include <linux/kthread.h> |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/input/mt.h> |
| #include "nanohub_exports.h" |
| #include "mcu_touch.h" |
| |
| #ifdef PT_PTSBC_SUPPORT |
| #define PT_CORE_PROBE_STARTUP_DELAY_MS 500 |
| #endif /* PT_PTSBC_SUPPORT */ |
| |
| #define PT_CORE_STARTUP_RETRY_COUNT 3 |
| #define PT_REPORT_TOUCH_EVENT_DELAY_TIME_MS 80 |
| |
| MODULE_FIRMWARE(PT_FW_FILE_NAME); |
| |
| static const char *pt_driver_core_name = PT_CORE_NAME; |
| static const char *pt_driver_core_version = PT_DRIVER_VERSION; |
| static const char *pt_driver_core_date = PT_DRIVER_DATE; |
| struct pt_input_event touch_event_list_head; |
| struct workqueue_struct *pt_touch_event_delay_wq; |
| |
| struct pt_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[PT_HID_MAX_COLLECTIONS]; |
| struct pt_hid_report *report; |
| bool record_field; |
| }; |
| |
| struct pt_hid_report { |
| u8 id; |
| u8 type; |
| int size; |
| struct pt_hid_field *fields[PT_HID_MAX_FIELDS]; |
| int num_fields; |
| int record_field_index; |
| int header_size; |
| int record_size; |
| u32 usage_page; |
| int log_collection_num; |
| }; |
| |
| struct atten_node { |
| struct list_head node; |
| char *id; |
| struct device *dev; |
| |
| int (*func)(struct device *dev); |
| int mode; |
| }; |
| |
| struct param_node { |
| struct list_head node; |
| u8 id; |
| u32 value; |
| u8 size; |
| }; |
| |
| struct module_node { |
| struct list_head node; |
| struct pt_module *module; |
| void *data; |
| }; |
| |
| struct pt_hid_cmd { |
| __le16 descriptor; |
| u8 opcode; |
| u8 report_type; |
| union { |
| u8 report_id; |
| u8 power_state; |
| }; |
| u8 has_data_register; |
| size_t write_length; |
| size_t read_length; |
| u8 *write_buf; |
| u8 *read_buf; |
| u8 wait_interrupt; |
| u8 reset_cmd; |
| u16 timeout_ms; |
| }; |
| |
| struct pt_hid_output { |
| u8 cmd_type; |
| u16 length; |
| u8 command_code; |
| size_t write_length; |
| u8 *write_buf; |
| }; |
| |
| struct pt_pip1_cmd { |
| u8 cmd_type; |
| u8 command_code; |
| u8 reset_expected; |
| u16 write_length; |
| u8 *write_buf; |
| u8 novalidate; |
| u16 timeout_ms; |
| }; |
| |
| struct pt_pip2_cmd { |
| u8 cmd_id; |
| u8 cmd_tag_seq; |
| u8 *report_data; |
| u16 report_len; |
| u8 *read_buf; /* to be removed */ |
| u16 actual_read_len; /* to be removed */ |
| u16 timeout_ms; |
| u8 novalidate; |
| }; |
| |
| struct pt_pip3_cmd { |
| u8 cmd_tag_seq; |
| u8 cmd_id; |
| u8 reset_expected; |
| u8 *param; |
| u16 param_len; |
| u16 timeout_ms; |
| }; |
| |
| struct pt_raw_cmd { |
| u8 *write_buf; |
| u16 write_length; |
| u8 *read_buf; |
| u16 read_len; |
| u16 actual_read_len; |
| }; |
| |
| #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 CREATE_PIP1_FW_CMD(command) \ |
| .cmd_type = PIP1_CMD_TYPE_FW, \ |
| .command_code = command |
| |
| #define CREATE_PIP1_BL_CMD(command) \ |
| .cmd_type = PIP1_CMD_TYPE_BL, \ |
| .command_code = command |
| |
| #define INIT_HID_OUTPUT_FROM_PIP_CMD(pip1_cmd) \ |
| .cmd_type = pip1_cmd->cmd_type, \ |
| .command_code = pip1_cmd->command_code, \ |
| .write_length = pip1_cmd->write_length, \ |
| .write_buf = pip1_cmd->write_buf |
| |
| #define INIT_HID_SET_REPORT_HEADER \ |
| .opcode = HID_CMD_SET_REPORT, \ |
| .report_type = 2, /* HID_FEATURE_REPORT */ \ |
| .report_id = PT_HID_OUTPUT_REPORT_ID, \ |
| .has_data_register = 1 /* Data register is used */ |
| |
| #define PT_IS_HID_WRAP_PIP_REPORT(id) \ |
| ((id) == PT_HID_WRAP_PIP_REPORT_ID) |
| |
| #define PT_MAX_PR_BUF_SIZE 2048 |
| |
| void queue_input_event(uint16_t type, uint16_t code, int32_t value, ktime_t timestamp) |
| { |
| struct pt_input_event *input_report_event = |
| kmalloc(sizeof(struct pt_input_event), GFP_KERNEL); |
| input_report_event->type = type; |
| input_report_event->code = code; |
| input_report_event->value = value; |
| input_report_event->timestamp = timestamp; |
| list_add_tail(&input_report_event->list, &touch_event_list_head.list); |
| } |
| /******************************************************************************* |
| * FUNCTION: pt_pr_buf |
| * |
| * SUMMARY: Print out the contents of a buffer to kmsg based on the debug level |
| * |
| * RETURN: Void |
| * |
| * PARAMETERS: |
| * *dev - pointer to Device structure |
| * debug_level - requested debug level to print at |
| * *buf - pointer to buffer to print |
| * buf_len - size of buf |
| * *data_name - Descriptive name of data prefixed to data |
| ******************************************************************************/ |
| void pt_pr_buf(struct device *dev, u8 debug_level, u8 *buf, |
| u16 buf_len, const char *data_name) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| int i; |
| int pr_buf_index = 0; |
| int max_size; |
| |
| /* only proceed if valid debug level and there is data to print */ |
| if (debug_level <= cd->debug_level && buf_len > 0) { |
| char *pr_buf = kzalloc(PT_MAX_PR_BUF_SIZE, GFP_KERNEL); |
| |
| if (!pr_buf) |
| return; |
| |
| /* |
| * With spaces each printed char takes 3 bytes, subtract |
| * the length of the data_name and length prefix and divide 3 |
| */ |
| pr_buf_index += |
| scnprintf(pr_buf, PT_MAX_PR_BUF_SIZE, "%s [0..%d]: ", data_name, buf_len); |
| max_size = (PT_MAX_PR_BUF_SIZE - pr_buf_index) / 3; |
| for (i = 0; i < buf_len && i < max_size; i++) |
| pr_buf_index += scnprintf(pr_buf + pr_buf_index, |
| PT_MAX_PR_BUF_SIZE - pr_buf_index, "%02X ", |
| buf[i]); |
| |
| pt_debug(dev, debug_level, "%s\n", pr_buf); |
| kfree(pr_buf); |
| } |
| } |
| EXPORT_SYMBOL_GPL(pt_pr_buf); |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| /******************************************************************************* |
| * FUNCTION: tthe_print |
| * |
| * SUMMARY: Format data name and time stamp as the header and format the |
| * content of input buffer with hex base to "tthe_buf". And then wake up event |
| * semaphore for tthe debugfs node. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *buf - pointer to input buffer |
| * buf_len - size of input buffer |
| * *data_name - pointer to data name |
| ******************************************************************************/ |
| static int tthe_print(struct pt_core_data *cd, u8 *buf, int buf_len, |
| const u8 *data_name) |
| { |
| int name_len = strlen(data_name); |
| int i, n; |
| u8 *p; |
| int remain; |
| u8 data_name_with_time_stamp[100]; |
| |
| /* Prepend timestamp, if requested, to data_name */ |
| if (cd->show_timestamp) { |
| scnprintf(data_name_with_time_stamp, sizeof(data_name_with_time_stamp), "[%u] %s", |
| pt_get_time_stamp(), data_name); |
| data_name = data_name_with_time_stamp; |
| name_len = strlen(data_name); |
| } |
| |
| mutex_lock(&cd->tthe_lock); |
| if (!cd->tthe_buf) |
| goto exit; |
| |
| /* Add 1 due to the '\n' that is appended at the end */ |
| if (cd->tthe_buf_len + name_len + buf_len + 1 > cd->tthe_buf_size) |
| goto exit; |
| |
| if (name_len + buf_len == 0) |
| goto exit; |
| |
| remain = cd->tthe_buf_size - cd->tthe_buf_len; |
| if (remain < name_len) |
| name_len = remain; |
| |
| p = cd->tthe_buf + cd->tthe_buf_len; |
| memcpy(p, data_name, name_len); |
| cd->tthe_buf_len += name_len; |
| p += name_len; |
| remain -= name_len; |
| |
| *p = 0; |
| for (i = 0; i < buf_len; i++) { |
| n = scnprintf(p, remain, "%02X ", buf[i]); |
| if (n <= 0) |
| break; |
| p += n; |
| remain -= n; |
| cd->tthe_buf_len += n; |
| } |
| |
| n = scnprintf(p, remain, "\n"); |
| cd->tthe_buf_len += n; |
| exit: |
| wake_up(&cd->wait_q); |
| mutex_unlock(&cd->tthe_lock); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_tthe_print |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request to print data to the "tthe_buffer". |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int _pt_request_tthe_print(struct device *dev, u8 *buf, |
| int buf_len, const u8 *data_name) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return tthe_print(cd, buf, buf_len, data_name); |
| } |
| #endif |
| |
| /******************************************************************************* |
| * FUNCTION: pt_platform_detect_read |
| * |
| * SUMMARY: To be passed to platform dectect function to perform a read |
| * operation. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to Device structure |
| * *buf - pointer to buffer where the data read will be stored |
| * size - size to be read |
| ******************************************************************************/ |
| static int pt_platform_detect_read(struct device *dev, void *buf, int size) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return pt_adap_read_default(cd, buf, size); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_add_parameter |
| * |
| * SUMMARY: Adds a parameter that has been altered to the parameter linked list. |
| * On every reset of the DUT this linked list is traversed and all |
| * parameters in it are restored to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * param_id - parameter ID to add |
| * param_value - Value corresponding to the ID |
| * param_size - Size of param_value |
| ******************************************************************************/ |
| static int pt_add_parameter(struct pt_core_data *cd, |
| u8 param_id, u32 param_value, u8 param_size) |
| { |
| struct param_node *param, *param_new; |
| |
| /* Check if parameter already exists in the list */ |
| spin_lock(&cd->spinlock); |
| list_for_each_entry(param, &cd->param_list, node) { |
| if (param->id == param_id) { |
| /* Update parameter */ |
| param->value = param_value; |
| pt_debug(cd->dev, DL_INFO, |
| "%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; |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%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; |
| } |
| |
| #ifndef TTDL_KERNEL_SUBMISSION |
| #ifdef TTDL_DIAGNOSTICS |
| /******************************************************************************* |
| * FUNCTION: pt_erase_parameter_list |
| * |
| * SUMMARY: Empty out the entire parameter linked list of all parameter/value |
| * pairs. In some test cases this functionality is needed to ensure DUT |
| * returns to a virgin state after a reset and no parameters are restored. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_erase_parameter_list(struct pt_core_data *cd) |
| { |
| struct param_node *pos, *temp; |
| |
| spin_lock(&cd->spinlock); |
| list_for_each_entry_safe(pos, temp, &cd->param_list, node) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Parameter Restore List - remove 0x%02x\n", |
| __func__, pos->id); |
| list_del(&pos->node); |
| kfree(pos); |
| } |
| spin_unlock(&cd->spinlock); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_count_parameter_list |
| * |
| * SUMMARY: Count the items in the RAM parameter restor list |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_count_parameter_list(struct pt_core_data *cd) |
| { |
| struct param_node *pos, *temp; |
| int entries = 0; |
| |
| spin_lock(&cd->spinlock); |
| list_for_each_entry_safe(pos, temp, &cd->param_list, node) |
| entries++; |
| spin_unlock(&cd->spinlock); |
| |
| return entries; |
| } |
| #endif /* TTDL_DIAGNOSTICS */ |
| #endif /* !TTDL_KERNEL_SUBMISSION */ |
| |
| /******************************************************************************* |
| * FUNCTION: request_exclusive |
| * |
| * SUMMARY: Request exclusive access to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *ownptr - pointer to device |
| * timeout_ms - Timeout value |
| ******************************************************************************/ |
| int request_exclusive(struct pt_core_data *cd, void *ownptr, |
| int timeout_ms) |
| { |
| int t = msecs_to_jiffies(timeout_ms); |
| bool with_timeout = (timeout_ms != 0); |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: Attempt to Request EXCLUSIVE t=%d\n", __func__, |
| timeout_ms); |
| |
| 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)) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%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); |
| pt_debug(cd->dev, DL_DEBUG, "%s: request exclusive ok=%p\n", |
| __func__, ownptr); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: release_exclusive_ |
| * |
| * SUMMARY: Release exclusive access to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *ownptr - pointer to device |
| ******************************************************************************/ |
| static int release_exclusive_(struct pt_core_data *cd, void *ownptr) |
| { |
| pt_debug(cd->dev, DL_DEBUG, "%s: Attempt to Release EXCLUSIVE\n", __func__); |
| if (cd->exclusive_dev != ownptr) |
| return -EINVAL; |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: exclusive_dev %p freed\n", |
| __func__, cd->exclusive_dev); |
| cd->exclusive_dev = NULL; |
| wake_up(&cd->wait_q); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: release_exclusive |
| * |
| * SUMMARY: Protected wrapper to release_exclusive_() |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *ownptr - pointer to device |
| ******************************************************************************/ |
| int release_exclusive(struct pt_core_data *cd, void *ownptr) |
| { |
| int rc; |
| |
| mutex_lock(&cd->system_lock); |
| rc = release_exclusive_(cd, ownptr); |
| mutex_unlock(&cd->system_lock); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_write_to_hid_cmd_reg_ |
| * |
| * SUMMARY: Send the HID command to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_cmd - pointer to the HID command to write to command register |
| ******************************************************************************/ |
| static int pt_write_to_hid_cmd_reg_(struct pt_core_data *cd, |
| struct pt_hid_cmd *hid_cmd) |
| { |
| int rc = 0; |
| u8 *cmd; |
| u16 cmd_length; |
| u8 cmd_offset = 0; |
| |
| if (hid_cmd->descriptor) { |
| cmd_length = 2; /* hid or report register */ |
| } else { |
| 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; |
| |
| /* hid & report descriptor doesn't require other field */ |
| if (hid_cmd->descriptor) { |
| memcpy(&cmd[cmd_offset], &hid_cmd->descriptor, cmd_length); |
| goto skip_other_field; |
| } |
| |
| /* 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); |
| } |
| |
| if (hid_cmd->opcode == HID_CMD_GET_REPORT) |
| goto skip_other_field; |
| |
| /* 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; |
| } |
| |
| pt_debug(cd->dev, DL_DEBUG, ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", __func__, |
| cmd_length, cmd[HID_CMD_COMMAND_ID_OFFSET]); |
| |
| skip_other_field: |
| pt_pr_buf(cd->dev, DL_DEBUG, cmd, cmd_length, ">>> ATM - HID CMD"); |
| |
| rc = pt_adap_write_read_specific(cd, cmd_length, cmd, hid_cmd->read_buf, |
| hid_cmd->read_length); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Fail pt_adap_transfer\n", __func__); |
| |
| kfree(cmd); |
| return rc; |
| } |
| #ifdef TTDL_DIAGNOSTICS |
| /******************************************************************************* |
| * FUNCTION: pt_toggle_err_gpio |
| * |
| * SUMMARY: Toggles the pre-defined error GPIO |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * type - type of err that occured |
| ******************************************************************************/ |
| void pt_toggle_err_gpio(struct pt_core_data *cd, u8 type) |
| { |
| pt_debug(cd->dev, DL_DEBUG, "%s called with type = %d\n", |
| __func__, type); |
| if (cd->err_gpio && type == cd->err_gpio_type) { |
| pt_debug(cd->dev, DL_WARN, "%s: Toggle ERR GPIO\n", __func__); |
| gpio_direction_output(cd->err_gpio, |
| !gpio_get_value(cd->err_gpio)); |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_toggle_err_gpio |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request to toggle the err_gpio |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * type - type of err that occured |
| ******************************************************************************/ |
| void _pt_request_toggle_err_gpio(struct device *dev, u8 type) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_toggle_err_gpio(cd, type); |
| } |
| #endif /* TTDL_DIAGNOSTICS */ |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_send_cmd_and_wait_ |
| * |
| * SUMMARY: Send the HID command to the DUT and wait for the response |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_cmd - pointer to the HID command to send |
| ******************************************************************************/ |
| static int pt_hid_send_cmd_and_wait_(struct pt_core_data *cd, |
| struct pt_hid_cmd *hid_cmd) |
| { |
| int rc = 0; |
| int t; |
| u16 timeout_ms; |
| int *cmd_state; |
| u16 max_timeout_ms = PT_HID_MULTIPACKET_TIMEOUT; |
| |
| if (hid_cmd->reset_cmd) |
| cmd_state = &cd->hid_reset_cmd_state; |
| else |
| cmd_state = &cd->hid_cmd_state; |
| |
| mutex_lock(&cd->system_lock); |
| *cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = pt_write_to_hid_cmd_reg_(cd, hid_cmd); |
| if (rc) |
| goto error; |
| |
| if (hid_cmd->timeout_ms) |
| timeout_ms = hid_cmd->timeout_ms; |
| else |
| timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT; |
| again: |
| t = wait_event_timeout(cd->wait_q, (*cmd_state == 0), |
| msecs_to_jiffies(timeout_ms)); |
| if (IS_TMO(t)) { |
| if ((cd->pt_hid_buf_op.last_remain_packet != 0) && |
| (max_timeout_ms > timeout_ms)) { |
| max_timeout_ms -= timeout_ms; |
| goto again; |
| } |
| #ifdef TTDL_DIAGNOSTICS |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| #endif /* TTDL_DIAGNOSTICS */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_send_cmd_no_wait_ |
| * |
| * SUMMARY: The function works to send HID command and can read response |
| * directly instead of waiting it to be recevied in interrupt function. It |
| * assgins the read buffer to receive response in following condition: |
| * 1) descriptor is assigned to get hid descriptor or report descripter |
| * 2) output register is assigned for vendor-defined commands (TBD) |
| * |
| * NOTE: If no read buffer is assigned, it only perform send action. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_cmd - pointer to the HID command to send |
| ******************************************************************************/ |
| static int pt_hid_send_cmd_no_wait_(struct pt_core_data *cd, |
| struct pt_hid_cmd *hid_cmd) |
| { |
| int rc = 0; |
| |
| if (hid_cmd->descriptor || |
| hid_cmd->opcode == HID_CMD_GET_REPORT) |
| hid_cmd->read_buf = cd->response_buf; |
| |
| rc = pt_write_to_hid_cmd_reg_(cd, hid_cmd); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_send_command_ |
| * |
| * SUMMARY: Wrapper function to call pt_hid_send_cmd_no_wait_() for HID |
| * protocol and pt_hid_send_cmd_and_wait_() for PIP protocol. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_cmd - pointer to the HID command to send |
| ******************************************************************************/ |
| static int pt_hid_send_command_(struct pt_core_data *cd, |
| struct pt_hid_cmd *hid_cmd) |
| { |
| if (cd->protocol_mode != PT_PROTOCOL_MODE_PIP && |
| !cd->dual_mcu_available) |
| return pt_hid_send_cmd_no_wait_(cd, hid_cmd); |
| else |
| return pt_hid_send_cmd_and_wait_(cd, hid_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_send_user_cmd_and_wait_ |
| * |
| * SUMMARY: Send HID user command to the DUT and wait for the response. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_cmd - pointer to HID command data structure |
| ******************************************************************************/ |
| static int pt_hid_send_user_cmd_and_wait_(struct pt_core_data *cd, |
| struct pt_hid_cmd *hid_cmd) |
| { |
| int rc = 0; |
| int t; |
| u8 cmd_id = 0; |
| u16 max_timeout_ms = PT_HID_MULTIPACKET_TIMEOUT; |
| |
| mutex_lock(&cd->system_lock); |
| /* |
| * The data in write_buf refers to the data in HID command offset 6 ~ N, |
| * so the command id offset in write_buf is 6. |
| */ |
| cmd_id = hid_cmd->write_buf[6] & 0x7F; |
| cd->hid_cmd_state = cmd_id + 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = pt_write_to_hid_cmd_reg_(cd, hid_cmd); |
| if (rc) |
| goto error; |
| if (cmd_id == HID_CMD_ID_START_BOOTLOADER |
| && cd->dual_mcu_available) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Dual MCU is enabled, no BL sentinel after 0x31 cmd\n", |
| __func__); |
| usleep_range(10000, 11000); |
| goto error; |
| } |
| again: |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(PT_HID_CMD_DEFAULT_TIMEOUT)); |
| if (IS_TMO(t)) { |
| if ((cd->pt_hid_buf_op.last_remain_packet != 0) && |
| (max_timeout_ms > PT_HID_CMD_DEFAULT_TIMEOUT)) { |
| max_timeout_ms -= PT_HID_CMD_DEFAULT_TIMEOUT; |
| goto again; |
| } |
| #ifdef TTDL_DIAGNOSTICS |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| #endif /* TTDL_DIAGNOSTICS */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: HID output cmd execution timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_user_cmd_send_ |
| * |
| * SUMMARY: Load HID payload data into a HID cmd structure and send it to DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_pl_buf - pointer to HID cmd payload data |
| * pl_buf_len - HID cmd payload data length |
| ******************************************************************************/ |
| static int pt_hid_user_cmd_send_(struct pt_core_data *cd, |
| u8 *hid_pl_buf, u8 pl_buf_len) |
| { |
| struct pt_hid_cmd hid_cmd = { |
| INIT_HID_SET_REPORT_HEADER, |
| }; |
| int rc = 0; |
| u16 index = 0; |
| |
| /* |
| * 'write_length' is calculated by: |
| * Length field(2) + |
| * REPORT_ID(1) + |
| * Payload (hid_max_output_len) |
| */ |
| hid_cmd.write_length = 3 + cd->hid_core.hid_max_output_len; |
| if (hid_cmd.write_length < (pl_buf_len + 2)) |
| return -EINVAL; |
| hid_cmd.write_buf = kzalloc(hid_cmd.write_length, GFP_KERNEL); |
| if (!hid_cmd.write_buf) |
| return -ENOMEM; |
| |
| /* Set Length Field(2) */ |
| hid_cmd.write_buf[index++] = LOW_BYTE(hid_cmd.write_length); |
| hid_cmd.write_buf[index++] = HI_BYTE(hid_cmd.write_length); |
| |
| /* Copy hid palyload data */ |
| memcpy(&hid_cmd.write_buf[index], hid_pl_buf, |
| pl_buf_len); |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, hid_pl_buf, pl_buf_len, "HID_CMD="); |
| #endif |
| |
| rc = pt_hid_send_user_cmd_and_wait_(cd, &hid_cmd); |
| kfree(hid_cmd.write_buf); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_user_cmd_send |
| * |
| * SUMMARY: Protected call to pt_hid_user_cmd_send_ by exclusive access to |
| * the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_pl_buf - pointer to HID cmd payload data |
| * pl_buf_len - HID cmd payload data length |
| ******************************************************************************/ |
| static int pt_hid_user_cmd_send(struct pt_core_data *cd, |
| u8 *hid_pl_buf, u8 pl_buf_len) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_user_cmd_send_(cd, hid_pl_buf, pl_buf_len); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_cmd_reset_ |
| * |
| * SUMMARY: Send the HID RESET command to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_hid_cmd_reset_(struct pt_core_data *cd) |
| { |
| struct pt_hid_cmd hid_cmd = { |
| .opcode = HID_CMD_RESET, |
| .reset_cmd = 1, |
| .timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT, |
| }; |
| |
| return pt_hid_send_command_(cd, &hid_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_cmd_reset |
| * |
| * SUMMARY: Wrapper function for pt_hid_cmd_reset_ that guarantees exclusive |
| * access. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_hid_cmd_reset(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| pt_debug(cd->dev, DL_INFO, "%s: Send HID Reset command\n", __func__); |
| rc = pt_hid_cmd_reset_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_cmd_get_report_ |
| * |
| * SUMMARY: Send the HID Get report command to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * get_rpt_cmd - Get report command LSB |
| ******************************************************************************/ |
| static int pt_hid_cmd_get_report_(struct pt_core_data *cd, |
| u8 get_rpt_cmd) |
| { |
| struct pt_hid_cmd hid_cmd = { |
| .opcode = HID_CMD_GET_REPORT, |
| .has_data_register = 1, |
| .timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT, |
| }; |
| |
| hid_cmd.report_type = (get_rpt_cmd >> 4) & 0x03; |
| hid_cmd.report_id = get_rpt_cmd & 0x0f; |
| |
| return pt_hid_send_command_(cd, &hid_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_cmd_get_report |
| * |
| * SUMMARY: Wrapper function for pt_hid_cmd_get_report_ that guarantees |
| * exclusive access. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * get_rpt_cmd - Get report command LSB |
| ******************************************************************************/ |
| static int pt_hid_cmd_get_report(struct pt_core_data *cd, |
| u8 get_rpt_cmd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_cmd_get_report_(cd, get_rpt_cmd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_cmd_set_power_ |
| * |
| * SUMMARY: Send hid cmd to set power state for the DUT and wait for response |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * power_state - power state to set(HID_POWER_ON/HID_POWER_SLEEP) |
| ******************************************************************************/ |
| static int pt_hid_cmd_set_power_(struct pt_core_data *cd, |
| u8 power_state) |
| { |
| int rc = 0; |
| struct pt_hid_cmd hid_cmd = { |
| .opcode = HID_CMD_SET_POWER, |
| .timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT, |
| }; |
| hid_cmd.power_state = power_state; |
| |
| /* The chip won't give response if goes to Deep Standby */ |
| if (power_state == HID_POWER_STANDBY) { |
| rc = pt_hid_send_cmd_no_wait_(cd, &hid_cmd); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Failed to set power to state:%d\n", |
| __func__, power_state); |
| else |
| cd->fw_sys_mode_in_standby_state = true; |
| return rc; |
| } |
| cd->fw_sys_mode_in_standby_state = false; |
| |
| rc = pt_hid_send_command_(cd, &hid_cmd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Failed to set power to state:%d\n", |
| __func__, power_state); |
| return rc; |
| } |
| |
| /* HID COMMAND doesn't have a response */ |
| if (cd->protocol_mode != PT_PROTOCOL_MODE_PIP) |
| 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; |
| } |
| |
| #ifndef TTDL_KERNEL_SUBMISSION |
| /******************************************************************************* |
| * FUNCTION: pt_hid_cmd_set_power |
| * |
| * SUMMARY: Wrapper function for pt_hid_cmd_set_power_ that guarantees |
| * exclusive access. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * power_state - power state to set(HID_POWER_ON/HID_POWER_SLEEP) |
| ******************************************************************************/ |
| static int pt_hid_cmd_set_power(struct pt_core_data *cd, |
| u8 power_state) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_cmd_set_power_(cd, power_state); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| #endif /* !TTDL_KERNEL_SUBMISSION */ |
| |
| static const u16 crc_table[16] = { |
| 0x0000, 0x1021, 0x2042, 0x3063, |
| 0x4084, 0x50a5, 0x60c6, 0x70e7, |
| 0x8108, 0x9129, 0xa14a, 0xb16b, |
| 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, |
| }; |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_compute_crc |
| * |
| * SUMMARY: Calculate CRC by CRC table. |
| * |
| * RETURN: |
| * CRC calculation result |
| * |
| * PARAMETERS: |
| * *buf - pointer to the data array to be calculated |
| * size - size of data array |
| ******************************************************************************/ |
| static u16 _pt_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; |
| } |
| |
| u16 ccitt_Table[] = { |
| 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, |
| 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, |
| 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, |
| 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, |
| 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, |
| 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, |
| 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, |
| 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, |
| 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, |
| 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, |
| 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, |
| 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, |
| 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, |
| 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, |
| 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, |
| 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, |
| 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, |
| 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, |
| 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, |
| 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, |
| 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, |
| 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, |
| 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, |
| 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, |
| 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, |
| 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, |
| 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, |
| 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, |
| 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, |
| 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, |
| 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, |
| 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, |
| }; |
| |
| /******************************************************************************* |
| * FUNCTION: crc_ccitt_calculate |
| * |
| * SUMMARY: Calculate CRC with ccitt standard by CRC table. |
| * |
| * RETURN: |
| * CRC calculation result |
| * |
| * PARAMETERS: |
| * *q - pointer to the data array to be calculated |
| * len - size of data array |
| ******************************************************************************/ |
| static unsigned short crc_ccitt_calculate(unsigned char *q, int len) |
| { |
| unsigned short crc = 0xffff; |
| |
| while (len-- > 0) |
| crc = ccitt_Table[(crc >> 8 ^ *q++) & 0xff] ^ (crc << 8); |
| return crc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_next_cmd_seq |
| * |
| * SUMMARY: Gets the next sequence number for a PIP2 command. The sequence |
| * number is a 3 bit value (bits [0-2]) but because TTDL will always have |
| * the TAG bit set (bit 3), the counter starts at 0x08 and goes to 0x0F. |
| * If the "force_pip2_seq" holds a valid seq value (0x08-0x0F) then do not |
| * increment, just use the forced value. |
| * |
| * RETURN: Next command sequence number [0x08-0x0F] |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static u8 pt_pip2_get_next_cmd_seq(struct pt_core_data *cd) |
| { |
| #ifdef TTDL_DIAGNOSTICS |
| if (cd->force_pip2_seq <= 0x07) { |
| cd->pip2_cmd_tag_seq++; |
| if (cd->pip2_cmd_tag_seq > 0x0F) |
| cd->pip2_cmd_tag_seq = 0x08; |
| } else { |
| cd->pip2_cmd_tag_seq = cd->force_pip2_seq; |
| } |
| #else |
| cd->pip2_cmd_tag_seq++; |
| if (cd->pip2_cmd_tag_seq > 0x0F) |
| cd->pip2_cmd_tag_seq = 0x08; |
| #endif |
| return cd->pip2_cmd_tag_seq; |
| } |
| |
| /* |
| * Following macros are to define the response time (the interval between PIP2 |
| * command finishes sending and INT pin falls). The unit is in microsecond. |
| * It has different time settings between the solution GPIO polling and Bus |
| * polling due to the considration for system load. |
| */ |
| #ifdef PT_POLL_RESP_BY_BUS |
| #define POLL_RETRY_DEFAULT_INTERVAL 50 |
| #define PIP2_RESP_DEFAULT_TIME_MIN 50 |
| #define PIP2_RESP_DEFAULT_TIME_MAX (PT_PIP_CMD_DEFAULT_TIMEOUT * 1000) |
| #define PIP2_RESP_FILE_WRITE_TIME_MIN 220 |
| #define PIP2_RESP_FILE_IOCTL_TIME_MAX (PT_PIP2_CMD_FILE_ERASE_TIMEOUT * 1000) |
| #else |
| #define POLL_RETRY_DEFAULT_INTERVAL 20 |
| #define PIP2_RESP_DEFAULT_TIME_MIN 20 |
| #define PIP2_RESP_DEFAULT_TIME_MAX (PT_PIP_CMD_DEFAULT_TIMEOUT * 1000) |
| #define PIP2_RESP_FILE_WRITE_TIME_MIN 20 |
| #define PIP2_RESP_FILE_IOCTL_TIME_MAX (PT_PIP2_CMD_FILE_ERASE_TIMEOUT * 1000) |
| #endif |
| /* |
| * id: the command id defined in PIP2 |
| * response_len: the (maximum) length of response. |
| * response_time_min: minimum response time in microsecond |
| * response_time_max: maximum response time in microsecond |
| */ |
| static const struct pip2_cmd_response_structure pip2_cmd_response[] = { |
| {.id = PIP2_CMD_ID_PING, |
| .response_len = 255, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_STATUS, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 5, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_CTRL, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PT_PIP2_CMD_FILE_ERASE_TIMEOUT}, |
| {.id = PIP2_CMD_ID_CONFIG, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_CLEAR, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 0, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_RESET, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_VERSION, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 23, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_FILE_OPEN, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 2, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_FILE_CLOSE, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_FILE_READ, |
| .response_len = 255, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_FILE_WRITE, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_FILE_WRITE_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_FILE_IOCTL, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 10, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_FILE_IOCTL_TIME_MAX}, |
| {.id = PIP2_CMD_ID_FLASH_INFO, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 17, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_EXECUTE, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_GET_LAST_ERRNO, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 3, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_EXIT_HOST_MODE, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_READ_GPIO, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 5, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_EXECUTE_SCAN, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_SET_PARAMETER, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_GET_PARAMETER, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 7, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_SET_DDI_REG, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 1, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_GET_DDI_REG, |
| .response_len = PIP2_EXTRA_BYTES_NUM + 249, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, |
| {.id = PIP2_CMD_ID_END, |
| .response_len = 255, |
| .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, |
| .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX} |
| }; |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_cmd_response_len |
| * |
| * SUMMARY: Gets the expected response length based on the command ID |
| * |
| * RETURN: Expected response length |
| * |
| * PARAMETERS: |
| * id - Command ID (-1 means input ID is not in list of PIP2 command) |
| ******************************************************************************/ |
| static int pt_pip2_get_cmd_response_len(u8 id) |
| { |
| const struct pip2_cmd_response_structure *p = pip2_cmd_response; |
| |
| while ((p->id != id) && (p->id != PIP2_CMD_ID_END)) |
| p++; |
| |
| if (p->id != PIP2_CMD_ID_END) |
| return p->response_len; |
| else |
| return -1; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_cmd_resp_time_min |
| * |
| * SUMMARY: Gets the minimum response time (the interval between PIP2 command |
| * finishes sending and INT pin falls) based on the command ID |
| * |
| * RETURN: Estimated minimum response time in microsecond |
| * |
| * PARAMETERS: |
| * id - Command ID |
| ******************************************************************************/ |
| static u32 pt_pip2_get_cmd_resp_time_min(u8 id) |
| { |
| const struct pip2_cmd_response_structure *p = pip2_cmd_response; |
| |
| while ((p->id != id) && (p->id != PIP2_CMD_ID_END)) |
| p++; |
| |
| if (p->id != PIP2_CMD_ID_END) |
| return p->response_time_min; |
| else |
| return PIP2_RESP_DEFAULT_TIME_MIN; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_cmd_resp_time_max |
| * |
| * SUMMARY: Gets the maximum response time (the interval between PIP2 command |
| * finishes sending and INT pin falls) based on the command ID |
| * |
| * RETURN: Estimated maximum response time in microsecond |
| * |
| * PARAMETERS: |
| * id - Command ID |
| ******************************************************************************/ |
| static u32 pt_pip2_get_cmd_resp_time_max(u8 id) |
| { |
| const struct pip2_cmd_response_structure *p = pip2_cmd_response; |
| |
| while ((p->id != id) && (p->id != PIP2_CMD_ID_END)) |
| p++; |
| |
| if (p->id != PIP2_CMD_ID_END) |
| return p->response_time_max; |
| else |
| return PIP2_RESP_DEFAULT_TIME_MAX; |
| } |
| |
| static const struct PIP_vs_PURE_HID_cmd_id_map PIP1_TO_HID_CMD_ID_MAP[] = { |
| {.pip_cmd_id = PIP1_CMD_ID_ENTER_EASYWAKE_STATE, |
| .hid_cmd_id = HID_CMD_ID_ENTER_EASYWAKE_STATE}, |
| {.pip_cmd_id = PIP1_CMD_ID_START_BOOTLOADER, |
| .hid_cmd_id = HID_CMD_ID_START_BOOTLOADER}, |
| {.pip_cmd_id = PIP1_CMD_ID_GET_NOISE_METRICS, |
| .hid_cmd_id = HID_CMD_ID_GET_NOISE_METRICS}, |
| {.pip_cmd_id = PIP1_CMD_ID_GET_SYSINFO, |
| .hid_cmd_id = HID_CMD_ID_GET_SYSINFO}, |
| {.pip_cmd_id = PIP1_CMD_ID_SUSPEND_SCANNING, |
| .hid_cmd_id = HID_CMD_ID_SUSPEND_SCANNING}, |
| {.pip_cmd_id = PIP1_CMD_ID_RESUME_SCANNING, |
| .hid_cmd_id = HID_CMD_ID_RESUME_SCANNING}, |
| {.pip_cmd_id = PIP1_CMD_ID_GET_PARAM, |
| .hid_cmd_id = HID_CMD_ID_GET_PARAM}, |
| {.pip_cmd_id = PIP1_CMD_ID_SET_PARAM, |
| .hid_cmd_id = HID_CMD_ID_SET_PARAM}, |
| {.pip_cmd_id = PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC, |
| .hid_cmd_id = HID_CMD_ID_VERIFY_CONFIG_BLOCK_CRC}, |
| {.pip_cmd_id = PIP1_CMD_ID_GET_CONFIG_ROW_SIZE, |
| .hid_cmd_id = HID_CMD_ID_GET_CONFIG_ROW_SIZE}, |
| {.pip_cmd_id = PIP1_CMD_ID_READ_DATA_BLOCK, |
| .hid_cmd_id = HID_CMD_ID_READ_DATA_BLOCK}, |
| {.pip_cmd_id = PIP1_CMD_ID_WRITE_DATA_BLOCK, |
| .hid_cmd_id = HID_CMD_ID_WRITE_DATA_BLOCK}, |
| {.pip_cmd_id = PIP1_CMD_ID_GET_DATA_STRUCTURE, |
| .hid_cmd_id = HID_CMD_ID_GET_DATA_STRUCTURE}, |
| {.pip_cmd_id = PIP1_CMD_ID_LOAD_SELF_TEST_PARAM, |
| .hid_cmd_id = HID_CMD_ID_LOAD_SELF_TEST_PARAM}, |
| {.pip_cmd_id = PIP1_CMD_ID_RUN_SELF_TEST, |
| .hid_cmd_id = HID_CMD_ID_RUN_SELF_TEST}, |
| {.pip_cmd_id = PIP1_CMD_ID_GET_SELF_TEST_RESULT, |
| .hid_cmd_id = HID_CMD_ID_GET_SELF_TEST_RESULT}, |
| {.pip_cmd_id = PIP1_CMD_ID_CALIBRATE_IDACS, |
| .hid_cmd_id = HID_CMD_ID_CALIBRATE_IDACS}, |
| {.pip_cmd_id = PIP1_CMD_ID_INITIALIZE_BASELINES, |
| .hid_cmd_id = HID_CMD_ID_INITIALIZE_BASELINES}, |
| {.pip_cmd_id = PIP1_CMD_ID_EXEC_PANEL_SCAN, |
| .hid_cmd_id = HID_CMD_ID_EXEC_PANEL_SCAN}, |
| {.pip_cmd_id = PIP1_CMD_ID_RETRIEVE_PANEL_SCAN, |
| .hid_cmd_id = HID_CMD_ID_RETRIEVE_PANEL_SCAN}, |
| {.pip_cmd_id = PIP1_CMD_ID_START_SENSOR_DATA_MODE, |
| .hid_cmd_id = HID_CMD_ID_START_SENSOR_DATA_MODE}, |
| {.pip_cmd_id = PIP1_CMD_ID_STOP_SENSOR_DATA_MODE, |
| .hid_cmd_id = HID_CMD_ID_STOP_SENSOR_DATA_MODE}, |
| {.pip_cmd_id = PIP1_CMD_ID_START_TRACKING_HEATMAP_MODE, |
| .hid_cmd_id = HID_CMD_ID_START_TRACKING_HEATMAP_MODE}, |
| {.pip_cmd_id = PIP1_CMD_ID_CALIBRATE_DEVICE_EXTENDED, |
| .hid_cmd_id = HID_CMD_ID_CALIBRATE_DEVICE_EXTENDED}, |
| {.pip_cmd_id = PIP1_CMD_ID_LAST, |
| .hid_cmd_id = HID_CMD_ID_END}, |
| }; |
| |
| static const struct PIP_vs_PURE_HID_cmd_id_map PIP2_TO_HID_CMD_ID_MAP[] = { |
| {.pip_cmd_id = PIP2_CMD_ID_STATUS, |
| .hid_cmd_id = HID_CMD_ID_STATUS}, |
| {.pip_cmd_id = PIP2_CMD_ID_VERSION, |
| .hid_cmd_id = HID_CMD_ID_VERSION}, |
| {.pip_cmd_id = PIP2_CMD_EXECUTE_SCAN, |
| .hid_cmd_id = HID_CMD_ID_EXEC_PANEL_SCAN}, |
| {.pip_cmd_id = PIP2_CMD_SET_PARAMETER, |
| .hid_cmd_id = HID_CMD_ID_SET_PARAM}, |
| {.pip_cmd_id = PIP2_CMD_GET_PARAMETER, |
| .hid_cmd_id = HID_CMD_ID_GET_PARAM}, |
| {.pip_cmd_id = PIP2_CMD_SET_DDI_REG, |
| .hid_cmd_id = HID_CMD_ID_SET_DDI_REG}, |
| {.pip_cmd_id = PIP2_CMD_GET_DDI_REG, |
| .hid_cmd_id = HID_CMD_ID_GET_DDI_REG}, |
| {.pip_cmd_id = PIP2_CMD_REALTIME_SIGNAL_MODE, |
| .hid_cmd_id = HID_CMD_ID_REALTIME_SIGNAL_MODE}, |
| {.pip_cmd_id = PIP2_CMD_ID_END, |
| .hid_cmd_id = HID_CMD_ID_END}, |
| }; |
| |
| static u8 pt_get_hid_cmd_id_from_pip(u8 id, |
| const struct PIP_vs_PURE_HID_cmd_id_map *id_map) |
| { |
| const struct PIP_vs_PURE_HID_cmd_id_map *p = id_map; |
| |
| while ((p->pip_cmd_id != id) && (p->hid_cmd_id != HID_CMD_ID_END)) |
| p++; |
| |
| return p->hid_cmd_id; |
| } |
| |
| static u8 pt_get_pip_cmd_id_from_hid(u8 id, |
| const struct PIP_vs_PURE_HID_cmd_id_map *id_map) |
| { |
| const struct PIP_vs_PURE_HID_cmd_id_map *p = id_map; |
| |
| while ((p->hid_cmd_id != id) && (p->hid_cmd_id != HID_CMD_ID_END)) |
| p++; |
| |
| return p->pip_cmd_id; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_validate_response |
| * |
| * SUMMARY: Validate the response of PIP2 command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip2_cmd - pointer to PIP2 command structure |
| ******************************************************************************/ |
| static int pt_pip2_validate_response(struct pt_core_data *cd, |
| struct pt_pip2_cmd *pip2_cmd) |
| { |
| int rc = 0; |
| u8 response_seq = 0; |
| u8 reserved_bits = 0; |
| u8 cmd_id = 0; |
| u8 response_bit = 0; |
| unsigned short calc_crc = 0; |
| unsigned short resp_crc = 0; |
| |
| /* Verify the length of response buffer */ |
| if (pip2_cmd->actual_read_len < PT_MIN_PIP2_PACKET_SIZE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s cmd[0x%02X] read lenght ERR: read_len = %d\n", |
| __func__, pip2_cmd->cmd_id, |
| pip2_cmd->actual_read_len); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| /* Verify the CRC */ |
| calc_crc = crc_ccitt_calculate(pip2_cmd->read_buf, |
| pip2_cmd->actual_read_len - 2); |
| resp_crc = pip2_cmd->read_buf[pip2_cmd->actual_read_len - 2] << 8; |
| resp_crc |= pip2_cmd->read_buf[pip2_cmd->actual_read_len - 1]; |
| if (resp_crc != calc_crc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: cmd[0x%02X] CRC ERR: calc=0x%04X rsp=0x%04X\n", |
| __func__, pip2_cmd->cmd_id, calc_crc, resp_crc); |
| #ifdef TTDL_DIAGNOSTICS |
| cd->pip2_crc_error_count++; |
| #endif /* TTDL_DIAGNOSTICS */ |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| /* Verify the response bit is set */ |
| response_bit = pip2_cmd->read_buf[PIP2_RESP_REPORT_ID_OFFSET] & 0x80; |
| if (!response_bit) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s cmd[0x%02X] response bit ERR: response_bit = %d\n", |
| __func__, pip2_cmd->cmd_id, response_bit); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| /* Verify the command ID matches from command to response */ |
| cmd_id = pip2_cmd->read_buf[PIP2_RESP_REPORT_ID_OFFSET] & 0x7F; |
| if (cmd_id != pip2_cmd->cmd_id) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s cmd[0x%02X] command ID ERR: cmd_id = 0x%02X\n", |
| __func__, pip2_cmd->cmd_id, cmd_id); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| /* Verify the SEQ number matches from command to response */ |
| response_seq = pip2_cmd->read_buf[PIP2_RESP_SEQUENCE_OFFSET] & 0x0F; |
| if ((pip2_cmd->cmd_tag_seq & 0x0F) != response_seq) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s cmd[0x%02X] send_seq = 0x%02X, resp_seq = 0x%02X\n", |
| __func__, pip2_cmd->cmd_id, |
| pip2_cmd->cmd_tag_seq, response_seq); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| /* Verify the reserved bits are 0 */ |
| reserved_bits = pip2_cmd->read_buf[PIP2_RESP_SEQUENCE_OFFSET] & 0xF0; |
| if (reserved_bits) |
| pt_debug(cd->dev, DL_WARN, |
| "%s cmd[0x%02X] reserved_bits = 0x%02X\n", |
| __func__, pip2_cmd->cmd_id, reserved_bits); |
| |
| exit: |
| if (rc) |
| pt_pr_buf(cd->dev, DL_WARN, cd->input_buf, |
| pip2_cmd->actual_read_len, "PIP RSP:"); |
| return rc; |
| } |
| |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_validate_bl_response |
| * |
| * SUMMARY: Validate the response of bootloader command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_validate_bl_response( |
| struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| u16 size; |
| u16 crc; |
| u8 status; |
| |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| |
| if (pip1_cmd->reset_expected && !size) |
| return 0; |
| |
| if (cd->response_buf[PIP1_RESP_REPORT_ID_OFFSET] |
| != PT_PIP_BL_RESPONSE_REPORT_ID) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: BL output response, wrong report_id\n", __func__); |
| return -EPROTO; |
| } |
| |
| if (cd->response_buf[4] != PIP1_BL_SOP) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: BL output response, wrong SOP\n", __func__); |
| return -EPROTO; |
| } |
| |
| if (cd->response_buf[size - 1] != PIP1_BL_EOP) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: BL output response, wrong EOP\n", __func__); |
| return -EPROTO; |
| } |
| |
| crc = _pt_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)) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: BL output response, wrong CRC 0x%X\n", |
| __func__, crc); |
| return -EPROTO; |
| } |
| |
| status = cd->response_buf[5]; |
| if (status) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: BL output response, ERROR:%d\n", |
| __func__, status); |
| return -EPROTO; |
| } |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_validate_app_response |
| * |
| * SUMMARY: Validate the response of application command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_validate_app_response( |
| struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| int command_code; |
| u16 size; |
| |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| |
| /* |
| * This case is for reset sentinel, it could be one of those: |
| * APP sentinel: size is 0; |
| * BL sentinel of Gen5/6: size is 0; |
| * BL sentinel of TC3XXX/TT7XXX/DP818: tested by macro IS_BL_SENTINEL(). |
| */ |
| if (pip1_cmd->reset_expected && (!size || IS_BL_SENTINEL(cd, size))) |
| return 0; |
| |
| if (cd->response_buf[PIP1_RESP_REPORT_ID_OFFSET] |
| != PT_PIP_NON_HID_RESPONSE_ID) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: APP output response, wrong report_id\n", __func__); |
| return -EPROTO; |
| } |
| |
| command_code = cd->response_buf[PIP1_RESP_COMMAND_ID_OFFSET] |
| & PIP1_RESP_COMMAND_ID_MASK; |
| if (command_code != pip1_cmd->command_code) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: APP output response, wrong command_code:%X\n", |
| __func__, command_code); |
| return -EPROTO; |
| } |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_check_set_parameter |
| * |
| * SUMMARY: Takes 'struct pt_pip1_cmd *' as an input parameter to check command |
| * input and response for Set Parameter command.And store the parameter to the |
| * list for resume work if pass the check. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static void pt_pip1_check_set_parameter(struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| u8 *param_buf; |
| u32 param_value = 0; |
| u8 param_size; |
| u8 param_id; |
| u8 read_size; |
| u8 read_param_id; |
| int i = 0; |
| |
| if (!(cd->cpdata->flags & PT_CORE_FLAG_RESTORE_PARAMETERS)) |
| return; |
| |
| /* Check command input for Set Parameter command */ |
| if (pip1_cmd->cmd_type == PIP1_CMD_TYPE_FW |
| && pip1_cmd->command_code == PIP1_CMD_ID_SET_PARAM |
| && pip1_cmd->write_length >= 3 |
| && pip1_cmd->write_length <= 6) |
| param_buf = &pip1_cmd->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) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid parameter size\n", __func__); |
| return; |
| } |
| |
| param_buf = ¶m_buf[2]; |
| while (i < param_size) |
| param_value += *(param_buf++) << (8 * i++); |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| read_param_id = cd->response_buf[6]; |
| read_size = cd->response_buf[7]; |
| } else { |
| read_param_id = cd->response_buf[5]; |
| read_size = cd->response_buf[6]; |
| } |
| |
| /* Check command response for Set Parameter command */ |
| if (cd->response_buf[2] != PT_PIP_NON_HID_RESPONSE_ID |
| || (cd->response_buf[4] & |
| PIP1_RESP_COMMAND_ID_MASK) != |
| PIP1_CMD_ID_SET_PARAM |
| || read_param_id != param_id |
| || read_size != param_size) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Set Parameter command not successful\n", |
| __func__); |
| return; |
| } |
| |
| pt_add_parameter(cd, param_id, param_value, param_size); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_raw_check_set_parameter |
| * |
| * SUMMARY: Takes 'struct pt_raw_cmd *' as an input parameter to check command |
| * input and response for Set Parameter command.And store the parameter to the |
| * list for resume work if pass the check. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *raw_cmd - pointer to raw command data structure |
| ******************************************************************************/ |
| static void pt_raw_check_set_parameter(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| u8 *param_buf; |
| u32 param_value = 0; |
| u8 param_size; |
| u8 param_id; |
| u8 read_param_id; |
| u8 read_size; |
| int i = 0; |
| |
| if (!(cd->cpdata->flags & PT_CORE_FLAG_RESTORE_PARAMETERS)) |
| return; |
| |
| /* Check command input for Set Parameter command */ |
| if (raw_cmd->write_length >= 10 && raw_cmd->write_length <= 13 |
| && !memcmp(&raw_cmd->write_buf[0], |
| &cd->hid_desc.output_register, |
| sizeof(cd->hid_desc.output_register)) |
| && raw_cmd->write_buf[4] == |
| PT_PIP_NON_HID_COMMAND_ID |
| && raw_cmd->write_buf[6] == |
| PIP1_CMD_ID_SET_PARAM) |
| param_buf = &raw_cmd->write_buf[7]; |
| else |
| return; |
| |
| /* Get parameter ID, size and value */ |
| param_id = param_buf[0]; |
| param_size = param_buf[1]; |
| if (param_size > 4) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid parameter size\n", __func__); |
| return; |
| } |
| |
| param_buf = ¶m_buf[2]; |
| while (i < param_size) |
| param_value += *(param_buf++) << (8 * i++); |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| read_param_id = cd->response_buf[6]; |
| read_size = cd->response_buf[7]; |
| } else { |
| read_param_id = cd->response_buf[5]; |
| read_size = cd->response_buf[6]; |
| } |
| |
| /* Check command response for Set Parameter command */ |
| if (cd->response_buf[2] != PT_PIP_NON_HID_RESPONSE_ID |
| || (cd->response_buf[4] & |
| PIP1_RESP_COMMAND_ID_MASK) != |
| PIP1_CMD_ID_SET_PARAM |
| || read_param_id != param_id |
| || read_size != param_size) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Set Parameter command not successful\n", |
| __func__); |
| return; |
| } |
| |
| pt_add_parameter(cd, param_id, param_value, param_size); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_validate_response |
| * |
| * SUMMARY: Validate PIP1 response of application or bootloader command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_validate_response(struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| if (pip1_cmd->cmd_type == PIP1_CMD_TYPE_BL) |
| return pt_pip1_validate_bl_response(cd, pip1_cmd); |
| |
| return pt_pip1_validate_app_response(cd, pip1_cmd); |
| |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_write_raw_direct_ |
| * |
| * SUMMARY: Blindly send raw data to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *raw_cmd - pointer to raw command data structure |
| ******************************************************************************/ |
| static int pt_write_raw_direct_(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| int rc = 0; |
| int cmd; |
| |
| if (!raw_cmd->write_length || !raw_cmd->write_buf) |
| return -EINVAL; |
| |
| if (cd->pip2_prot_active) { |
| cmd = raw_cmd->write_buf[PIP2_CMD_COMMAND_ID_OFFSET]; |
| cmd &= PIP2_CMD_COMMAND_ID_MASK; |
| } else |
| cmd = raw_cmd->write_buf[PIP1_CMD_COMMAND_ID_OFFSET]; |
| |
| pt_debug(cd->dev, DL_INFO, |
| ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", |
| __func__, raw_cmd->write_length, cmd); |
| pt_pr_buf(cd->dev, DL_DEBUG, raw_cmd->write_buf, |
| raw_cmd->write_length, ">>> User CMD"); |
| |
| rc = pt_adap_write_read_specific(cd, raw_cmd->write_length, |
| raw_cmd->write_buf, NULL, 0); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Fail pt_adap_transfer\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_write_raw_direct |
| * |
| * SUMMARY: Wrapper function for pt_write_raw_direct_ that guarantees |
| * exclusive access. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *raw_cmd - pointer to raw command data structure |
| ******************************************************************************/ |
| static int pt_write_raw_direct(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_write_raw_direct_(cd, raw_cmd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| #ifdef FUTURE |
| static int pt_raw_send_as_pip_(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| return 0; |
| } |
| |
| static int pt_raw_send_as_hid_(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| return 0; |
| } |
| |
| static int pt_raw_send_(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| return 0; |
| } |
| #endif |
| /******************************************************************************* |
| * FUNCTION: pt_raw_send_and_wait_ |
| * |
| * SUMMARY: Blindly send raw data to the DUT and wait for the response. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *raw_cmd - pointer to raw command data structure |
| ******************************************************************************/ |
| static int pt_raw_send_and_wait_(struct pt_core_data *cd, |
| struct pt_raw_cmd *raw_cmd) |
| { |
| int rc = 0; |
| int t; |
| u16 size = 0; |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = PIP1_CMD_ID_USER_CMD + 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = pt_write_raw_direct_(cd, raw_cmd); |
| if (rc) |
| goto error; |
| |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(cd->pip_cmd_timeout)); |
| if (IS_TMO(t)) { |
| #ifdef TTDL_DIAGNOSTICS |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| #endif /* TTDL_DIAGNOSTICS */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: HID output cmd execution timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| /* Get the response size from the first 2 bytes in the response */ |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| |
| /* Ensure size is not greater than max buffer size */ |
| if (size > PT_MAX_PIP2_MSG_SIZE) |
| size = PT_MAX_PIP2_MSG_SIZE; |
| |
| /* Minimum size to read is the 2 byte len field */ |
| if (size == 0) |
| size = 2; |
| |
| if (size > raw_cmd->read_len) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP2 len field=%d, requested read_len=%d\n", |
| __func__, size, raw_cmd->read_len); |
| raw_cmd->actual_read_len = 0; |
| rc = -EIO; |
| goto error; |
| } |
| |
| if (raw_cmd->read_buf) { |
| raw_cmd->actual_read_len = size; |
| memcpy(raw_cmd->read_buf, cd->response_buf, size); |
| } |
| |
| pt_raw_check_set_parameter(cd, raw_cmd); |
| |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_check_irq_asserted |
| * |
| * SUMMARY: Checks if the IRQ GPIO is asserted or not. There are times when |
| * the FW can hold the INT line low ~150us after the read is complete. |
| * NOTE: if irq_stat is not defined this function will return false |
| * |
| * RETURN: |
| * true = IRQ asserted |
| * false = IRQ not asserted |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static bool pt_check_irq_asserted(struct pt_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 == PIP1_BL_CMD_ID_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); |
| } |
| #endif |
| if (cd->cpdata->irq_stat) { |
| if (cd->cpdata->irq_stat(cd->cpdata, cd->dev) |
| == PT_IRQ_ASSERTED_VALUE) { |
| /* Debounce to allow FW to release INT */ |
| usleep_range(100, 200); |
| } |
| if (cd->cpdata->irq_stat(cd->cpdata, cd->dev) |
| == PT_IRQ_ASSERTED_VALUE) |
| return true; |
| else |
| return false; |
| } |
| return true; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_flush_bus |
| * |
| * SUMMARY: Force flushing the bus by reading len bytes or forced 255 bytes |
| * Used if IRQ is found to be stuck low |
| * |
| * RETURN: Length of bytes read from bus |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * flush_type - type of flush |
| * - PT_FLUSH_BUS_BASED_ON_LEN (two reads) |
| * - PT_FLUSH_BUS_FULL_256_READ |
| * *read_buf - pointer to store read data |
| ******************************************************************************/ |
| static ssize_t pt_flush_bus(struct pt_core_data *cd, |
| u8 flush_type, u8 *read_buf) |
| { |
| u8 buf[PT_MAX_PIP2_MSG_SIZE]; |
| u16 pip_len; |
| int bytes_read; |
| int rc = 0; |
| |
| if (flush_type == PT_FLUSH_BUS_BASED_ON_LEN) { |
| #ifdef TTDL_PTVIRTDUT_SUPPORT |
| if (cd->route_bus_virt_dut) |
| rc = pt_adap_read_default(cd, buf, |
| PT_MAX_PIP2_MSG_SIZE); |
| else |
| rc = pt_adap_read_default(cd, buf, 2); |
| #else |
| rc = pt_adap_read_default(cd, buf, 2); |
| #endif /* TTDL_PTVIRTDUT_SUPPORT */ |
| if (rc) { |
| bytes_read = 0; |
| goto exit; |
| } |
| |
| pip_len = get_unaligned_le16(&buf[0]); |
| |
| if (pip_len == 2 || pip_len >= PT_PIP_1P7_EMPTY_BUF) { |
| #ifdef TTDL_DIAGNOSTICS |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_EMPTY_PACKET); |
| #endif |
| bytes_read = 2; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Empty buf detected - len=0x%04X\n", |
| __func__, pip_len); |
| } else if (pip_len == 0) { |
| bytes_read = 0; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Sentinel detected\n", __func__); |
| } else if (pip_len > PT_MAX_PIP2_MSG_SIZE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Illegal len=0x%04x, force %d byte read\n", |
| __func__, pip_len, PT_MAX_PIP2_MSG_SIZE); |
| rc = pt_adap_read_default(cd, buf, |
| PT_MAX_PIP2_MSG_SIZE); |
| if (!rc) |
| bytes_read = PT_MAX_PIP2_MSG_SIZE; |
| else |
| bytes_read = 0; |
| } else { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Flush read of %d bytes...\n", |
| __func__, pip_len); |
| |
| #ifdef TTDL_PTVIRTDUT_SUPPORT |
| rc = 0; |
| if (cd->route_bus_virt_dut) |
| bytes_read = pip_len; |
| else |
| rc = pt_adap_read_default(cd, buf, pip_len); |
| #else |
| rc = pt_adap_read_default(cd, buf, pip_len); |
| #endif /* TTDL_PTVIRTDUT_SUPPORT */ |
| if (!rc) |
| bytes_read = pip_len; |
| else |
| bytes_read = 0; |
| } |
| } else { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Forced flush of max %d bytes...\n", |
| __func__, PT_MAX_PIP2_MSG_SIZE); |
| rc = pt_adap_read_default(cd, buf, PT_MAX_PIP2_MSG_SIZE); |
| if (!rc) |
| bytes_read = PT_MAX_PIP2_MSG_SIZE; |
| else |
| bytes_read = 0; |
| } |
| |
| if (bytes_read > 0) |
| pt_pr_buf(cd->dev, DL_WARN, buf, bytes_read, "WD-flush_bus"); |
| |
| if (read_buf && (bytes_read > 3)) |
| memcpy(read_buf, buf, bytes_read); |
| |
| exit: |
| return bytes_read; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_flush_bus_if_irq_asserted |
| * |
| * SUMMARY: This function will flush the active bus if the INT is found to be |
| * asserted. |
| * |
| * RETURN: bytes cleared from bus |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * flush_type - type of flush |
| * - PT_FLUSH_BUS_BASED_ON_LEN |
| * - PT_FLUSH_BUS_FULL_256_READ |
| ******************************************************************************/ |
| static int pt_flush_bus_if_irq_asserted(struct pt_core_data *cd, u8 flush_type) |
| { |
| int count = 0; |
| int bytes_read = 0; |
| |
| while (pt_check_irq_asserted(cd) && count < 5) { |
| count++; |
| bytes_read = pt_flush_bus(cd, flush_type, NULL); |
| if (bytes_read) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Cleared %d bytes off bus\n", |
| __func__, bytes_read); |
| } |
| } |
| if (pt_check_irq_asserted(cd)) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: IRQ still asserted, %d bytes read\n", |
| __func__, bytes_read); |
| } else { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: IRQ cleared, %d bytes read\n", |
| __func__, bytes_read); |
| } |
| return bytes_read; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_write_to_hid_output_reg_ |
| * |
| * SUMMARY: Low level HID function to send touch command to the DUT's output |
| * register under HID protocol. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hid_output - pointer to the command to write to output register |
| ******************************************************************************/ |
| static int pt_write_to_hid_output_reg_(struct pt_core_data *cd, |
| struct pt_hid_output *hid_output) |
| { |
| int rc = 0; |
| u8 *cmd; |
| u16 length; |
| u16 crc; |
| u8 report_id; |
| u8 cmd_offset = 0; |
| u8 cmd_allocated = 0; |
| |
| #ifdef FUTURE |
| /* |
| * *** TODO - Determine side effects of adding this safty net *** |
| * If IRQ is already asserted due to a pending report, it must be |
| * cleared before sending command. |
| */ |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| #endif |
| |
| switch (hid_output->cmd_type) { |
| case PIP1_CMD_TYPE_FW: |
| report_id = PT_PIP_NON_HID_COMMAND_ID; |
| length = 5; |
| break; |
| case PIP1_CMD_TYPE_BL: |
| report_id = PT_PIP_BL_COMMAND_REPORT_ID; |
| length = 11 /* 5 + SOP + LEN(2) + CRC(2) + EOP */; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| length += hid_output->write_length; |
| |
| if (length + 2 > PT_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 == PIP1_CMD_TYPE_BL) |
| cmd[cmd_offset++] = PIP1_BL_SOP; |
| cmd[cmd_offset++] = hid_output->command_code; |
| |
| /* Set Data Length for bootloader */ |
| if (hid_output->cmd_type == PIP1_CMD_TYPE_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 == PIP1_CMD_TYPE_BL) { |
| crc = _pt_compute_crc(&cmd[6], |
| hid_output->write_length + 4); |
| cmd[cmd_offset++] = LOW_BYTE(crc); |
| cmd[cmd_offset++] = HI_BYTE(crc); |
| cmd[cmd_offset++] = PIP1_BL_EOP; |
| } |
| |
| pt_debug(cd->dev, DL_DEBUG, ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", __func__, |
| length + 2, hid_output->command_code); |
| pt_pr_buf(cd->dev, DL_DEBUG, cmd, length + 2, ">>> CMD"); |
| rc = pt_adap_write_read_specific(cd, length + 2, cmd, NULL, 0); |
| |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Fail pt_adap_transfer rc=%d\n", __func__, rc); |
| |
| if (cmd_allocated) |
| kfree(cmd); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_send_as_pip_ |
| * |
| * SUMMARY: Wrapper function to send PIP1 command to HID output register |
| * register with function pt_write_to_hid_output_reg_(). |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_send_as_pip_(struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| struct pt_hid_output hid_output = { |
| INIT_HID_OUTPUT_FROM_PIP_CMD(pip1_cmd), |
| }; |
| |
| return pt_write_to_hid_output_reg_(cd, &hid_output); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_send_as_hid_ |
| * |
| * SUMMARY: Function to pack and send PIP1 command to DUT's HID command |
| * register with HID SET_REPORT command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_send_as_hid_(struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| struct pt_hid_cmd hid_cmd = { |
| INIT_HID_SET_REPORT_HEADER, |
| }; |
| int rc = 0; |
| u16 index = 0; |
| u16 payload_len = 0; |
| unsigned short crc; |
| |
| /* |
| * 'write_length' is calculated by: |
| * Length field(2) + |
| * REPORT_ID(1) + |
| * Payload (hid_max_output_len) |
| */ |
| hid_cmd.write_length = 3 + cd->hid_core.hid_max_output_len; |
| if (hid_cmd.write_length < (9 + pip1_cmd->write_length)) |
| return -EINVAL; |
| hid_cmd.write_buf = kzalloc(hid_cmd.write_length, GFP_KERNEL); |
| if (!hid_cmd.write_buf) |
| return -ENOMEM; |
| |
| /* Set Length Field(2) */ |
| hid_cmd.write_buf[index++] = LOW_BYTE(hid_cmd.write_length); |
| hid_cmd.write_buf[index++] = HI_BYTE(hid_cmd.write_length); |
| |
| /* Set HID output Report ID(1) */ |
| hid_cmd.write_buf[index++] = PT_HID_OUTPUT_REPORT_ID; |
| |
| /* |
| * 'payload_len' is calculated by: |
| * Length of payload(2) + |
| * TAG/SEQ(1) + |
| * command ID(1) + |
| * Parameter Data(pip1_cmd->write_length) + |
| * CRC(2) |
| */ |
| payload_len = 6 + pip1_cmd->write_length; |
| hid_cmd.write_buf[index++] = LOW_BYTE(payload_len); |
| hid_cmd.write_buf[index++] = HI_BYTE(payload_len); |
| |
| /* Request and set SEQ(1) */ |
| hid_cmd.write_buf[index++] = pt_pip2_get_next_cmd_seq(cd); |
| |
| /* Set command code(1) */ |
| hid_cmd.write_buf[index++] = pt_get_hid_cmd_id_from_pip( |
| pip1_cmd->command_code, PIP1_TO_HID_CMD_ID_MAP); |
| |
| /* Copy PIP1 Parameter Data(N) */ |
| memcpy(&hid_cmd.write_buf[index], pip1_cmd->write_buf, |
| pip1_cmd->write_length); |
| index += pip1_cmd->write_length; |
| |
| /* |
| * Calculate CRC(2): from 'Length of payload' to 'PIP1 Parameter Data' |
| */ |
| crc = crc_ccitt_calculate(&hid_cmd.write_buf[3], index - 3); |
| hid_cmd.write_buf[index++] = (crc & 0xff00) >> 8; |
| hid_cmd.write_buf[index++] = (crc & 0xff); |
| |
| rc = pt_write_to_hid_cmd_reg_(cd, &hid_cmd); |
| kfree(hid_cmd.write_buf); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_send_ |
| * |
| * SUMMARY: Wrapper function to send PIP1 command to HID output register or |
| * HID command register (with HID SET_REPORT command) by function |
| * pt_pip1_send_as_pip_() and pt_pip1_send_as_hid_(). |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_send_(struct pt_core_data *cd, struct pt_pip1_cmd *pip1_cmd) |
| { |
| cd->report_type = PIP1_CMD_REPORT; |
| if ((cd->protocol_mode == PT_PROTOCOL_MODE_HID) && |
| (cd->mode != PT_MODE_BOOTLOADER)) |
| return pt_pip1_send_as_hid_(cd, pip1_cmd); |
| else |
| return pt_pip1_send_as_pip_(cd, pip1_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_send_host_mode_cmd_ |
| * |
| * SUMMARY: Sending the special "Host Mode" command will instruct the BL |
| * to not execute the FW it has loaded into RAM. The command must be sent |
| * within a 40ms window after chip reset by XRES pin or command. If the |
| * messages is sent too early it will NAK, so keep sending it every 2ms |
| * until it is accepted by the BL. A no-flash DUT does not require this |
| * command as there is no FW for the BL to load and execute. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static inline int pt_send_host_mode_cmd_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| u8 host_mode_cmd[4] = {0xA5, 0xA5, 0xA5, 0xA5}; |
| u8 time = 0; |
| |
| /* Wait BL to be ready to accept host command */ |
| usleep_range(4000, 6000); |
| pt_debug(cd->dev, DL_INFO, |
| ">>> %s: Write Buffer Size[%d] Stay in BL\n", |
| __func__, (int)sizeof(host_mode_cmd)); |
| pt_pr_buf(cd->dev, DL_DEBUG, host_mode_cmd, |
| (int)sizeof(host_mode_cmd), ">>> User CMD"); |
| rc = 1; |
| while (rc && time < 34) { |
| rc = pt_adap_write_read_specific(cd, 4, |
| host_mode_cmd, NULL, 0); |
| usleep_range(1800, 2000); |
| time += 2; |
| } |
| |
| /* Sleep to allow the BL to come up */ |
| usleep_range(1000, 2000); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_send_and_wait_ |
| * |
| * SUMMARY: Send valid PIP1 command to the DUT and wait for the response. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP1 command data structure |
| ******************************************************************************/ |
| static int pt_pip1_send_and_wait_(struct pt_core_data *cd, |
| struct pt_pip1_cmd *pip1_cmd) |
| { |
| int rc = 0; |
| int t; |
| u16 timeout_ms; |
| u16 max_timeout_ms = PT_HID_MULTIPACKET_TIMEOUT; |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = pip1_cmd->command_code + 1; |
| mutex_unlock(&cd->system_lock); |
| |
| if (pip1_cmd->timeout_ms) |
| timeout_ms = pip1_cmd->timeout_ms; |
| else |
| timeout_ms = cd->pip_cmd_timeout_default; |
| |
| rc = pt_pip1_send_(cd, pip1_cmd); |
| if (rc) |
| goto error; |
| |
| /* |
| * Enter bootloader by StartBootloader command for TC3XXX/TT7XXX/DP818 |
| * 1) Send Startbootloader command |
| * 2) Send host_cmd within 40 ms |
| * 3) Receive BL sentinel |
| */ |
| if (pip1_cmd->command_code == PIP1_CMD_ID_START_BOOTLOADER && |
| cd->active_dut_generation != DUT_PIP1_ONLY) { |
| pt_send_host_mode_cmd_(cd); |
| } |
| |
| if (pip1_cmd->command_code == PIP1_BL_CMD_ID_LAUNCH_APP && |
| cd->dual_mcu_available) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Dual MCU is enabled, no sentinel after cmd 0x3B\n", |
| __func__); |
| /* |
| * Typically a Gen6 needs about 100ms to generate a sentinel on |
| * boot, however for a dual MCU solution no sentinel is produced |
| * but the wait time is still needed to allow the Gen6 to fully |
| * boot. The sleep time is doubled as a safe guard. |
| */ |
| msleep(200); |
| cd->hid_reset_cmd_state = 0; |
| goto exit; |
| } |
| |
| again: |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(timeout_ms)); |
| if (IS_TMO(t)) { |
| if ((cd->protocol_mode == PT_PROTOCOL_MODE_HID) && |
| (cd->pt_hid_buf_op.last_remain_packet != 0) && |
| (max_timeout_ms > timeout_ms)) { |
| max_timeout_ms -= timeout_ms; |
| goto again; |
| } |
| #ifdef TTDL_DIAGNOSTICS |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| #endif /* TTDL_DIAGNOSTICS */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: HID output cmd execution timed out (%dms)\n", |
| __func__, timeout_ms); |
| rc = -ETIME; |
| goto error; |
| } |
| |
| if (!pip1_cmd->novalidate && |
| cd->protocol_mode != PT_PROTOCOL_MODE_HID) |
| rc = pt_pip1_validate_response(cd, pip1_cmd); |
| |
| pt_pip1_check_set_parameter(cd, pip1_cmd); |
| goto exit; |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_write_to_pip2_cmd_reg_ |
| * |
| * SUMMARY: Low level function to send PIP2 command to PIP2 command register. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip2_cmd - pointer to PIP2 command data structure |
| ******************************************************************************/ |
| static int pt_write_to_pip2_cmd_reg_(struct pt_core_data *cd, |
| struct pt_pip2_cmd *pip2_cmd) |
| { |
| int rc = 0; |
| int index = 0; |
| u16 crc = 0; |
| u16 cmd_len = 0; |
| u16 write_len = 0; |
| u8 *write_buf = NULL; |
| |
| /* The PIP2.1+ the length field includes: |
| * 2 byte length |
| * 1 byte sequence |
| * 1 byte command ID |
| * N byte report body |
| * 2 byte CRC |
| */ |
| cmd_len = pip2_cmd->report_len + 6; |
| |
| /* The overall write length include register(2 bytes) */ |
| write_len = 2 + cmd_len; |
| write_buf = kzalloc(write_len, GFP_KERNEL); |
| if (write_buf == NULL) { |
| rc = -ENOMEM; |
| return rc; |
| } |
| |
| /* PIP2 command register */ |
| write_buf[index++] = LOW_BYTE(PT_PIP2_CMD_REGISTER); |
| write_buf[index++] = HI_BYTE(PT_PIP2_CMD_REGISTER); |
| /* Length field (LSB) */ |
| write_buf[index++] = LOW_BYTE(cmd_len); |
| write_buf[index++] = HI_BYTE(cmd_len); |
| /* Tag and Seq, request it at first */ |
| pip2_cmd->cmd_tag_seq = pt_pip2_get_next_cmd_seq(cd); |
| write_buf[index++] = pip2_cmd->cmd_tag_seq; |
| /* Command ID */ |
| write_buf[index++] = pip2_cmd->cmd_id; |
| /* Report Body */ |
| memcpy(&write_buf[index], pip2_cmd->report_data, pip2_cmd->report_len); |
| index += pip2_cmd->report_len; |
| /* |
| * The CRC calculation content includes: |
| * 2 (LEN) + 1 (SEQ) + 1 (REPORT ID) + N (report) |
| */ |
| crc = crc_ccitt_calculate(&write_buf[2], cmd_len - 2); |
| /* CRC (MSB) */ |
| write_buf[index++] = HI_BYTE(crc); |
| write_buf[index++] = LOW_BYTE(crc); |
| |
| pt_debug(cd->dev, DL_INFO, |
| ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", |
| __func__, write_len, pip2_cmd->cmd_id); |
| pt_pr_buf(cd->dev, DL_DEBUG, write_buf, write_len, ">>> CMD"); |
| rc = pt_adap_write_read_specific(cd, write_len, write_buf, NULL, 0); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Fail pt_adap_transfer\n", __func__); |
| |
| kfree(write_buf); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_send_as_pip_ |
| * |
| * SUMMARY: Wrapper function to send PIP2 command to DUT's PIP2 command |
| * register with function pt_write_to_pip2_cmd_reg_(). |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip2_cmd - pointer to PIP2 command data structure |
| ******************************************************************************/ |
| static int pt_pip2_send_as_pip_(struct pt_core_data *cd, |
| struct pt_pip2_cmd *pip2_cmd) |
| { |
| return pt_write_to_pip2_cmd_reg_(cd, pip2_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_send_as_hid_ |
| * |
| * SUMMARY: Function to pack and send PIP2 command to DUT's HID command |
| * register with HID SET_REPORT command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip2_cmd - pointer to PIP2 command data structure |
| ******************************************************************************/ |
| static int pt_pip2_send_as_hid_(struct pt_core_data *cd, |
| struct pt_pip2_cmd *pip2_cmd) |
| { |
| struct pt_hid_cmd hid_cmd = { |
| INIT_HID_SET_REPORT_HEADER, |
| }; |
| int rc = 0; |
| u16 index = 0; |
| u16 payload_len = 0; |
| unsigned short crc; |
| |
| /* |
| * 'write_length' is calculated by: |
| * Length field(2) + |
| * REPORT_ID(1) + |
| * Payload (hid_max_output_len) |
| */ |
| hid_cmd.write_length = 3 + cd->hid_core.hid_max_output_len; |
| if (hid_cmd.write_length < (pip2_cmd->report_len + 9)) |
| return -EINVAL; |
| hid_cmd.write_buf = kzalloc(hid_cmd.write_length, GFP_KERNEL); |
| if (!hid_cmd.write_buf) |
| return -ENOMEM; |
| |
| /* Set Length Field(2) */ |
| hid_cmd.write_buf[index++] = LOW_BYTE(hid_cmd.write_length); |
| hid_cmd.write_buf[index++] = HI_BYTE(hid_cmd.write_length); |
| |
| /* Set HID output Report ID(1) */ |
| hid_cmd.write_buf[index++] = PT_HID_OUTPUT_REPORT_ID; |
| |
| /* |
| * 'payload_len' is calculated by: |
| * Length of payload(2) + |
| * TAG/SEQ(1) + |
| * command ID(1) + |
| * report body(pip2_cmd->report_len) + |
| * CRC(2) |
| */ |
| payload_len = 6 + pip2_cmd->report_len; |
| hid_cmd.write_buf[index++] = LOW_BYTE(payload_len); |
| hid_cmd.write_buf[index++] = HI_BYTE(payload_len); |
| |
| /* Request and set SEQ(1) */ |
| pip2_cmd->cmd_tag_seq = pt_pip2_get_next_cmd_seq(cd); |
| hid_cmd.write_buf[index++] = pip2_cmd->cmd_tag_seq; |
| |
| /* Set command ID(1) */ |
| hid_cmd.write_buf[index++] = pt_get_hid_cmd_id_from_pip( |
| pip2_cmd->cmd_id, PIP2_TO_HID_CMD_ID_MAP); |
| |
| /* Copy PIP2 report body(N) */ |
| memcpy(&hid_cmd.write_buf[index], pip2_cmd->report_data, |
| pip2_cmd->report_len); |
| index += pip2_cmd->report_len; |
| |
| /* |
| * Calcualte CRC(2): from 'Length of payload' to 'PIP2 report body' |
| */ |
| crc = crc_ccitt_calculate(&hid_cmd.write_buf[3], index - 3); |
| hid_cmd.write_buf[index++] = (crc & 0xff00) >> 8; |
| hid_cmd.write_buf[index++] = (crc & 0xff); |
| |
| rc = pt_write_to_hid_cmd_reg_(cd, &hid_cmd); |
| kfree(hid_cmd.write_buf); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_send_ |
| * |
| * SUMMARY: Wrapper function to send PIP2 command to PIP2 command register or |
| * HID command register (with HID SET_REPORT command) by function |
| * pt_pip2_send_as_pip_() and pt_pip2_send_as_hid_(). |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip1_cmd - pointer to PIP2 command data structure |
| ******************************************************************************/ |
| static int pt_pip2_send_(struct pt_core_data *cd, struct pt_pip2_cmd *pip2_cmd) |
| { |
| cd->report_type = PIP2_CMD_REPORT; |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| (cd->mode != PT_MODE_BOOTLOADER)) |
| return pt_pip2_send_as_hid_(cd, pip2_cmd); |
| else |
| return pt_pip2_send_as_pip_(cd, pip2_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_send_and_wait_ |
| * |
| * SUMMARY: Send valid PIP2 command to the DUT and wait for the response. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *pip2_cmd - pointer to PIP2 command data structure |
| ******************************************************************************/ |
| static int pt_pip2_send_and_wait_(struct pt_core_data *cd, |
| struct pt_pip2_cmd *pip2_cmd) |
| { |
| int rc = 0; |
| int t; |
| u16 size; |
| u16 read_len; |
| u16 timeout_ms; |
| u16 max_timeout_ms = PT_HID_MULTIPACKET_TIMEOUT; |
| |
| /* PIP2 the cmd ID is a 7bit field */ |
| if (pip2_cmd->cmd_id > PIP2_CMD_ID_END) { |
| pt_debug(cd->dev, DL_WARN, "%s: Invalid PIP2 CMD ID 0x%02X\n", |
| __func__, pip2_cmd->cmd_id); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| read_len = pt_pip2_get_cmd_response_len(pip2_cmd->cmd_id); |
| if (read_len < 0) |
| read_len = 255; |
| pt_debug(cd->dev, DL_INFO, |
| "%s cmd_id[0x%02X] expected response length:%d\n", |
| __func__, pip2_cmd->cmd_id, read_len); |
| |
| mutex_lock(&cd->system_lock); |
| cd->pip2_prot_active = true; |
| cd->hid_cmd_state = pip2_cmd->cmd_id + 1; |
| mutex_unlock(&cd->system_lock); |
| |
| if (pip2_cmd->timeout_ms) |
| timeout_ms = pip2_cmd->timeout_ms; |
| else |
| timeout_ms = cd->pip_cmd_timeout_default; |
| |
| rc = pt_pip2_send_(cd, pip2_cmd); |
| if (rc) |
| goto error; |
| again: |
| t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), |
| msecs_to_jiffies(timeout_ms)); |
| if (IS_TMO(t)) { |
| if ((cd->protocol_mode == PT_PROTOCOL_MODE_HID) && |
| (cd->pt_hid_buf_op.last_remain_packet != 0) && |
| (max_timeout_ms > timeout_ms)) { |
| max_timeout_ms -= timeout_ms; |
| goto again; |
| } |
| #ifdef TTDL_DIAGNOSTICS |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| #endif /* TTDL_DIAGNOSTICS */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP2 cmd execution timed out (%dms)\n", |
| __func__, timeout_ms); |
| rc = -ETIME; |
| goto error; |
| } |
| /* Get the response size from the first 2 bytes in the response */ |
| size = get_unaligned_le16(&cd->response_buf[0]); |
| |
| /* Ensure size is not greater than max buffer size */ |
| if (size > PT_MAX_PIP2_MSG_SIZE) |
| size = PT_MAX_PIP2_MSG_SIZE; |
| |
| /* Minimum size to read is the 2 byte len field */ |
| if (size == 0) |
| size = 2; |
| |
| if (size > read_len) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP2 len field=%d, requested read_len=%d\n", |
| __func__, size, read_len); |
| pip2_cmd->actual_read_len = 0; |
| rc = -EIO; |
| goto error; |
| } |
| |
| if (pip2_cmd->read_buf) { |
| pip2_cmd->actual_read_len = size; |
| memcpy(pip2_cmd->read_buf, cd->response_buf, size); |
| } |
| |
| if (!pip2_cmd->novalidate) |
| rc = pt_pip2_validate_response(cd, pip2_cmd); |
| |
| error: |
| mutex_lock(&cd->system_lock); |
| cd->pip2_prot_active = false; |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_user_cmd_ |
| * |
| * SUMMARY: Load the write buffer into a HID structure and send it as a HID cmd |
| * to the DUT waiting for the response and loading it into the read buffer |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * read_len - expected read length of the response |
| * *read_buf - pointer to where the response will be loaded |
| * write_len - length of the write buffer |
| * *write_buf - pointer to the write buffer |
| * *actual_read_len - pointer to the actual amount of data read back |
| ******************************************************************************/ |
| static int pt_hid_output_user_cmd_(struct pt_core_data *cd, |
| u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, |
| u16 *actual_read_len) |
| { |
| int rc = 0; |
| struct pt_raw_cmd raw_cmd = { |
| .write_length = write_len, |
| .write_buf = write_buf, |
| .read_buf = read_buf, |
| .read_len = read_len |
| }; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| if (!cd->pip2_send_user_cmd) { |
| int command_code = 0; |
| int len; |
| |
| /* Print up to cmd ID */ |
| len = PIP1_CMD_COMMAND_ID_OFFSET + 1; |
| if (write_len < len) |
| len = write_len; |
| else |
| command_code = write_buf[PIP1_CMD_COMMAND_ID_OFFSET] |
| & PIP1_CMD_COMMAND_ID_MASK; |
| |
| /* Don't print EXEC_PANEL_SCAN & RETRIEVE_PANEL_SCAN commands */ |
| if (command_code != PIP1_CMD_ID_EXEC_PANEL_SCAN && |
| command_code != PIP1_CMD_ID_RETRIEVE_PANEL_SCAN) |
| tthe_print(cd, write_buf, len, "CMD="); |
| } |
| #endif |
| |
| rc = pt_raw_send_and_wait_(cd, &raw_cmd); |
| if (rc) |
| return rc; |
| |
| if (actual_read_len) |
| *actual_read_len = raw_cmd.actual_read_len; |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_user_cmd |
| * |
| * SUMMARY: Protected call to pt_hid_output_user_cmd_ by exclusive access to |
| * the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * read_len - length of data to read |
| * *read_buf - pointer to store read data |
| * write_len - length of data to write |
| * *write_buf - pointer to buffer to write |
| * *actual_read_len - pointer to store data length actually read |
| ******************************************************************************/ |
| static int pt_hid_output_user_cmd(struct pt_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, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_user_cmd_(cd, read_len, read_buf, |
| write_len, write_buf, actual_read_len); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip2_send_cmd |
| * |
| * SUMMARY: Writes a PIP2 command packet to DUT, then waits for the |
| * interrupt and reads response data to read_buf |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to run in protected mode |
| * id - ID of PIP command |
| * *data - pointer to PIP data payload |
| * report_body_len - report length |
| * *read_buf - pointer to response buffer |
| * *actual_read_len - pointer to response buffer length |
| ******************************************************************************/ |
| static int _pt_request_pip2_send_cmd(struct device *dev, |
| int protect, u8 id, u8 *data, u16 report_body_len, u8 *read_buf, |
| u16 *actual_read_len) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pip2_cmd pip2_cmd = { |
| .cmd_id = id, |
| .report_data = data, |
| .report_len = report_body_len, |
| .read_buf = read_buf, |
| }; |
| int rc; |
| |
| if (protect) { |
| rc = request_exclusive(cd, cd->dev, |
| PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| } |
| |
| rc = pt_pip2_send_and_wait_(cd, &pip2_cmd); |
| if (rc) |
| goto exit; |
| |
| if (actual_read_len) |
| *actual_read_len = pip2_cmd.actual_read_len; |
| |
| exit: |
| if (protect) { |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| } |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_send_cmd_no_int |
| * |
| * SUMMARY: Writes a PIP2 command packet to DUT, then poll the response and |
| * reads response data to read_buf if response is available. |
| * |
| * NOTE: |
| * Interrupt MUST be disabled before to call this function. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to run in protected mode |
| * id - ID of PIP command |
| * *data - pointer to PIP data payload |
| * report_body_len - report length |
| * *read_buf - pointer to response buffer |
| * *actual_read_len - pointer to response buffer length |
| ******************************************************************************/ |
| static int _pt_pip2_send_cmd_no_int(struct device *dev, |
| int protect, u8 id, u8 *data, u16 report_body_len, u8 *read_buf, |
| u16 *actual_read_len) |
| { |
| int max_retry = 0; |
| int retry = 0; |
| int rc = 0; |
| int read_len; |
| u16 size = 0; |
| u8 response_seq = 0; |
| u32 retry_interval = 0; |
| u32 retry_total_time = 0; |
| u32 resp_time_min = pt_pip2_get_cmd_resp_time_min(id); |
| u32 resp_time_max = pt_pip2_get_cmd_resp_time_max(id); |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pip2_cmd pip2_cmd = { |
| .cmd_id = id, |
| .report_data = data, |
| .report_len = report_body_len |
| }; |
| |
| if (protect == PT_CORE_CMD_PROTECTED) { |
| rc = request_exclusive(cd, |
| cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| } |
| |
| read_len = pt_pip2_get_cmd_response_len(pip2_cmd.cmd_id); |
| if (read_len < 0) |
| read_len = 255; |
| pt_debug(dev, DL_INFO, |
| "%s: ATM - cmd_id[0x%02X] expected response length:%d\n", |
| __func__, pip2_cmd.cmd_id, read_len); |
| |
| rc = pt_pip2_send_as_pip_(cd, &pip2_cmd); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, "%s: PIP2 command send err=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| #ifdef PT_POLL_RESP_BY_BUS |
| /* |
| * Frequent bus read can increase system load obviously. The expected |
| * first bus read should be valid and timely. The tollerance for |
| * usleep_range should be limited. The minimum response delay (between |
| * command finishes sending and INT pin falls) is less than 50 |
| * microseconds. So the 10 microseconds should be maximum tollerance |
| * with the consideration that the unit to calculate the response delay |
| * is 10 microseconds and more precise is not necessary. Every |
| * additional 10 microseconds only contribute less than 3 milliseconds |
| * for whole BL. |
| */ |
| usleep_range(resp_time_min, resp_time_min+10); |
| max_retry = resp_time_max / POLL_RETRY_DEFAULT_INTERVAL; |
| while ((retry < max_retry) && (retry_total_time < resp_time_max)) { |
| rc = pt_adap_read_default(cd, read_buf, read_len); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, "%s: SPI read Error = %d\n", |
| __func__, rc); |
| break; |
| } |
| response_seq = read_buf[PIP2_RESP_SEQUENCE_OFFSET]; |
| size = get_unaligned_le16(&read_buf[0]); |
| if ((size <= read_len) && |
| (size >= PIP2_EXTRA_BYTES_NUM) && |
| (pip2_cmd.seq & 0x07) == (response_seq & 0x07)) { |
| break; |
| } |
| |
| /* |
| * To reduce the bus and system load, increase the sleep |
| * step gradually: |
| * 1 ~ 19 : step=50 us, sleep_us=[50, 100, 150, 200, ..950] |
| * 20 ~ 39 : step=1000 us, sleep_us=[1950, 2950, ...20950] |
| * 40 ~ MAX: step=50 ms, sleep_ms=[71, 121, 191,..] |
| */ |
| retry++; |
| if (retry < 20) { |
| retry_interval += POLL_RETRY_DEFAULT_INTERVAL; |
| usleep_range(retry_interval, |
| retry_interval + POLL_RETRY_DEFAULT_INTERVAL); |
| } else if (retry < 40) { |
| retry_interval += 1000; |
| usleep_range(retry_interval, retry_interval + 1000); |
| } else { |
| retry_interval += 50000; |
| msleep(retry_interval/1000); |
| } |
| retry_total_time += retry_interval; |
| } |
| #else |
| /* |
| * Frequent GPIO read will not increase CPU/sytem load heavily if the |
| * interval is longer than 10 us, so it is safe to poll GPIO with a |
| * fixed interval: 20 us. |
| */ |
| usleep_range(resp_time_min, resp_time_min+10); |
| max_retry = resp_time_max / POLL_RETRY_DEFAULT_INTERVAL; |
| while ((retry < max_retry) && (retry_total_time < resp_time_max)) { |
| if (!gpio_get_value(cd->cpdata->irq_gpio)) { |
| rc = pt_adap_read_default(cd, read_buf, read_len); |
| response_seq = read_buf[PIP2_RESP_SEQUENCE_OFFSET]; |
| size = get_unaligned_le16(&read_buf[0]); |
| if (rc) |
| pt_debug(dev, DL_ERROR, |
| "%s: SPI read Error = %d\n", |
| __func__, rc); |
| else if (size > read_len) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP2 len field=%d, requested read_len=%d\n", |
| __func__, size, read_len); |
| rc = -EIO; |
| } |
| break; |
| } |
| /* |
| * Poll GPIO with fixed interval 20 us, and tollerance is |
| * limited to 10 us to speed up the process. |
| */ |
| retry_interval = POLL_RETRY_DEFAULT_INTERVAL; |
| usleep_range(retry_interval, retry_interval+10); |
| retry_total_time += retry_interval; |
| } |
| #endif |
| |
| *actual_read_len = size; |
| |
| if (rc || (retry >= max_retry) || (retry_total_time >= resp_time_max)) { |
| pt_debug(dev, DL_ERROR, |
| "%s cmd[0x%02X] timed out, send_seq=0x%02X, resp_seq=0x%02X\n", |
| __func__, pip2_cmd.cmd_id, pip2_cmd.cmd_tag_seq, |
| response_seq); |
| *actual_read_len = 0; |
| rc = -EINVAL; |
| } |
| |
| pt_pr_buf(cd->dev, DL_DEBUG, read_buf, *actual_read_len, |
| "<<< NO_INT Read"); |
| |
| exit: |
| if (protect == PT_CORE_CMD_PROTECTED) { |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| } |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_null_ |
| * |
| * SUMMARY: Send the PIP "ping"(0x00) command to the DUT and wait for response. |
| * This function is used by watchdog to check if the fw corrupts. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_pip_null_(struct pt_core_data *cd) |
| { |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_NULL), |
| }; |
| |
| return pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| } |
| |
| #ifndef TTDL_KERNEL_SUBMISSION |
| /******************************************************************************* |
| * FUNCTION: pt_pip_null |
| * |
| * SUMMARY: Wrapper function for pt_pip_null_ that guarantees exclusive access. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_pip_null(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_null_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| #endif /*!TTDL_KERNEL_SUBMISSION */ |
| |
| static void pt_stop_wd_timer(struct pt_core_data *cd); |
| /******************************************************************************* |
| * FUNCTION: pt_pip_start_bootloader_ |
| * |
| * SUMMARY: Sends the PIP command start_bootloader [PIP cmd 0x01] to the DUT |
| * |
| * NOTE: The WD MUST be stopped/restarted by the calling Function. Having |
| * the WD active could cause this function to fail! |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_pip_start_bootloader_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_START_BOOTLOADER), |
| .timeout_ms = PT_PIP1_START_BOOTLOADER_TIMEOUT, |
| .reset_expected = 1, |
| }; |
| |
| if (cd->watchdog_enabled) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: watchdog isn't stopped before enter bl\n", |
| __func__); |
| goto exit; |
| } |
| |
| /* Reset enum status after entering BL, new DUT enum required */ |
| cd->enum_status = ENUM_STATUS_START; |
| pt_debug(cd->dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Start BL PIP cmd failed. rc = %d\n", |
| __func__, rc); |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_start_bootloader |
| * |
| * SUMMARY: Protected function to force DUT to enter the BL |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip_start_bootloader(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_start_bootloader_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_start_bl |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmds to allow other |
| * modules to request the DUT to enter the BL |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to run in protected mode |
| ******************************************************************************/ |
| static int _pt_request_pip_start_bl(struct device *dev, int protect) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_start_bootloader(cd); |
| |
| return pt_pip_start_bootloader_(cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_ver_load_ttdata |
| * |
| * SUMMARY: Function to load the Version information from the PIP2 VERSION |
| * command into the core data struct. |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * len - Length of data in response_buf |
| ******************************************************************************/ |
| static void pt_pip2_ver_load_ttdata(struct pt_core_data *cd, u16 len) |
| { |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| struct pt_pip2_version_full *full_ver; |
| struct pt_pip2_version *ver; |
| |
| /* |
| * The PIP2 VERSION command can return different lengths of data. |
| * The additional LOT fields are included when the packet |
| * size is >= 29 bytes. Older FW sends a reduced packet size. |
| * NOTE: |
| * - The FW would swap the BL and FW versions when reporting |
| * the small packet. |
| * - Sub Lot bytes 16 and 17 are reserved. |
| */ |
| if (len >= 0x1D) { |
| full_ver = (struct pt_pip2_version_full *) |
| &cd->response_buf[PIP2_RESP_STATUS_OFFSET]; |
| |
| ttdata->pip_ver_major = full_ver->pip2_version_msb; |
| ttdata->pip_ver_minor = full_ver->pip2_version_lsb; |
| ttdata->bl_ver_major = full_ver->bl_version_msb; |
| ttdata->bl_ver_minor = full_ver->bl_version_lsb; |
| ttdata->fw_ver_major = full_ver->fw_version_msb; |
| ttdata->fw_ver_minor = full_ver->fw_version_lsb; |
| /* |
| * BL PIP 2.02 and greater the version fields are |
| * swapped |
| */ |
| if (ttdata->pip_ver_major >= 2 && ttdata->pip_ver_minor >= 2) { |
| ttdata->chip_rev = |
| get_unaligned_le16(&full_ver->chip_rev); |
| ttdata->chip_id = |
| get_unaligned_le16(&full_ver->chip_id); |
| } else { |
| ttdata->chip_rev = |
| get_unaligned_le16(&full_ver->chip_id); |
| ttdata->chip_id = |
| get_unaligned_le16(&full_ver->chip_rev); |
| } |
| memcpy(ttdata->uid, full_ver->uid, PT_UID_SIZE); |
| |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)full_ver, |
| sizeof(struct pt_pip2_version_full), |
| "PIP2 VERSION FULL"); |
| } else { |
| ver = (struct pt_pip2_version *) |
| &cd->response_buf[PIP2_RESP_STATUS_OFFSET]; |
| |
| ttdata->pip_ver_major = ver->pip2_version_msb; |
| ttdata->pip_ver_minor = ver->pip2_version_lsb; |
| ttdata->bl_ver_major = ver->bl_version_msb; |
| ttdata->bl_ver_minor = ver->bl_version_lsb; |
| ttdata->fw_ver_major = ver->fw_version_msb; |
| ttdata->fw_ver_minor = ver->fw_version_lsb; |
| ttdata->chip_rev = get_unaligned_le16(&ver->chip_rev); |
| ttdata->chip_id = get_unaligned_le16(&ver->chip_id); |
| |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)ver, |
| sizeof(struct pt_pip2_version), "PIP2 VERSION"); |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_si_get_ttdata |
| * |
| * SUMMARY: Function to load the version information from the system information |
| * PIP command into the core data struct. |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_si_get_ttdata(struct pt_core_data *cd) |
| { |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| struct pt_ttdata_dev *ttdata_dev; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) |
| ttdata_dev = |
| (struct pt_ttdata_dev *) |
| &cd->response_buf[HID_SYSINFO_TTDATA_OFFSET]; |
| else |
| ttdata_dev = |
| (struct pt_ttdata_dev *) |
| &cd->response_buf[PIP1_SYSINFO_TTDATA_OFFSET]; |
| |
| ttdata->pip_ver_major = ttdata_dev->pip_ver_major; |
| ttdata->pip_ver_minor = ttdata_dev->pip_ver_minor; |
| ttdata->bl_ver_major = ttdata_dev->bl_ver_major; |
| ttdata->bl_ver_minor = ttdata_dev->bl_ver_minor; |
| ttdata->fw_ver_major = ttdata_dev->fw_ver_major; |
| ttdata->fw_ver_minor = ttdata_dev->fw_ver_minor; |
| |
| ttdata->fw_pid = get_unaligned_le16(&ttdata_dev->fw_pid); |
| ttdata->fw_ver_conf = get_unaligned_le16(&ttdata_dev->fw_ver_conf); |
| ttdata->post_code = get_unaligned_le16(&ttdata_dev->post_code); |
| ttdata->revctrl = get_unaligned_le32(&ttdata_dev->revctrl); |
| ttdata->jtag_id_l = get_unaligned_le16(&ttdata_dev->jtag_si_id_l); |
| ttdata->jtag_id_h = get_unaligned_le16(&ttdata_dev->jtag_si_id_h); |
| |
| memcpy(ttdata->mfg_id, ttdata_dev->mfg_id, PT_NUM_MFGID); |
| |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)ttdata_dev, |
| sizeof(struct pt_ttdata_dev), "sysinfo_ttdata"); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_si_get_sensing_conf_data |
| * |
| * SUMMARY: Function to load the sensing information from the system information |
| * PIP command into the core data struct. |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_si_get_sensing_conf_data(struct pt_core_data *cd) |
| { |
| struct pt_sensing_conf_data *scd = &cd->sysinfo.sensing_conf_data; |
| struct pt_sensing_conf_data_dev *scd_dev; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) |
| scd_dev = |
| (struct pt_sensing_conf_data_dev *) |
| &cd->response_buf[HID_SYSINFO_SENSING_OFFSET]; |
| else |
| scd_dev = |
| (struct pt_sensing_conf_data_dev *) |
| &cd->response_buf[PIP1_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; |
| } |
| |
| /* |
| * When the Panel ID is coming from an XY pin and not a dedicated |
| * GPIO, store the PID in pid_for_loader. This cannot be done for all |
| * other DUTs as the loader will use cd->pid_for_loader to generate |
| * the bin file name but will ignore it if pid_for_loader is still |
| * set to PANEL_ID_NOT_ENABLED |
| */ |
| if (cd->panel_id_support & |
| (PT_PANEL_ID_BY_BL | PT_PANEL_ID_BY_SYS_INFO)) { |
| mutex_lock(&cd->system_lock); |
| cd->pid_for_loader = scd_dev->panel_id; |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| 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); |
| |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)scd_dev, |
| sizeof(struct pt_sensing_conf_data_dev), |
| "sensing_conf_data"); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_si_setup |
| * |
| * SUMMARY: Setup the xy_data and xy_mode by allocating the needed memory |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_si_setup(struct pt_core_data *cd) |
| { |
| struct pt_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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_si_get_btn_data |
| * |
| * SUMMARY: Setup the core data button information based on the response of the |
| * System Information PIP command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_si_get_btn_data(struct pt_core_data *cd) |
| { |
| struct pt_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; |
| size_t btn_keys_size; |
| |
| pt_debug(cd->dev, DL_INFO, "%s: get btn data\n", __func__); |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) |
| btns = cd->response_buf[HID_SYSINFO_BTN_OFFSET] |
| & PIP1_SYSINFO_BTN_MASK; |
| else |
| btns = cd->response_buf[PIP1_SYSINFO_BTN_OFFSET] |
| & PIP1_SYSINFO_BTN_MASK; |
| |
| for (i = 0; i < PIP1_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 pt_btn); |
| if (!si->btn) |
| si->btn = kzalloc(btn_keys_size, GFP_KERNEL); |
| if (!si->btn) |
| return -ENOMEM; |
| |
| if (cd->cpdata->sett[PT_IC_GRPNUM_BTN_KEYS] == NULL) |
| num_defined_keys = 0; |
| else if (cd->cpdata->sett[PT_IC_GRPNUM_BTN_KEYS]->data == NULL) |
| num_defined_keys = 0; |
| else |
| num_defined_keys = cd->cpdata->sett |
| [PT_IC_GRPNUM_BTN_KEYS]->size; |
| |
| for (btn = 0; btn < num_btns && btn < num_defined_keys; btn++) { |
| key_table = (u16 *)cd->cpdata->sett |
| [PT_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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_si_put_log_data |
| * |
| * SUMMARY: Prints all sys info data to kmsg log |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_si_put_log_data(struct pt_core_data *cd) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| struct pt_ttdata *ttdata = &si->ttdata; |
| struct pt_sensing_conf_data *scd = &si->sensing_conf_data; |
| int i; |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: pip_ver_major = 0x%02X (%d)\n", |
| __func__, ttdata->pip_ver_major, ttdata->pip_ver_major); |
| pt_debug(cd->dev, DL_DEBUG, "%s: pip_ver_minor = 0x%02X (%d)\n", |
| __func__, ttdata->pip_ver_minor, ttdata->pip_ver_minor); |
| pt_debug(cd->dev, DL_DEBUG, "%s: fw_pid = 0x%04X (%d)\n", |
| __func__, ttdata->fw_pid, ttdata->fw_pid); |
| pt_debug(cd->dev, DL_DEBUG, "%s: fw_ver_major = 0x%02X (%d)\n", |
| __func__, ttdata->fw_ver_major, ttdata->fw_ver_major); |
| pt_debug(cd->dev, DL_DEBUG, "%s: fw_ver_minor = 0x%02X (%d)\n", |
| __func__, ttdata->fw_ver_minor, ttdata->fw_ver_minor); |
| pt_debug(cd->dev, DL_DEBUG, "%s: revctrl = 0x%08X (%d)\n", |
| __func__, ttdata->revctrl, ttdata->revctrl); |
| pt_debug(cd->dev, DL_DEBUG, "%s: fw_ver_conf = 0x%04X (%d)\n", |
| __func__, ttdata->fw_ver_conf, ttdata->fw_ver_conf); |
| pt_debug(cd->dev, DL_DEBUG, "%s: bl_ver_major = 0x%02X (%d)\n", |
| __func__, ttdata->bl_ver_major, ttdata->bl_ver_major); |
| pt_debug(cd->dev, DL_DEBUG, "%s: bl_ver_minor = 0x%02X (%d)\n", |
| __func__, ttdata->bl_ver_minor, ttdata->bl_ver_minor); |
| pt_debug(cd->dev, DL_DEBUG, "%s: jtag_id_h = 0x%04X (%d)\n", |
| __func__, ttdata->jtag_id_h, ttdata->jtag_id_h); |
| pt_debug(cd->dev, DL_DEBUG, "%s: jtag_id_l = 0x%04X (%d)\n", |
| __func__, ttdata->jtag_id_l, ttdata->jtag_id_l); |
| |
| for (i = 0; i < PT_NUM_MFGID; i++) |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: mfg_id[%d] = 0x%02X (%d)\n", |
| __func__, i, ttdata->mfg_id[i], |
| ttdata->mfg_id[i]); |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: post_code = 0x%04X (%d)\n", |
| __func__, ttdata->post_code, ttdata->post_code); |
| pt_debug(cd->dev, DL_DEBUG, "%s: electrodes_x = 0x%02X (%d)\n", |
| __func__, scd->electrodes_x, scd->electrodes_x); |
| pt_debug(cd->dev, DL_DEBUG, "%s: electrodes_y = 0x%02X (%d)\n", |
| __func__, scd->electrodes_y, scd->electrodes_y); |
| pt_debug(cd->dev, DL_DEBUG, "%s: len_x = 0x%04X (%d)\n", |
| __func__, scd->len_x, scd->len_x); |
| pt_debug(cd->dev, DL_DEBUG, "%s: len_y = 0x%04X (%d)\n", |
| __func__, scd->len_y, scd->len_y); |
| pt_debug(cd->dev, DL_DEBUG, "%s: res_x = 0x%04X (%d)\n", |
| __func__, scd->res_x, scd->res_x); |
| pt_debug(cd->dev, DL_DEBUG, "%s: res_y = 0x%04X (%d)\n", |
| __func__, scd->res_y, scd->res_y); |
| pt_debug(cd->dev, DL_DEBUG, "%s: max_z = 0x%04X (%d)\n", |
| __func__, scd->max_z, scd->max_z); |
| pt_debug(cd->dev, DL_DEBUG, "%s: origin_x = 0x%02X (%d)\n", |
| __func__, scd->origin_x, scd->origin_x); |
| pt_debug(cd->dev, DL_DEBUG, "%s: origin_y = 0x%02X (%d)\n", |
| __func__, scd->origin_y, scd->origin_y); |
| pt_debug(cd->dev, DL_DEBUG, "%s: panel_id = 0x%02X (%d)\n", |
| __func__, scd->panel_id, scd->panel_id); |
| pt_debug(cd->dev, DL_DEBUG, "%s: btn =0x%02X (%d)\n", |
| __func__, scd->btn, scd->btn); |
| pt_debug(cd->dev, DL_DEBUG, "%s: scan_mode = 0x%02X (%d)\n", |
| __func__, scd->scan_mode, scd->scan_mode); |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: max_num_of_tch_per_refresh_cycle = 0x%02X (%d)\n", |
| __func__, scd->max_tch, scd->max_tch); |
| pt_debug(cd->dev, DL_DEBUG, "%s: xy_mode = %p\n", |
| __func__, si->xy_mode); |
| pt_debug(cd->dev, DL_DEBUG, "%s: xy_data = %p\n", |
| __func__, si->xy_data); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_sysinfo_regs |
| * |
| * SUMMARY: Setup all the core data System information based on the response |
| * of the System Information PIP command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_get_sysinfo_regs(struct pt_core_data *cd) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| int rc; |
| |
| rc = pt_si_get_btn_data(cd); |
| if (rc < 0) |
| return rc; |
| |
| pt_si_get_ttdata(cd); |
| |
| pt_si_get_sensing_conf_data(cd); |
| |
| pt_si_setup(cd); |
| |
| pt_si_put_log_data(cd); |
| |
| si->ready = true; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_free_si_ptrs |
| * |
| * SUMMARY: Frees all memory associated with the System Information within |
| * core data |
| * |
| * RETURN: n/a |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_free_si_ptrs(struct pt_core_data *cd) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| |
| kfree(si->btn); |
| kfree(si->xy_mode); |
| kfree(si->xy_data); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_get_sysinfo_ |
| * |
| * SUMMARY: Sends the PIP Get SYS INFO command to the DUT and waits for the |
| * response. |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_hid_output_get_sysinfo_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_SYSINFO), |
| .timeout_ms = PT_PIP1_CMD_GET_SYSINFO_TIMEOUT, |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| /* Parse the sysinfo data */ |
| rc = pt_get_sysinfo_regs(cd); |
| if (rc) |
| pt_free_si_ptrs(cd); |
| |
| return rc; |
| } |
| |
| #ifndef TTDL_KERNEL_SUBMISSION |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_get_sysinfo |
| * |
| * SUMMARY: Protected call to pt_hid_output_get_sysinfo_ |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_hid_output_get_sysinfo(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_get_sysinfo_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| #endif /*!TTDL_KERNEL_SUBMISSION */ |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_suspend_scanning_ |
| * |
| * SUMMARY: Sends the PIP Suspend Scanning command to the DUT |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip_suspend_scanning_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_SUSPEND_SCANNING), |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Suspend Scan PIP cmd failed. rc = %d\n", |
| __func__, rc); |
| } |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_suspend_scanning |
| * |
| * SUMMARY: Protected wrapper for calling pt_hid_output_suspend_scanning_ |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip_suspend_scanning(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_suspend_scanning_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_suspend_scanning |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_suspend_scanning |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - 0 = call non-protected function |
| * 1 = call protected function |
| ******************************************************************************/ |
| static int _pt_request_pip_suspend_scanning(struct device *dev, |
| int protect) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_suspend_scanning(cd); |
| |
| return pt_pip_suspend_scanning_(cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_resume_scanning_ |
| * |
| * SUMMARY: Sends the PIP Resume Scanning command to the DUT |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip_resume_scanning_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_RESUME_SCANNING), |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Resume Scan PIP cmd failed. rc = %d\n", |
| __func__, rc); |
| } |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_resume_scanning |
| * |
| * SUMMARY: Protected wrapper for calling pt_pip_resume_scanning_ |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip_resume_scanning(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_resume_scanning_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_resume_scanning |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_resume_scanning |
| * |
| * RETURN:: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - 0 = call non-protected function |
| * 1 = call protected function |
| ******************************************************************************/ |
| static int _pt_request_pip_resume_scanning(struct device *dev, |
| int protect) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_resume_scanning(cd); |
| |
| return pt_pip_resume_scanning_(cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_get_param_ |
| * |
| * SUMMARY: Sends a PIP command 0x05 Get Parameter to the DUT and returns |
| * the 32bit parameter value |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * param_id - parameter ID to retrieve |
| * *value - value of DUT parameter |
| ******************************************************************************/ |
| static int pt_pip_get_param_(struct pt_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 = 0; |
| int i; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_PARAM), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| read_param_id = cd->response_buf[6]; |
| param_size = cd->response_buf[7]; |
| ptr = &cd->response_buf[8]; |
| } else { |
| read_param_id = cd->response_buf[5]; |
| param_size = cd->response_buf[6]; |
| ptr = &cd->response_buf[7]; |
| } |
| |
| if (read_param_id != param_id) |
| return -EPROTO; |
| |
| *value = 0; |
| for (i = 0; i < param_size; i++) |
| *value += ptr[i] << (i * 8); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_get_param |
| * |
| * SUMMARY: Protected call to pt_hid_output_get_param_ by a request exclusive |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * param_id - parameter ID to retrieve |
| * *value - value of DUT parameter |
| ******************************************************************************/ |
| static int pt_pip_get_param(struct pt_core_data *cd, |
| u8 param_id, u32 *value) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_get_param_(cd, param_id, value); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_get_param |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_get_param |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non protected function |
| * param_id - parameter ID to retrieve |
| * *value - value of DUT parameter |
| ******************************************************************************/ |
| int _pt_request_pip_get_param(struct device *dev, |
| int protect, u8 param_id, u32 *value) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_get_param(cd, param_id, value); |
| |
| return pt_pip_get_param_(cd, param_id, value); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_set_param_ |
| * |
| * SUMMARY: Sends a PIP command 0x06 Set Parameter to the DUT writing the |
| * passed in value to flash |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * param_id - parameter ID to set |
| * value - value to write |
| * size - size to write |
| ******************************************************************************/ |
| static int pt_pip_set_param_(struct pt_core_data *cd, |
| u8 param_id, u32 value, u8 size) |
| { |
| u8 write_buf[6]; |
| u8 *ptr = &write_buf[2]; |
| int rc = 0; |
| int i; |
| u8 read_param_id = 0; |
| u8 read_size = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_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; |
| } |
| |
| pip1_cmd.write_length = 2 + size; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| read_param_id = cd->response_buf[6]; |
| read_size = cd->response_buf[7]; |
| } else { |
| read_param_id = cd->response_buf[5]; |
| read_size = cd->response_buf[6]; |
| } |
| |
| if (param_id != read_param_id || size != read_size) |
| return -EPROTO; |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_set_param |
| * |
| * SUMMARY: Protected call to pt_hid_output_set_param_ by a request exclusive |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * param_id - parameter ID to set |
| * value - value to write |
| * size - size to write |
| ******************************************************************************/ |
| static int pt_pip_set_param(struct pt_core_data *cd, |
| u8 param_id, u32 value, u8 size) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_set_param_(cd, param_id, value, size); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_set_param |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_set_param |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * param_id - parameter ID to set |
| * value - value to write |
| * size - size to write |
| ******************************************************************************/ |
| int _pt_request_pip_set_param(struct device *dev, int protect, |
| u8 param_id, u32 value, u8 size) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_set_param(cd, param_id, value, size); |
| |
| return pt_pip_set_param_(cd, param_id, value, size); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip_enter_easywake_state_ |
| * |
| * SUMMARY: Sends a PIP command 0x09 Enter EasyWake State to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * data - easywake guesture (Only used for PIP1.6 and earlier) |
| * *return_data - return status if easywake was entered |
| ******************************************************************************/ |
| static int pt_hid_output_enter_easywake_state_( |
| struct pt_core_data *cd, u8 data, u8 *return_data) |
| { |
| int write_length = 1; |
| u8 param[1] = { data }; |
| int rc = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_ENTER_EASYWAKE_STATE), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| *return_data = cd->response_buf[5]; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_verify_config_block_crc_ |
| * |
| * SUMMARY: Sends the PIP "Verify Data Block CRC" (0x20) command to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * ebid - enumerated block ID |
| * *status - PIP command status |
| * calculated_crc - calculated CRC |
| * stored_crc - stored CRC in config area |
| ******************************************************************************/ |
| static int pt_pip_verify_config_block_crc_( |
| struct pt_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 = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC), |
| .write_length = write_length, |
| .write_buf = param, |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| 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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_verify_config_block_crc |
| * |
| * SUMMARY: Protected call to pt_hid_output_verify_config_block_crc_() within |
| * an exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * ebid - enumerated block ID |
| * *status - PIP command status |
| * calculated_crc - calculated CRC |
| * stored_crc - stored CRC in config area |
| ******************************************************************************/ |
| static int pt_pip_verify_config_block_crc( |
| struct pt_core_data *cd, u8 ebid, u8 *status, |
| u16 *calculated_crc, u16 *stored_crc) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_verify_config_block_crc_(cd, ebid, status, |
| calculated_crc, stored_crc); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_verify_config_block_crc |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip_verify_config_block_crc_ |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * ebid - enumerated block ID |
| * *status - PIP command status |
| * calculated_crc - calculated CRC |
| * stored_crc - stored CRC in config area |
| ******************************************************************************/ |
| static int _pt_request_pip_verify_config_block_crc( |
| struct device *dev, int protect, u8 ebid, u8 *status, |
| u16 *calculated_crc, u16 *stored_crc) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_verify_config_block_crc(cd, ebid, |
| status, calculated_crc, stored_crc); |
| |
| return pt_pip_verify_config_block_crc_(cd, ebid, |
| status, calculated_crc, stored_crc); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_get_config_row_size_ |
| * |
| * SUMMARY: Sends the PIP "Get Data Row Size" (0x21) command to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * protect - flag to call protected or non-protected |
| * *row_size - pointer to store the retrieved row size |
| ******************************************************************************/ |
| static int pt_pip_get_config_row_size_(struct pt_core_data *cd, |
| u16 *row_size) |
| { |
| int rc = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_CONFIG_ROW_SIZE), |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) |
| *row_size = get_unaligned_le16(&cd->response_buf[6]); |
| else |
| *row_size = get_unaligned_le16(&cd->response_buf[5]); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_get_config_row_size |
| * |
| * SUMMARY: Protected call to pt_hid_output_get_config_row_size_ within |
| * an exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * protect - flag to call protected or non-protected |
| * *row_size - pointer to store the retrieved row size |
| ******************************************************************************/ |
| static int pt_pip_get_config_row_size(struct pt_core_data *cd, |
| u16 *row_size) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_get_config_row_size_(cd, row_size); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_get_config_row_size |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip_get_config_row_size_ |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * *row_size - pointer to store the retrieved row size |
| ******************************************************************************/ |
| static int _pt_request_pip_get_config_row_size(struct device *dev, |
| int protect, u16 *row_size) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_get_config_row_size(cd, row_size); |
| |
| return pt_pip_get_config_row_size_(cd, row_size); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_read_data_block_ |
| * |
| * SUMMARY: Sends the PIP "Read Data Block" (0x22) command to the DUT and print |
| * output data to the "read_buf" and update "crc". |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * row_number - row number |
| * length - length of data to read |
| * ebid - block id |
| * *actual_read_len - Actual data length read |
| * *read_buf - pointer to the buffer to store read data |
| * read_buf_size - size of read_buf |
| * *crc - pointer to store CRC of row data |
| ******************************************************************************/ |
| static int pt_pip1_read_data_block_(struct pt_core_data *cd, |
| u16 row_number, u16 length, u8 ebid, u16 *actual_read_len, |
| u8 *read_buf, u16 read_buf_size, u16 *crc) |
| { |
| int read_ebid; |
| int status; |
| int rc = 0; |
| int write_length = 5; |
| u8 write_buf[5]; |
| u8 cmd_offset = 0; |
| u16 calc_crc; |
| u16 rsp_len; |
| u8 pip3_exofs = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_READ_DATA_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; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| cd->force_pip3_report_type = true; |
| pip3_exofs = 1; |
| } |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| goto exit; |
| |
| status = cd->response_buf[5 - pip3_exofs]; |
| if (status) { |
| rc = status; |
| goto exit; |
| } |
| |
| read_ebid = cd->response_buf[6 - pip3_exofs]; |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| if (read_ebid != ebid) { |
| rc = -EPROTO; |
| goto exit; |
| } |
| } else { |
| if ((read_ebid != ebid) || (cd->response_buf[9] != 0)) { |
| rc = -EPROTO; |
| goto exit; |
| } |
| } |
| |
| rsp_len = get_unaligned_le16(&cd->response_buf[0]); |
| |
| /* |
| * For PIP3/HID command 0x22, the offset of ARL filed in |
| * the response payload is 6, so PIP1 offset (7) needs |
| * to be decremented by 1. |
| */ |
| *actual_read_len = get_unaligned_le16( |
| &cd->response_buf[7 - pip3_exofs]); |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Resp Len=0x%04X Actual Read Length=0x%04X\n", |
| __func__, rsp_len, *actual_read_len); |
| if (length == 0 || *actual_read_len == 0) |
| goto exit; |
| |
| /* Do not continue if the ARL is larger than the response */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| (*actual_read_len + 12) != rsp_len) { |
| /* |
| * For HID/PIP3 response, command 0x22 payload length is |
| * 12 bytes larger than ARL. |
| */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid Actual Read Length\n", __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| /* |
| * For PIP3/HID command 0x22, the offset of DATA filed in |
| * the response payload is 8, so PIP1 offset (10) needs |
| * to be decremented by 2. |
| */ |
| if (read_buf_size >= *actual_read_len) |
| memcpy(read_buf, &cd->response_buf[10 - 2 * pip3_exofs], |
| *actual_read_len); |
| else { |
| rc = -EPROTO; |
| goto exit; |
| } |
| |
| /* |
| * For PIP3/HID command 0x22, the offset of CRC filed in |
| * the response payload is (ARL+8), so PIP1 offset (ARL+10) |
| * needs to be decremented by 2. |
| */ |
| *crc = get_unaligned_le16( |
| &cd->response_buf[*actual_read_len + 10 - 2 * pip3_exofs]); |
| |
| /* Validate Row Data CRC */ |
| calc_crc = _pt_compute_crc(read_buf, *actual_read_len); |
| if (*crc == calc_crc) { |
| goto exit; |
| } else { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: CRC Missmatch packet=0x%04X calc=0x%04X\n", |
| __func__, *crc, calc_crc); |
| rc = -EPROTO; |
| } |
| |
| exit: |
| cd->force_pip3_report_type = false; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_read_data_block |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to pt_pip1_read_data_block_ |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * row_number - row number |
| * length - length of data to read |
| * ebid - block id |
| * *actual_read_len - Actual data length read |
| * *read_buf - pointer to the buffer to store read data |
| * *crc - pointer to store CRC of row data |
| ******************************************************************************/ |
| static int _pt_request_pip_read_data_block(struct device *dev, |
| u16 row_number, u16 length, u8 ebid, u16 *actual_read_len, |
| u8 *read_buf, u16 read_buf_size, u16 *crc) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return pt_pip1_read_data_block_(cd, row_number, length, |
| ebid, actual_read_len, read_buf, read_buf_size, crc); |
| } |
| |
| /* |
| * There is fixed 4 bytes shift, 5 bytes are needed at least if to read |
| * 1 byte (i.e. MFG_VAL0) from Manufactuary Data Block. |
| */ |
| #define MDATA_OFFSET (4) |
| #define MDATA_READ_SIZE (1) |
| #define MDATA_READ_BUFFER_SIZE (MDATA_OFFSET + MDATA_READ_SIZE) |
| /******************************************************************************* |
| * FUNCTION: _pt_get_pid_from_mfg_data_ |
| * |
| * SUMMARY: Wrapper function to call pt_pip1_read_data_block_() to get |
| * Manufacturing Data which stores panel ID information in this case. |
| * |
| * NOTE: This function doesn't support PIP3. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *core_data - pointer to core data |
| * *panel_id - pointer to store Panel ID |
| ******************************************************************************/ |
| static int _pt_get_pid_from_mfg_data_(struct pt_core_data *core_data, u8 *panel_id) |
| { |
| int rc; |
| u8 read_buf[MDATA_READ_BUFFER_SIZE]; |
| u16 crc; |
| u16 actual_read_len = 0; |
| |
| if (core_data->protocol_mode == PT_PROTOCOL_MODE_HID) |
| return -EINVAL; |
| |
| rc = pt_pip1_read_data_block_(core_data, 0, MDATA_READ_BUFFER_SIZE, PT_MDATA_EBID, |
| &actual_read_len, read_buf, MDATA_READ_BUFFER_SIZE, &crc); |
| if (rc) |
| return rc; |
| |
| if (panel_id) |
| *panel_id = read_buf[MDATA_OFFSET]; |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_pid_from_mfg_data_ |
| * |
| * SUMMARY: This function retrieves MFG_DATA[0] to get panel ID information. |
| * |
| * NOTE: The post condition of calling this function will be that the DUT will |
| * be in SCANNINING mode if no failures occur |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *core_data - pointer to core data |
| * *panel_id - pointer to store Panel ID |
| ******************************************************************************/ |
| static int pt_get_pid_from_mfg_data_(struct pt_core_data *core_data, u8 *panel_id) |
| { |
| int rc; |
| |
| rc = pt_pip_suspend_scanning_(core_data); |
| if (rc) |
| goto error; |
| |
| rc = _pt_get_pid_from_mfg_data_(core_data, panel_id); |
| |
| pt_pip_resume_scanning_(core_data); |
| error: |
| pt_debug(core_data->dev, DL_INFO, "%s: panel_id:%02X\n", __func__, *panel_id); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip1_write_data_block_ |
| * |
| * SUMMARY: Sends the PIP "Write Data Block" (0x23) command to the DUT and |
| * write data to the data block. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * row_number - row in config block to write to |
| * write_length - length of data to write |
| * ebid - enumerated block ID |
| * *write_buf - pointer to buffer to write |
| * *security_key - pointer to security key to allow write |
| * *actual_write_len - pointer to store data length actually written |
| ******************************************************************************/ |
| static int pt_pip1_write_data_block_(struct pt_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 = 0; |
| int read_ebid; |
| u8 *data; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_WRITE_DATA_BLOCK), |
| .write_length = full_write_length, |
| .timeout_ms = PT_PIP1_CMD_WRITE_CONF_BLOCK_TIMEOUT, |
| }; |
| |
| full_write_buf = kzalloc(full_write_length, GFP_KERNEL); |
| if (!full_write_buf) |
| return -ENOMEM; |
| |
| pip1_cmd.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 = _pt_compute_crc(data, write_length); |
| full_write_buf[cmd_offset++] = LOW_BYTE(crc); |
| full_write_buf[cmd_offset++] = HI_BYTE(crc); |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| 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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_write_data_block |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip1_write_data_block_ |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * row_number - row in config block to write to |
| * write_length - length of data to write |
| * ebid - enumerated block ID |
| * *write_buf - pointer to buffer to write |
| * *security_key - pointer to security key to allow write |
| * *actual_write_len - pointer to store data length actually written |
| ******************************************************************************/ |
| static int _pt_request_pip_write_data_block(struct device *dev, |
| u16 row_number, u16 write_length, u8 ebid, |
| u8 *write_buf, u8 *security_key, u16 *actual_write_len) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return pt_pip1_write_data_block_(cd, row_number, |
| write_length, ebid, write_buf, security_key, |
| actual_write_len); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_get_data_structure_ |
| * |
| * SUMMARY: Sends the PIP "Retrieve Data Structure" (0x24) command to the DUT |
| * returning a structure of data defined by data_id |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * read_offset - read pointer offset |
| * read_length - length of data to read |
| * data_id - data ID to read |
| * *status - pointer to store the read response status |
| * *data_format - pointer to store format of data read |
| * *actual_read_len - pointer to store data length actually read |
| * *data - pointer to store data read |
| ******************************************************************************/ |
| static int pt_pip_get_data_structure_( |
| struct pt_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 = 0; |
| u16 total_read_len = 0; |
| u16 rsp_len; |
| u16 read_len; |
| u16 off_buf = 0; |
| u8 write_buf[5]; |
| u8 read_data_id; |
| u8 pip3_exofs = 0; |
| u8 data_elem_size; |
| int data_size; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_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; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| cd->force_pip3_report_type = true; |
| pip3_exofs = 1; |
| } |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| goto exit; |
| |
| if (cd->response_buf[5 - pip3_exofs] != PT_CMD_STATUS_SUCCESS) |
| goto set_status; |
| |
| read_data_id = cd->response_buf[6 - pip3_exofs]; |
| if (read_data_id != data_id) { |
| rc = -EPROTO; |
| goto exit; |
| } |
| |
| rsp_len = get_unaligned_le16(&cd->response_buf[0]); |
| read_len = get_unaligned_le16(&cd->response_buf[7]); |
| data_elem_size = cd->response_buf[9] & 0x07; |
| data_size = read_len * data_elem_size; |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%s: %s=0x%04X %s=0x%04X %s=0x%02X %s=0x%04X\n", |
| __func__, "Resp Len", rsp_len, |
| "Actual Read Length", read_len, |
| "Element Size", data_elem_size, |
| "Calc Data Size", data_size); |
| |
| /* Do not continue if the ARL is larger than the response */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| (data_size + 12) != rsp_len) { |
| /* |
| * For HID/PIP3 response, command 0x24 payload length |
| * is 12 bytes larger than ARL * Element Size. |
| */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid Actual Read Length\n", __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| if (read_len && data) { |
| memcpy(&data[off_buf], |
| &cd->response_buf[10], read_len); |
| |
| total_read_len += read_len; |
| |
| if (read_len < read_length && |
| cd->protocol_mode != PT_PROTOCOL_MODE_HID) { |
| 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 - pip3_exofs]; |
| |
| exit: |
| cd->force_pip3_report_type = false; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_get_data_structure |
| * |
| * SUMMARY: Protected call to pt_hid_output_get_data_structure within |
| * an exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * read_offset - read pointer offset |
| * read_length - length of data to read |
| * data_id - data ID to read |
| * *status - pointer to store the read response status |
| * *data_format - pointer to store format of data read |
| * *actual_read_len - pointer to store data length actually read |
| * *data - pointer to store data read |
| ******************************************************************************/ |
| static int pt_pip_get_data_structure( |
| struct pt_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, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_get_data_structure_(cd, read_offset, |
| read_length, data_id, status, data_format, |
| actual_read_len, data); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_get_data_structure |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip_get_data_structure |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * read_offset - read pointer offset |
| * read_length - length of data to read |
| * data_id - data ID to read |
| * *status - pointer to store the read response status |
| * *data_format - pointer to store format of data read |
| * *actual_read_len - pointer to store data length actually read |
| * *data - pointer to store data read |
| ******************************************************************************/ |
| static int _pt_request_pip_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 pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_get_data_structure(cd, |
| read_offset, read_length, data_id, status, |
| data_format, actual_read_len, data); |
| |
| return pt_pip_get_data_structure_(cd, |
| read_offset, read_length, data_id, status, |
| data_format, actual_read_len, data); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_manage_local_cal_data |
| * |
| * SUMMARY: This function manages storing or restoring a copy of the Firmware |
| * CALIBRATION data. It stores it in a local static array and can be |
| * cleared, loaded or used to restore the CAL data back to the running FW. |
| * The CAL data is read or restored by use of the PIP1 commands: |
| * - READ_DATA_BLOCK (0x22) |
| * - WRITE_DATA_BLOCK (0x23) |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * action - One of the following actions: |
| * - PT_CAL_DATA_SAVE |
| * - PT_CAL_DATA_RESTORE |
| * - PT_CAL_DATA_CLEAR |
| * - PT_CAL_DATA_SIZE |
| * *size - pointer to the number of bytes transfered |
| * *crc - pointer to Chip ID CRC that the CAL data was retrieved from |
| ******************************************************************************/ |
| static int _pt_manage_local_cal_data(struct device *dev, u8 action, u16 *size, |
| unsigned short *crc) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| unsigned short calc_id_crc = 0; |
| static u8 *cal_cache_data; |
| static u16 cal_cache_len; |
| static unsigned short cal_cache_chip_id; |
| int rc = 0; |
| u8 *tmp_data = NULL; |
| u8 row_number = 0; |
| u8 prefix[20]; |
| u16 cal_size = 0; |
| u16 transfer_size; |
| u16 act_trans_len = 0; |
| u16 byte_offset = 0; |
| u16 cal_blk_size; |
| u16 total_rows; |
| u16 remain_bytes; |
| u16 data_block_crc; |
| u16 buf_size = 12; |
| |
| pt_debug(dev, DL_INFO, "%s: ATM - CAL Cache action=%d\n", |
| __func__, action); |
| switch (action) { |
| case PT_CAL_DATA_SAVE: |
| /* Read the size of the CAL block and calculate # rows */ |
| tmp_data = kzalloc(buf_size, GFP_KERNEL); |
| if (!tmp_data) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| /* |
| * Don't check rc as doing a read size will give a false |
| * error on the CRC check. |
| */ |
| rc = pt_pip1_read_data_block_(cd, row_number, 0, PT_CAL_EBID, |
| &act_trans_len, tmp_data, buf_size, &data_block_crc); |
| cal_blk_size = act_trans_len; |
| kfree(tmp_data); |
| |
| pt_debug(dev, DL_INFO, |
| "%s: CAL Cache size=%d FW CAL Size=%d\n", |
| __func__, cal_cache_len, cal_blk_size); |
| |
| /* Safety net to ensure we didn't read incorrect size */ |
| if (cal_blk_size > PT_CAL_DATA_MAX_SIZE) { |
| pt_debug(dev, DL_ERROR, "%s: Alloc struct Failed\n", |
| __func__); |
| rc = 1; |
| goto exit; |
| } |
| |
| /* Panels could have diff CAL sizes, Re-allocate the cache */ |
| if (cal_blk_size != cal_cache_len) { |
| kfree(cal_cache_data); |
| cal_cache_data = kzalloc(cal_blk_size + 2, |
| GFP_KERNEL); |
| if (!cal_cache_data) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| pt_debug(dev, DL_INFO, "%s: CAL Cache Allocated\n", |
| __func__); |
| } |
| memset(&cal_cache_data[0], 0, cal_blk_size + 2); |
| |
| /* Calculate how many rows [0-n] (PIP Transactions) */ |
| total_rows = (cal_blk_size / PT_CAL_DATA_ROW_SIZE) - 1; |
| remain_bytes = cal_blk_size % PT_CAL_DATA_ROW_SIZE; |
| /* Add row if we have a last partial row */ |
| if (remain_bytes > 0) |
| total_rows++; |
| pt_debug(dev, DL_INFO, |
| "%s: CAL size=%d rows=[0-%d] partial row bytes=%d\n", |
| __func__, cal_blk_size, total_rows, remain_bytes); |
| |
| /* Read all rows unless an error occurs */ |
| rc = 0; |
| while (rc == 0 && row_number <= total_rows) { |
| act_trans_len = 0; |
| if (remain_bytes > 0 && row_number == total_rows) |
| transfer_size = remain_bytes; |
| else |
| transfer_size = PT_CAL_DATA_ROW_SIZE; |
| |
| rc = pt_pip1_read_data_block_(cd, row_number, |
| transfer_size, PT_CAL_EBID, |
| &act_trans_len, |
| &cal_cache_data[byte_offset], cal_blk_size + 2, |
| &data_block_crc); |
| if (rc) { |
| /* Error occured, exit loop */ |
| cal_size = 0; |
| break; |
| } |
| |
| pt_debug(dev, DL_INFO, |
| "%s: CAL read rc=%d actual read len=%d\n", |
| __func__, rc, act_trans_len); |
| |
| byte_offset += act_trans_len; |
| cal_size = byte_offset; |
| |
| scnprintf(prefix, sizeof(prefix), "%s[%d]", "CAL DATA ROW", row_number); |
| pt_pr_buf(dev, DL_INFO, |
| &cal_cache_data[byte_offset - act_trans_len], |
| act_trans_len, prefix); |
| |
| row_number++; |
| } |
| |
| if (cal_size > 0) { |
| /* Save a CRC of the chip info the CAL was saved from */ |
| calc_id_crc = crc_ccitt_calculate( |
| (u8 *)&ttdata->chip_rev, 4 + PT_UID_SIZE); |
| cal_cache_chip_id = calc_id_crc; |
| cal_cache_len = cal_size; |
| pt_debug(dev, DL_INFO, |
| "%s: CAL Cache: CRC=0x%04X Total Size=%d\n", |
| __func__, calc_id_crc, cal_size); |
| } |
| |
| *size = cal_size; |
| *crc = calc_id_crc; |
| break; |
| case PT_CAL_DATA_RESTORE: |
| cal_size = cal_cache_len; |
| while ((rc == 0) && (byte_offset < cal_size)) { |
| if (cal_size - byte_offset > PT_CAL_DATA_ROW_SIZE) |
| transfer_size = PT_CAL_DATA_ROW_SIZE; |
| else |
| transfer_size = cal_size - byte_offset; |
| |
| rc = pt_pip1_write_data_block_(cd, row_number, |
| transfer_size, PT_CAL_EBID, |
| &cal_cache_data[byte_offset], |
| (u8 *)pt_data_block_security_key, |
| &act_trans_len); |
| |
| byte_offset += act_trans_len; |
| pt_debug(dev, DL_INFO, "%s: CAL write byte offset=%d\n", |
| __func__, byte_offset); |
| |
| scnprintf(prefix, sizeof(prefix), "%s[%d]", "CAL DATA ROW", row_number); |
| pt_pr_buf(dev, DL_INFO, |
| &cal_cache_data[byte_offset - act_trans_len], |
| act_trans_len, prefix); |
| |
| |
| if ((byte_offset > cal_size) || |
| (act_trans_len != transfer_size)) |
| rc = -EIO; |
| row_number++; |
| } |
| *size = byte_offset; |
| *crc = cal_cache_chip_id; |
| break; |
| case PT_CAL_DATA_CLEAR: |
| if (cal_cache_data) |
| memset(&cal_cache_data[0], 0, cal_cache_len); |
| cal_cache_len = 0; |
| cal_cache_chip_id = 0; |
| *size = 0; |
| *crc = 0; |
| break; |
| case PT_CAL_DATA_INFO: |
| default: |
| *size = cal_cache_len; |
| *crc = cal_cache_chip_id; |
| pt_debug(dev, DL_INFO, |
| "%s: CAL Cache: CRC=%04X Total Size=%d\n", |
| __func__, cal_cache_chip_id, |
| cal_cache_len); |
| break; |
| } |
| |
| exit: |
| pt_debug(dev, DL_INFO, |
| "%s: CAL Cache exit: rc=%d CRC=0x%04X Total Size=%d\n", |
| __func__, rc, *crc, *size); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_run_selftest_ |
| * |
| * SUMMARY: Sends the PIP "Run Self Test" (0x26) command to the DUT |
| * to execute a FW built in self test |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * test_id - enumerated test ID to run |
| * write_idacs_to_flash - flag whether to write new IDACS to flash |
| * *status - pointer to store the read response status |
| * *summary_results - pointer to store the results summary |
| * *results_available - pointer to store if results are available |
| *****************************************************************************/ |
| static int pt_pip_run_selftest_( |
| struct pt_core_data *cd, u8 test_id, |
| u8 write_idacs_to_flash, u8 *status, u8 *summary_result, |
| u8 *results_available) |
| { |
| int rc = 0; |
| u8 write_buf[2]; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_RUN_SELF_TEST), |
| .write_length = 2, |
| .write_buf = write_buf, |
| .timeout_ms = PT_PIP1_CMD_RUN_SELF_TEST_TIMEOUT, |
| }; |
| |
| write_buf[0] = test_id; |
| write_buf[1] = write_idacs_to_flash; |
| |
| if (cd->active_dut_generation == DUT_PIP2_CAPABLE) |
| pip1_cmd.write_length = 1; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| if (status) |
| *status = cd->response_buf[5]; |
| |
| if (cd->protocol_mode != PT_PROTOCOL_MODE_HID) { |
| if (summary_result) |
| *summary_result = cd->response_buf[6]; |
| /* results_available only available before PIP 1.03 */ |
| if (cd->sysinfo.ready && !IS_PIP_VER_GE(&cd->sysinfo, 1, 3)) { |
| if (results_available) |
| *results_available = cd->response_buf[7]; |
| } |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_run_selftest |
| * |
| * SUMMARY: Protected call to pt_hid_output_run_selftest within |
| * an exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * test_id - enumerated test ID to run |
| * write_idacs_to_flash - flag whether to write new IDACS to flash |
| * *status - pointer to store the read response status |
| * *summary_results - pointer to store the results summary |
| * *results_available - pointer to store if results are available |
| ******************************************************************************/ |
| static int pt_pip_run_selftest( |
| struct pt_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, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_run_selftest_(cd, test_id, |
| write_idacs_to_flash, status, summary_result, |
| results_available); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_run_selftest |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_run_selftest |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * test_id - enumerated test ID to run |
| * write_idacs_to_flash - flag whether to write new IDACS to flash |
| * *status - pointer to store the read response status |
| * *summary_results - pointer to store the results summary |
| * *results_available - pointer to store if results are available |
| ******************************************************************************/ |
| static int _pt_request_pip_run_selftest(struct device *dev, |
| int protect, u8 test_id, u8 write_idacs_to_flash, u8 *status, |
| u8 *summary_result, u8 *results_available) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_run_selftest(cd, test_id, |
| write_idacs_to_flash, status, summary_result, |
| results_available); |
| |
| return pt_pip_run_selftest_(cd, test_id, |
| write_idacs_to_flash, status, summary_result, |
| results_available); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip_get_selftest_result_ |
| * |
| * SUMMARY: Sends the PIP "Get Self Test Results" (0x27) command to the DUT |
| * to retrieve the self test results from the self test already executed |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * read_offset - read pointer offset |
| * read_length - length of data to read |
| * test_id - enumerated test ID to read selftest results from |
| * *status - pointer to store the read response status |
| * *actual_read_len - pointer to store data length actually read |
| * *status - pointer to where the cmd response statas is stored |
| ******************************************************************************/ |
| static int pt_pip_get_selftest_result_( |
| struct pt_core_data *cd, u16 read_offset, u16 read_length, |
| u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) |
| { |
| int rc = 0; |
| u16 total_read_len = 0; |
| u16 rsp_len; |
| u16 read_len; |
| u16 off_buf = 0; |
| u8 write_buf[5]; |
| u8 read_test_id; |
| u8 pip3_exofs = 0; |
| bool repeat; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_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 != PT_ST_ID_AUTOSHORTS) |
| && cd->protocol_mode != PT_PROTOCOL_MODE_HID; |
| |
| 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; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| cd->force_pip3_report_type = true; |
| pip3_exofs = 1; |
| } |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| goto exit; |
| |
| if (cd->response_buf[5 - pip3_exofs] != PT_CMD_STATUS_SUCCESS) |
| goto set_status; |
| |
| read_test_id = cd->response_buf[6 - pip3_exofs]; |
| if (read_test_id != test_id) { |
| rc = -EPROTO; |
| goto exit; |
| } |
| |
| rsp_len = get_unaligned_le16(&cd->response_buf[0]); |
| read_len = get_unaligned_le16(&cd->response_buf[7]); |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Resp Len=0x%04X Actual Read Length=0x%04X\n", |
| __func__, rsp_len, read_len); |
| |
| /* Do not continue if the ARL is larger than the response */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| (read_len + 11) != rsp_len) { |
| /* |
| * For HID/PIP3 response, payload length is 11 bytes |
| * larger than ARL. |
| */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid Actual Read Length\n", __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| if (read_len && data) { |
| memcpy(&data[off_buf], |
| &cd->response_buf[10 - pip3_exofs], 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 - pip3_exofs]; |
| |
| exit: |
| cd->force_pip3_report_type = false; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip_get_selftest_result |
| * |
| * SUMMARY: Protected call to pt_hid_output_get_selftest_result by exclusive |
| * access to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * read_offset - read pointer offset |
| * read_length - length of data to read |
| * test_id - enumerated test ID to read selftest results from |
| * *status - pointer to store the read response status |
| * *actual_read_len - pointer to store data length actually read |
| * *status - pointer to where the cmd response statas is stored |
| ******************************************************************************/ |
| static int pt_pip_get_selftest_result( |
| struct pt_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, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_get_selftest_result_(cd, read_offset, |
| read_length, test_id, status, actual_read_len, data); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_get_selftest_result |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_get_selftest_result |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * read_offset - read pointer offset |
| * read_length - length of data to read |
| * test_id - enumerated test ID to read selftest results from |
| * *status - pointer to store the read response status |
| * *actual_read_len - pointer to store data length actually read |
| * *data - pointer to where the data read is stored |
| ******************************************************************************/ |
| static int _pt_request_pip_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 pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_get_selftest_result(cd, read_offset, |
| read_length, test_id, status, actual_read_len, |
| data); |
| |
| return pt_pip_get_selftest_result_(cd, read_offset, |
| read_length, test_id, status, actual_read_len, |
| data); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip_load_self_test_param |
| * |
| * SUMMARY: Sends the PIP "Load Self Test Parameters" (0x25) command to the DUT |
| * to load paramters needed by a self test |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * self_test_id - enumerated test ID for which the parmeters belong |
| * load_offset - mem offset to where to load parameters |
| * load_length - length of parameter data to load |
| * *parameters - pointer to list of parameter data |
| * *status - pointer to store the response status |
| * *ret_test_id - pointer to returned test id the paramters were stored |
| * *act_load_len - pointer to store the actual load length that was writen |
| ******************************************************************************/ |
| static int pt_pip_load_self_test_param_(struct pt_core_data *cd, |
| u8 self_test_id, u16 load_offset, u16 load_length, |
| u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len) |
| { |
| int rc = 0; |
| int i; |
| u8 write_buf[PT_MAX_PIP1_MSG_SIZE]; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_LOAD_SELF_TEST_PARAM), |
| .write_length = 5 + load_length, |
| .write_buf = write_buf, |
| .timeout_ms = PT_PIP1_CMD_DEFAULT_TIMEOUT, |
| }; |
| |
| write_buf[0] = LOW_BYTE(load_offset); |
| write_buf[1] = HI_BYTE(load_offset); |
| write_buf[2] = LOW_BYTE(load_length); |
| write_buf[3] = HI_BYTE(load_length); |
| write_buf[4] = self_test_id; |
| for (i = 0; i < load_length; i++) |
| write_buf[i + 5] = parameters[i]; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| if (status) |
| *status = cd->response_buf[5]; |
| if (ret_test_id) |
| *ret_test_id = cd->response_buf[6]; |
| if (act_load_len) |
| *act_load_len = get_unaligned_le16(&cd->response_buf[7]); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_load_self_test_param |
| * |
| * SUMMARY: Protected call to pt_pip_load_self_test_param_ within an exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * self_test_id - enumerated test ID for which the parmeters belong |
| * load_offset - mem offset to where to load parameters |
| * load_length - length of parameter data to load |
| * *parameters - pointer to list of parameter data |
| * *status - pointer to store the response status |
| * *ret_test_id - pointer to returned test id the paramters were stored |
| * *act_load_len - pointer to store the actual load length that was writen |
| ******************************************************************************/ |
| static int pt_pip_load_self_test_param(struct pt_core_data *cd, |
| u8 self_test_id, u16 load_offset, u16 load_length, |
| u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_load_self_test_param_(cd, self_test_id, load_offset, |
| load_length, parameters, status, ret_test_id, act_load_len); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_load_self_test_param |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip_load_self_test_param |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * self_test_id - enumerated test ID for which the parmeters belong |
| * load_offset - mem offset to where to load parameters |
| * load_length - length of parameter data to load |
| * *parameters - pointer to list of parameter data |
| * *status - pointer to store the response status |
| * *ret_test_id - pointer to returned test id the paramters were stored |
| * *act_load_len - pointer to store the actual load length that was writen |
| ******************************************************************************/ |
| static int _pt_request_pip_load_self_test_param(struct device *dev, |
| int protect, u8 self_test_id, u16 load_offset, u16 load_length, |
| u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_load_self_test_param(cd, self_test_id, |
| load_offset, load_length, parameters, status, ret_test_id, |
| act_load_len); |
| |
| return pt_pip_load_self_test_param_(cd, self_test_id, load_offset, |
| load_length, parameters, status, ret_test_id, act_load_len); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_calibrate_ext_ |
| * |
| * SUMMARY: Send the PIP1 Extended Calibrate command (0x30) to the DUT waiting |
| * for the response |
| * |
| * NOTE: This calibrate command requires the DUT to support PIP version >= 1.10 |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *cal_data - pointer to extended calibration data structure |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int pt_pip_calibrate_ext_(struct pt_core_data *cd, |
| struct pt_cal_ext_data *cal_data, u8 *status) |
| { |
| int rc = 0; |
| int write_length = 4; |
| u8 write_buf[4]; |
| u16 size = 0; |
| unsigned short crc = 0; |
| |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_CALIBRATE_DEVICE_EXTENDED), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| .timeout_ms = PT_PIP1_CMD_CALIBRATE_EXT_TIMEOUT, |
| }; |
| |
| if (cal_data == NULL) |
| return -EINVAL; |
| |
| memcpy(write_buf, cal_data, sizeof(struct pt_cal_ext_data)); |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| if (status) |
| *status = cd->response_buf[5]; |
| |
| /* |
| * When doing a calibration on a flashless DUT, save CAL data in |
| * the TTDL cache on any successful calibration |
| */ |
| if (*status == 0 && cd->cal_cache_in_host) { |
| pt_debug(cd->dev, DL_INFO, "%s: Retrieve and Save CAL\n", |
| __func__); |
| rc = _pt_manage_local_cal_data(cd->dev, PT_CAL_DATA_SAVE, |
| &size, &crc); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error Saving CAL rc=%d\n", __func__, rc); |
| else |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Saved CAL: chip ID=0x%04X size=%d\n", |
| __func__, crc, size); |
| } |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_calibrate_ext |
| * |
| * SUMMARY: Protected call to pt_pip_calibrate_ext_ by exclusive access to the |
| * DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *cal_data - pointer to extended calibration data structure |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int pt_pip_calibrate_ext(struct pt_core_data *cd, |
| struct pt_cal_ext_data *cal_data, u8 *status) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_calibrate_ext_(cd, cal_data, status); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_calibrate_ext |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_calibrate_ext |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * *cal_data - pointer to extended calibration data structure |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int _pt_request_pip_calibrate_ext(struct device *dev, |
| int protect, struct pt_cal_ext_data *cal_data, u8 *status) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_calibrate_ext(cd, cal_data, status); |
| |
| return pt_pip_calibrate_ext_(cd, cal_data, status); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_calibrate_idacs_ |
| * |
| * SUMMARY: Send the PIP Calibrate IDACs command (0x28) to the DUT waiting |
| * for the response |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * mode - sense mode to calibrate (0-5) |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int pt_pip_calibrate_idacs_(struct pt_core_data *cd, |
| u8 mode, u8 *status) |
| { |
| int rc = 0; |
| int write_length = 1; |
| u8 write_buf[1]; |
| u8 cmd_offset = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_CALIBRATE_IDACS), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| .timeout_ms = PT_PIP1_CMD_CALIBRATE_IDAC_TIMEOUT, |
| }; |
| |
| write_buf[cmd_offset++] = mode; |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| *status = cd->response_buf[5]; |
| if (*status) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip_calibrate_idacs |
| * |
| * SUMMARY: Protected call to pt_hid_output_calibrate_idacs_ by exclusive |
| * access to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * mode - sense mode to calibrate (0-5) |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int pt_pip_calibrate_idacs(struct pt_core_data *cd, |
| u8 mode, u8 *status) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip_calibrate_idacs_(cd, mode, status); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_calibrate_idacs |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to pt_pip_calibrate_idacs |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * mode - sense mode to calibrate (0-5) |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int _pt_request_pip_calibrate_idacs(struct device *dev, |
| int protect, u8 mode, u8 *status) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip_calibrate_idacs(cd, mode, status); |
| |
| return pt_pip_calibrate_idacs_(cd, mode, status); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_initialize_baselines_ |
| * |
| * SUMMARY: Send the PIP "Initialize Baselines" command (0x29) to the DUT |
| * waiting for the response. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * test_id - bit type flag to allow initialize baseline MUT,BTN,SELG |
| * each or together with a single command. |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int pt_hid_output_initialize_baselines_( |
| struct pt_core_data *cd, u8 test_id, u8 *status) |
| { |
| int rc = 0; |
| int write_length = 1; |
| u8 write_buf[1]; |
| u8 cmd_offset = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_INITIALIZE_BASELINES), |
| .write_length = write_length, |
| .write_buf = write_buf, |
| }; |
| |
| write_buf[cmd_offset++] = test_id; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| return rc; |
| |
| *status = cd->response_buf[5]; |
| if (*status) |
| return -EINVAL; |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_initialize_baselines |
| * |
| * SUMMARY: Protected call to pt_hid_output_initialize_baselines_ by exclusive |
| * access to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * test_id - enumerated ID against which to initialize the baseline |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int pt_hid_output_initialize_baselines(struct pt_core_data *cd, |
| u8 test_id, u8 *status) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_initialize_baselines_(cd, test_id, status); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_initialize_baselines |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip_initialize_baselines |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * test_id - enumerated ID against which to initialize the baseline |
| * *status - pointer to where the command response status is stored |
| ******************************************************************************/ |
| static int _pt_request_pip_initialize_baselines(struct device *dev, |
| int protect, u8 test_id, u8 *status) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_initialize_baselines(cd, test_id, |
| status); |
| |
| return pt_hid_output_initialize_baselines_(cd, test_id, status); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_exec_panel_scan_ |
| * |
| * SUMMARY: Sends the PIP "Execute Panel Scan" (0x2A) to the DUT and waits for |
| * the response |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_hid_output_exec_panel_scan_(struct pt_core_data *cd) |
| { |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_EXEC_PANEL_SCAN), |
| }; |
| |
| return pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_exec_panel_scan |
| * |
| * SUMMARY: Protected call to pt_hid_output_exec_panel_scan_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_hid_output_exec_panel_scan(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_exec_panel_scan_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_exec_panel_scan_ |
| * |
| * SUMMARY: Send the PIP2 "Execute Panel Scan" (0x21) to the DUT and waits for |
| * the response |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * scan_type - type of panel scan to perform (PIP2 only) |
| ******************************************************************************/ |
| static int pt_pip2_exec_panel_scan_(struct pt_core_data *cd, u8 scan_type) |
| { |
| int rc = 0; |
| u8 data[2]; |
| u8 read_buf[10]; |
| u16 actual_read_len; |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: PIP2 Execute Scan %d\n", |
| __func__, scan_type); |
| data[0] = scan_type; |
| rc = _pt_request_pip2_send_cmd(cd->dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_EXECUTE_SCAN, |
| data, 1, read_buf, &actual_read_len); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s EXECUTE_SCAN command for type %d failed. rc=%d\n", |
| __func__, scan_type, rc); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_exec_panel_scan |
| * |
| * SUMMARY: Protected call to pt_pip2_exec_panel_scan_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * scan_type - type of panel scan to perform (PIP2 only) |
| ******************************************************************************/ |
| static int pt_pip2_exec_panel_scan(struct pt_core_data *cd, u8 scan_type) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip2_exec_panel_scan_(cd, scan_type); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_exec_panel_scan |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip2_exec_panel_scan or pt_hid_output_exec_panel_scan |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * scan_type - type of panel scan to perform (PIP2 only) |
| ******************************************************************************/ |
| static int _pt_request_pip_exec_panel_scan(struct device *dev, |
| int protect, u8 scan_type) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (cd->sysinfo.ready && IS_PIP_VER_GE(&cd->sysinfo, 1, 12)) { |
| if (protect) |
| return pt_pip2_exec_panel_scan(cd, scan_type); |
| |
| return pt_pip2_exec_panel_scan_(cd, scan_type); |
| } |
| |
| if (protect) |
| return pt_hid_output_exec_panel_scan(cd); |
| |
| return pt_hid_output_exec_panel_scan_(cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_retrieve_panel_scan_ |
| * |
| * SUMMARY: Sends the PIP "Retrieve Panel Scan" (0x2B) command to the DUT |
| * to retrieve the specified data type for a the last successful Execute |
| * Panel Scan command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * read_offset - read pointer offset |
| * read_count - length of data to read |
| * data_id - enumerated test ID to read selftest results from |
| * *response - pointer to store the read response status |
| * *config - pointer to store config data |
| * *actual_read_len - pointer to store data length actually read |
| * *read_buf - pointer to the read buffer |
| ******************************************************************************/ |
| static int pt_hid_output_retrieve_panel_scan_( |
| struct pt_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 = 0; |
| int write_length = 5; |
| u8 write_buf[5]; |
| u8 cmd_offset = 0; |
| u8 data_elem_size; |
| u8 pip3_exofs = 0; |
| int size; |
| int data_size; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_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; |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| cd->force_pip3_report_type = true; |
| pip3_exofs = 1; |
| } |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| goto exit; |
| |
| status = cd->response_buf[5 - pip3_exofs]; |
| if (status) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| read_data_id = cd->response_buf[6 - pip3_exofs]; |
| if (read_data_id != data_id) { |
| rc = -EPROTO; |
| goto exit; |
| } |
| |
| 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; |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%s: %s=0x%04X %s=0x%04X %s=0x%02X %s=0x%04X\n", |
| __func__, "Resp Len", size, |
| "Actual Read Length", *actual_read_len, |
| "Element Size", data_elem_size, |
| "Calc Data Size", data_size); |
| |
| /* Do not continue if the ARL is larger than the response */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| (data_size + 12) != size) { |
| /* |
| * For HID/PIP3 response, command 0x2B payload length |
| * is 12 bytes larger than ARL * Element Size. |
| */ |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid Actual Read Length\n", __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| if (read_buf) |
| memcpy(read_buf, &cd->response_buf[10], data_size); |
| if (response) { |
| /* Reconstruct header based on PIP1 response */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| /* Remove 2 bytes CRC */ |
| size -= 2; |
| cd->response_buf[0] = LOW_BYTE(size); |
| cd->response_buf[1] = HI_BYTE(size); |
| cd->response_buf[2] = PT_PIP_NON_HID_RESPONSE_ID; |
| /* RSVD(0x00) */ |
| cd->response_buf[3] = 0x00; |
| /* PIP cmd id */ |
| cd->response_buf[4] = 0x2B; |
| /* Status */ |
| cd->response_buf[5] = 0; |
| /* Data ID */ |
| cd->response_buf[6] = read_data_id; |
| } |
| memcpy(response, cd->response_buf, size); |
| } |
| |
| exit: |
| cd->force_pip3_report_type = false; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_retrieve_panel_scan |
| * |
| * SUMMARY: Protected call to pt_hid_output_retrieve_panel_scan_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core_data structure |
| * read_offset - read pointer offset |
| * read_count - length of data to read |
| * data_id - enumerated test ID to read selftest results from |
| * *response - pointer to store the read response status |
| * *config - pointer to store config data |
| * *actual_read_len - pointer to store data length actually read |
| * *read_buf - pointer to the read buffer |
| ******************************************************************************/ |
| static int pt_hid_output_retrieve_panel_scan( |
| struct pt_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, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_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) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_retrieve_panel_scan |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_hid_output_retrieve_panel_scan |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * read_offset - read pointer offset |
| * read_count - length of data to read |
| * data_id - enumerated test ID to read selftest results from |
| * *response - pointer to store the read response status |
| * *config - pointer to store config data |
| * *actual_read_len - pointer to store data length actually read |
| * *read_buf - pointer to the read buffer |
| ******************************************************************************/ |
| static int _pt_request_pip_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 pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_retrieve_panel_scan(cd, |
| read_offset, read_count, data_id, response, |
| config, actual_read_len, read_buf); |
| |
| return pt_hid_output_retrieve_panel_scan_(cd, |
| read_offset, read_count, data_id, response, |
| config, actual_read_len, read_buf); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_user_cmd |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_hid_output_user_cmd |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * read_len - length of data to read |
| * *read_buf - pointer to store read data |
| * write_len - length of data to write |
| * *write_buf - pointer to buffer to write |
| * *actual_read_len - pointer to store data length actually read |
| ******************************************************************************/ |
| static int _pt_request_pip_user_cmd(struct device *dev, |
| int protect, u16 read_len, u8 *read_buf, u16 write_len, |
| u8 *write_buf, u16 *actual_read_len) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_user_cmd(cd, read_len, read_buf, |
| write_len, write_buf, actual_read_len); |
| |
| return pt_hid_output_user_cmd_(cd, read_len, read_buf, |
| write_len, write_buf, actual_read_len); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_get_information_ |
| * |
| * SUMMARY: Sends the PIP "Get Bootloader Information" (0x38) command to the |
| * DUT to retrieve bootloader version and chip identification information. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *return_data - pointer to store the return data |
| *****************************************************************************/ |
| static int pt_hid_output_bl_get_information_(struct pt_core_data *cd, |
| u8 *return_data) |
| { |
| int rc; |
| int data_len; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_GET_INFO), |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| 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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_get_information |
| * |
| * SUMMARY: Protected call to pt_hid_output_bl_get_information_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *return_data - pointer to store the return data |
| ******************************************************************************/ |
| static int pt_hid_output_bl_get_information(struct pt_core_data *cd, |
| u8 *return_data) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_bl_get_information_(cd, return_data); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_bl_get_information |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_hid_output_bl_get_information |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * *return_data - pointer to store bl data |
| ******************************************************************************/ |
| static int _pt_request_pip_bl_get_information(struct device *dev, |
| int protect, u8 *return_data) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_bl_get_information(cd, return_data); |
| |
| return pt_hid_output_bl_get_information_(cd, return_data); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_initiate_bl_ |
| * |
| * SUMMARY: Sends the PIP "Initiate Bootload" (0x48) command to the |
| * DUT to erases the entire TrueTouch application, Configuration Data block, |
| * and Design Data block in flash and enables the host to execute the Program |
| * and Verify Row command to bootload the application image and data. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * protect - flag to call protected or non-protected |
| * key_size - size of key |
| * *key_buf - pointer to key data to allow operation |
| * row_size - size of the meta data row |
| * *metadata_row_buf - pointer to meta data to write |
| ******************************************************************************/ |
| static int pt_hid_output_bl_initiate_bl_(struct pt_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 = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_INITIATE_BL), |
| .write_length = write_length, |
| .timeout_ms = PT_PIP1_CMD_INITIATE_BL_TIMEOUT, |
| }; |
| |
| write_buf = kzalloc(write_length, GFP_KERNEL); |
| if (!write_buf) |
| return -ENOMEM; |
| |
| pip1_cmd.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 = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| |
| kfree(write_buf); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_initiate_bl |
| * |
| * SUMMARY: Protected call to pt_hid_output_bl_initiate_bl_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * key_size - size of key |
| * *key_buf - pointer to key data to allow operation |
| * row_size - size of the meta data row |
| * *metadata_row_buf - pointer to meta data to write |
| ******************************************************************************/ |
| static int pt_hid_output_bl_initiate_bl(struct pt_core_data *cd, |
| u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_bl_initiate_bl_(cd, key_size, key_buf, |
| row_size, metadata_row_buf); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_bl_initiate_bl |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmd struct for external |
| * calls to the protected or unprotected call to |
| * pt_hid_output_bl_initiate_bl |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * key_size - size of key |
| * *key_buf - pointer to key data to allow operation |
| * row_size - size of the meta data row |
| * *metadata_row_buf - pointer to meta data to write |
| ******************************************************************************/ |
| static int _pt_request_pip_bl_initiate_bl(struct device *dev, |
| int protect, u16 key_size, u8 *key_buf, u16 row_size, |
| u8 *metadata_row_buf) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_bl_initiate_bl(cd, key_size, key_buf, |
| row_size, metadata_row_buf); |
| |
| return pt_hid_output_bl_initiate_bl_(cd, key_size, key_buf, |
| row_size, metadata_row_buf); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_program_and_verify_ |
| * |
| * SUMMARY: Sends the PIP "Program and Verify" (0x39) command to upload |
| * and program a 128-byte row into the flash, and then verifies written data. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * data_len - length of data_buf |
| * *data_buf - firmware image to program |
| ******************************************************************************/ |
| static int pt_hid_output_bl_program_and_verify_( |
| struct pt_core_data *cd, u16 data_len, u8 *data_buf) |
| { |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_PROGRAM_AND_VERIFY), |
| .write_length = data_len, |
| .write_buf = data_buf, |
| .timeout_ms = PT_PIP1_CMD_PROGRAM_AND_VERIFY_TIMEOUT, |
| }; |
| |
| return pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_program_and_verify |
| * |
| * SUMMARY: Protected call to pt_hid_output_bl_program_and_verify_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * data_len - length of data_buf |
| * *data_buf - firmware image to program |
| ******************************************************************************/ |
| static int pt_hid_output_bl_program_and_verify( |
| struct pt_core_data *cd, u16 data_len, u8 *data_buf) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_bl_program_and_verify_(cd, data_len, data_buf); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_bl_program_and_verify |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules |
| * to request to have the BL program and verify a FW image |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - boolean to determine to call the protected function |
| * data_len - length of data_buf |
| * *data_buf - firmware image to program |
| ******************************************************************************/ |
| static int _pt_request_pip_bl_program_and_verify( |
| struct device *dev, int protect, u16 data_len, u8 *data_buf) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_bl_program_and_verify(cd, data_len, |
| data_buf); |
| |
| return pt_hid_output_bl_program_and_verify_(cd, data_len, |
| data_buf); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_verify_app_integrity_ |
| * |
| * SUMMARY: Sends the PIP "Verify Application Integrity" (0x31) command to |
| * perform a full verification of the application integrity by calculating the |
| * CRC of the image in flash and compare it to the expected CRC stored in the |
| * Metadata row. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *result - pointer to store result |
| ******************************************************************************/ |
| static int pt_hid_output_bl_verify_app_integrity_( |
| struct pt_core_data *cd, u8 *result) |
| { |
| int rc; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_VERIFY_APP_INTEGRITY), |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) { |
| *result = 0; |
| return rc; |
| } |
| |
| *result = cd->response_buf[8]; |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_verify_app_integrity |
| * |
| * SUMMARY: Protected call to pt_hid_output_bl_verify_app_integrity_ by |
| * exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *result - pointer to store result |
| ******************************************************************************/ |
| static int pt_hid_output_bl_verify_app_integrity( |
| struct pt_core_data *cd, u8 *result) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_bl_verify_app_integrity_(cd, result); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_bl_verify_app_integrity |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules |
| * to request to have the BL verify the application integrity (PIP1.x only) |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - boolean to determine to call the protected function |
| * *result - pointer to store result |
| ******************************************************************************/ |
| static int _pt_request_pip_bl_verify_app_integrity( |
| struct device *dev, int protect, u8 *result) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_bl_verify_app_integrity(cd, result); |
| |
| return pt_hid_output_bl_verify_app_integrity_(cd, result); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_launch_app_ |
| * |
| * SUMMARY: Sends the PIP "Launch Application" (0x3B) command to launch the |
| * application from bootloader (PIP1.x only). |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_hid_output_bl_launch_app_(struct pt_core_data *cd) |
| { |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_LAUNCH_APP), |
| .reset_expected = 1, |
| }; |
| |
| return pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_launch_app |
| * |
| * SUMMARY: Protected call to pt_hid_output_bl_launch_app_ by exclusive access |
| * to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_hid_output_bl_launch_app(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_bl_launch_app_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_launch_app |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules |
| * to request to have the BL launch the application. (PIP1.x only) |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - boolean to determine to call the protected function |
| ******************************************************************************/ |
| static int _pt_request_pip_launch_app(struct device *dev, |
| int protect) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_bl_launch_app(cd); |
| |
| return pt_hid_output_bl_launch_app_(cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_get_panel_id_ |
| * |
| * SUMMARY: Sends the PIP "Get Panel ID" (0x3E) command to return the Panel ID |
| * value store in the System Information. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *panel_id - pointer to where the panel ID will be stored |
| ******************************************************************************/ |
| static int pt_hid_output_bl_get_panel_id_( |
| struct pt_core_data *cd, u8 *panel_id) |
| { |
| int rc; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_GET_PANEL_ID), |
| }; |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc == -EPROTO && cd->response_buf[5] == ERROR_COMMAND) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Get Panel ID command not supported\n", |
| __func__); |
| *panel_id = PANEL_ID_NOT_ENABLED; |
| return 0; |
| } else if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on Get Panel ID command\n", __func__); |
| return rc; |
| } |
| |
| *panel_id = cd->response_buf[8]; |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hid_output_bl_get_panel_id |
| * |
| * SUMMARY: Protected call to pt_hid_output_bl_get_panel_id_ by exclusive access |
| * to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *panel_id - pointer to where the panel ID will be stored |
| ******************************************************************************/ |
| static int pt_hid_output_bl_get_panel_id( |
| struct pt_core_data *cd, u8 *panel_id) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_hid_output_bl_get_panel_id_(cd, panel_id); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip_bl_get_panel_id |
| * |
| * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules |
| * to have the BL retrieve the panel ID |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to run in protected mode |
| * *panel_id - pointer to where the panel ID will be stored |
| ******************************************************************************/ |
| static int _pt_request_pip_bl_get_panel_id( |
| struct device *dev, int protect, u8 *panel_id) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_hid_output_bl_get_panel_id(cd, panel_id); |
| |
| return pt_hid_output_bl_get_panel_id_(cd, panel_id); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_status_ |
| * |
| * SUMMARY: Sends a PIP2 STATUS command to the DUT and stores the data in |
| * cd status_data. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip2_get_status_(struct pt_core_data *cd) |
| { |
| u16 actual_read_len; |
| u8 read_buf[12]; |
| u8 status, boot; |
| int rc = 0; |
| |
| rc = _pt_request_pip2_send_cmd(cd->dev, PT_CORE_CMD_UNPROTECTED, |
| PIP2_CMD_ID_STATUS, NULL, 0, read_buf, &actual_read_len); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, "%s: PIP2 STATUS command rc = %d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| pt_pr_buf(cd->dev, DL_DEBUG, read_buf, actual_read_len, |
| "PIP2 STATUS"); |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (status == PIP2_RSP_ERR_INIT_FAILURE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP2 Initialization Failure, FW stuck in BOOTUP SysMode. status code = %d\n", |
| __func__, status); |
| rc = status; |
| } |
| |
| boot = read_buf[PIP2_RESP_BODY_OFFSET] & 0x01; |
| cd->dut_status.fw_system_mode = |
| read_buf[PIP2_RESP_BODY_OFFSET + 1]; |
| |
| if (status == PIP2_RSP_ERR_NONE && boot == 0x00) |
| cd->dut_status.mode = PT_MODE_BOOTLOADER; |
| else if ((status == PIP2_RSP_ERR_NONE || |
| status == PIP2_RSP_ERR_INIT_FAILURE) && |
| boot == 0x01) { |
| cd->dut_status.mode = PT_MODE_OPERATIONAL; |
| /* protocol mode need a mask: 0x07 */ |
| cd->dut_status.protocol_mode = |
| read_buf[PIP2_RESP_BODY_OFFSET + 2] & 0x07; |
| if (cd->dut_status.fw_system_mode == |
| FW_SYS_MODE_SECONDARY_IMAGE) |
| cd->dut_status.mode = PT_MODE_SECONDARY_IMAGE; |
| } else |
| cd->dut_status.mode = PT_MODE_UNKNOWN; |
| |
| if (cd->set_protocol_mode) { |
| if (cd->dut_status.protocol_mode != cd->protocol_mode) |
| pt_debug(cd->dev, DL_WARN, |
| "%s: ATM - protocol mode mismatch! dut_status.protocol_mode=%d cd->protocol_mode=%d\n", |
| __func__, cd->dut_status.protocol_mode, |
| cd->protocol_mode); |
| } else |
| cd->protocol_mode = cd->dut_status.protocol_mode; |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_mode_sysmode_ |
| * |
| * SUMMARY: Determine the current mode and system mode of the DUT by use of the |
| * PIP2 STATUS command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *mode - pointer to store the retrieved mode |
| * *sys_mode - pointer to store the FW system mode |
| ******************************************************************************/ |
| static int pt_pip2_get_mode_sysmode_(struct pt_core_data *cd, |
| u8 *mode, u8 *sys_mode) |
| { |
| int rc = 0; |
| |
| rc = pt_pip2_get_status_(cd); |
| if (!rc || (rc == PIP2_RSP_ERR_INIT_FAILURE)) { |
| if (sys_mode) |
| *sys_mode = cd->dut_status.fw_system_mode; |
| if (mode) |
| *mode = cd->dut_status.mode; |
| } else { |
| if (mode) |
| *mode = PT_MODE_UNKNOWN; |
| if (sys_mode) |
| *sys_mode = FW_SYS_MODE_UNDEFINED; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Mode and sys_mode could not be determined\n", |
| __func__); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_mode_sysmode |
| * |
| * SUMMARY: Protected call to pt_pip2_get_mode_sysmode_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *mode - pointer to store the retrieved mode |
| * *sys_mode - pointer to store the FW system mode |
| ******************************************************************************/ |
| static int pt_pip2_get_mode_sysmode(struct pt_core_data *cd, |
| u8 *mode, u8 *sys_mode) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip2_get_mode_sysmode_(cd, mode, sys_mode); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip2_get_mode_sysmode |
| * |
| * SUMMARY: Function pointer included in core_commands struct for external |
| * calls to the protected or unprotected call to |
| * pt_pip2_get_mode_sysmode |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *mode - pointer to store the retrieved mode |
| * *sys_mode - pointer to store the FW system mode |
| ******************************************************************************/ |
| static int _pt_request_pip2_get_mode_sysmode(struct device *dev, |
| int protect, u8 *mode, u8 *sys_mode) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_pip2_get_mode_sysmode(cd, mode, sys_mode); |
| |
| return pt_pip2_get_mode_sysmode_(cd, mode, sys_mode); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_poll_for_fw_exit_boot_mode |
| * |
| * SUMMARY: Verify and or poll for the FW to exit BOOT mode. During the FW BOOT |
| * mode only the following PIP commands will be serviced, any other PIP |
| * command the FW will respond with an "Invalid PIP Command" response. |
| * - Get HID Descriptor (Register 0x0001, no command ID) |
| * - Reset (Register 0x0005, RESET HID request) |
| * - Ping (Register 0x0004, Command ID 0x00 |
| * - Get System Information (Register 0x0004, Command ID 0x02) |
| * - PIP2 Status (Register 0x0101, Command ID 0x01) |
| * - PIP2 Version (Register 0x0101, Command ID 0x07) |
| * This function will loop on the results of the STATUS command until |
| * the FW reports it is out of BOOT mode. |
| * |
| * NOTE: |
| * - This function will update cd->fw_system_mode |
| * - The STATUS cmd only supports this functionality for PIP 1.11+ |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * timeout - max time (ms) to wait for FW to exit BOOT mode |
| * actual_wait - pointer to actual time waited for FW to exit BOOT mode |
| ******************************************************************************/ |
| static int _pt_poll_for_fw_exit_boot_mode(struct pt_core_data *cd, int timeout, |
| int *actual_wait) |
| { |
| int loop = 0; |
| u8 sys_mode = cd->fw_system_mode; |
| u8 pause = 10; /* in ms */ |
| int rc = 0; |
| int max_loop = (timeout / pause) + 1; /* Add 1 due to int math */ |
| |
| if (cd->sysinfo.ready && !IS_PIP_VER_GE(&cd->sysinfo, 1, 11)) { |
| /* |
| * For PIP <1.11, no support for polling wait so do a hard |
| * coded wait and assume the FW is out of BOOT. Added 1 to |
| * timeout to make it clear in kmsg if non polling was done. |
| */ |
| *actual_wait = PT_FW_EXIT_BOOT_MODE_TIMEOUT + 1; |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP %d.%d no support for ext STATUS, sleep %d\n", |
| __func__, |
| cd->sysinfo.ttdata.pip_ver_major, |
| cd->sysinfo.ttdata.pip_ver_minor, *actual_wait); |
| msleep(*actual_wait); |
| sys_mode = FW_SYS_MODE_SCANNING; |
| } |
| |
| if (sys_mode == FW_SYS_MODE_BOOT) { |
| while (!rc && loop <= max_loop && |
| (sys_mode == FW_SYS_MODE_BOOT)) { |
| loop++; |
| usleep_range(9000, pause * 1000); |
| rc = pt_pip2_get_mode_sysmode_(cd, NULL, &sys_mode); |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: FW in BOOT mode-sleep %dms, sys_mode=%d\n", |
| __func__, loop * pause, sys_mode); |
| } |
| *actual_wait = (int)(loop * pause); |
| pt_debug(cd->dev, DL_WARN, |
| "%s: FW exited BOOT mode in %dms, sys_mode=%d\n", |
| __func__, *actual_wait, sys_mode); |
| |
| if (rc) |
| sys_mode = FW_SYS_MODE_UNDEFINED; |
| else if (sys_mode == FW_SYS_MODE_BOOT || |
| sys_mode == FW_SYS_MODE_UNDEFINED) |
| rc = -EBUSY; |
| } |
| |
| mutex_lock(&cd->system_lock); |
| cd->fw_system_mode = sys_mode; |
| mutex_unlock(&cd->system_lock); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_poll_for_fw_exit_boot_mode |
| * |
| * SUMMARY: Protected call to _pt_poll_for_fw_exit_boot_mode by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * timeout - max time (ms) to wait for FW to exit BOOT mode |
| * actual_wait - pointer to actual time waited for FW to exit BOOT mode |
| ******************************************************************************/ |
| static int pt_poll_for_fw_exit_boot_mode(struct pt_core_data *cd, int timeout, |
| int *actual_wait) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = _pt_poll_for_fw_exit_boot_mode(cd, timeout, actual_wait); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_get_fw_sys_mode |
| * |
| * SUMMARY: Determine the FW system mode. For PIP 1.11+ the |
| * PIP2 STATUS command is used to directly query the FW system mode. For older |
| * PIP versions, there is no direct PIP commamnd that will directly provide this |
| * information but any PIP command above 0x1F requires scanning to be disabled |
| * before it will be operational. If scanning was not disabled before sending |
| * these PIP commands the FW will respond with a 6 byte error response. So to |
| * safely determine the scanning state, a PIP message that will not affect the |
| * operation of the FW was chosen: |
| * "Verify Data Block CRC (ID 0x20)" is sent and if a 6 byte error code is |
| * received scanning is active. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *sys_mode - pointer to FW System mode |
| * *mode - pointer to mode (BL/FW) |
| ******************************************************************************/ |
| static int _pt_get_fw_sys_mode(struct pt_core_data *cd, u8 *sys_mode, u8 *mode) |
| { |
| int write_length = 1; |
| int report_length; |
| int rc = 0; |
| u8 tmp_sys_mode = FW_SYS_MODE_UNDEFINED; |
| u8 tmp_mode = PT_MODE_UNKNOWN; |
| u8 param[1] = { PT_TCH_PARM_EBID }; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC), |
| .write_length = write_length, |
| .write_buf = param, |
| .novalidate = true, |
| }; |
| |
| /* AFter PIP1.11 the preferred method is using STATUS cmd */ |
| if (IS_PIP_VER_GE(&cd->sysinfo, 1, 11)) { |
| rc = pt_pip2_get_mode_sysmode_(cd, &tmp_mode, &tmp_sys_mode); |
| pt_debug(cd->dev, DL_DEBUG, "%s: tmp_sys_mode=%d tmp_mode=%d\n", |
| __func__, tmp_sys_mode, tmp_mode); |
| if (!rc) { |
| if (tmp_mode != PT_MODE_OPERATIONAL) |
| tmp_sys_mode = FW_SYS_MODE_UNDEFINED; |
| } |
| goto exit; |
| } |
| |
| /* Older systems use PIP1 CONFIG_BLOCK_CRC to best determine sys_mode */ |
| if (cd->mode != PT_MODE_OPERATIONAL) { |
| tmp_mode = cd->mode; |
| goto exit; |
| } |
| |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (rc) |
| goto exit; |
| |
| report_length = (cd->response_buf[1] << 8) | (cd->response_buf[0]); |
| if ((report_length == 0x06) && |
| ((cd->response_buf[4] & PIP1_RESP_COMMAND_ID_MASK) == 0x00) && |
| (cd->response_buf[5] == PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC)) { |
| tmp_mode = PIP2_STATUS_APP_EXEC; |
| tmp_sys_mode = FW_SYS_MODE_SCANNING; |
| } else if ((report_length == 0x0A) && |
| ((cd->response_buf[4] & PIP1_RESP_COMMAND_ID_MASK) == |
| PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC)) { |
| tmp_mode = PIP2_STATUS_APP_EXEC; |
| tmp_sys_mode = FW_SYS_MODE_TEST; |
| } |
| |
| exit: |
| if (mode) |
| *mode = tmp_mode; |
| if (sys_mode) |
| *sys_mode = tmp_sys_mode; |
| pt_debug(cd->dev, DL_INFO, "%s: Return Mode=%d sys_mode=%d\n", |
| __func__, tmp_mode, tmp_sys_mode); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_fw_sys_mode |
| * |
| * SUMMARY: Protected call to _pt_get_fw_sys_mode() to determine if FW scanning |
| * is active or not. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *sys_mode - pointer to fw system mode |
| * *mode - pointer to mode |
| ******************************************************************************/ |
| static int pt_get_fw_sys_mode(struct pt_core_data *cd, u8 *sys_mode, u8 *mode) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = _pt_get_fw_sys_mode(cd, sys_mode, mode); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_get_fw_sys_mode |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request to get scan state |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| * *sys_mode - pointer to FW system mode |
| * *mode - pointer to mode |
| ******************************************************************************/ |
| static int _pt_request_get_fw_sys_mode(struct device *dev, int protect, |
| u8 *sys_mode, u8 *mode) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (protect) |
| return pt_get_fw_sys_mode(cd, sys_mode, mode); |
| |
| return _pt_get_fw_sys_mode(cd, sys_mode, mode); |
| } |
| |
| /* Default hid descriptor to provide basic register map */ |
| const struct pt_hid_desc hid_desc_default = { |
| 230, /* hid_desc_len */ |
| HID_APP_REPORT_ID, /* packet_id */ |
| 0x00, /* reserved_byte */ |
| 0x0100, /* bcd_version */ |
| 0x00EC, /* report_desc_len */ |
| 0x0002, /* report_desc_register */ |
| 0x0003, /* input_register */ |
| 0x00FE, /* max_input_len */ |
| 0x0004, /* output_register */ |
| 0x00FF, /* max_output_len */ |
| 0x0005, /* command_register */ |
| 0x0006, /* data_register */ |
| 0x04B4, /* vendor_id */ |
| 0xC101, /* product_id */ |
| 0x0100, /* version_id */ |
| {0x00, 0x00, 0x00, 0x00} /* reserved[4] */ |
| }; |
| |
| /******************************************************************************* |
| * FUNCTION: pt_init_hid_descriptor |
| * |
| * SUMMARY: Setup default values for HID descriptor structure |
| * |
| * |
| * PARAMETERS: |
| * *desc - pointer to the HID descriptor data read back from DUT |
| ******************************************************************************/ |
| static inline void pt_init_hid_descriptor(struct pt_hid_desc *desc) |
| { |
| memcpy(desc, &hid_desc_default, sizeof(hid_desc_default)); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_hid_descriptor_ |
| * |
| * SUMMARY: Send the get HID descriptor command to the DUT and load the response |
| * into the HID descriptor structure |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *desc - pointer to the HID descriptor data read back from DUT |
| ******************************************************************************/ |
| static int pt_get_hid_descriptor_(struct pt_core_data *cd, |
| struct pt_hid_desc *desc) |
| { |
| struct device *dev = cd->dev; |
| struct pt_hid_cmd hid_cmd = { |
| .descriptor = cd->hid_core.hid_desc_register, |
| .read_length = 0, |
| }; |
| int rc = 0; |
| u16 hid_len; |
| |
| /* |
| * During startup the HID descriptor is required for all future |
| * processing. If IRQ is already asserted due to an early touch report |
| * the report must be cleared before sending command. |
| */ |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| rc = pt_hid_send_command_(cd, &hid_cmd); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: failed to get HID descriptor, rc=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| hid_len = get_unaligned_le16(&cd->response_buf[0]); |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->response_buf, hid_len, "<<< HIDDesc"); |
| |
| /* |
| * HID doesn't have packet_id and reserve_byte in struct struct |
| * pt_hid_desc and assign fixed value to packet_id. |
| */ |
| if (cd->protocol_mode != PT_PROTOCOL_MODE_PIP) { |
| if ((hid_len + 2) != sizeof(*desc)) { |
| pt_debug(dev, DL_ERROR, "%s: Unsupported HID length: %X\n", |
| __func__, hid_len); |
| rc = -ENODEV; |
| goto exit; |
| } |
| /* Copy length field */ |
| memcpy((u8 *)desc, &cd->response_buf[0], 2); |
| /* Assign fixed value to packet_id */ |
| desc->packet_id = HID_APP_REPORT_ID; |
| /* Skip 2 bytes in desc and Copy to left fields */ |
| memcpy((u8 *)desc + 4, &cd->response_buf[2], hid_len - 2); |
| } else { |
| if (hid_len != sizeof(*desc)) { |
| pt_debug(dev, DL_ERROR, "%s: Unsupported HID length: %X\n", |
| __func__, hid_len); |
| rc = -ENODEV; |
| goto exit; |
| } |
| |
| /* Load the HID descriptor including all registers */ |
| memcpy((u8 *)desc, cd->response_buf, hid_len); |
| } |
| |
| /* Check HID descriptor length and version */ |
| pt_debug(dev, DL_INFO, "%s: HID len:%X HID ver:%X\n", __func__, |
| le16_to_cpu(desc->hid_desc_len), |
| le16_to_cpu(desc->bcd_version)); |
| |
| cd->hid_core.hid_report_desc_len = |
| le16_to_cpu(desc->report_desc_len); |
| |
| cd->hid_core.hid_max_input_len = |
| le16_to_cpu(desc->max_input_len); |
| |
| cd->hid_core.hid_max_output_len = |
| le16_to_cpu(desc->max_output_len); |
| |
| cd->hid_core.hid_protocol_ver = |
| (desc->version_id & 0xff00) >> 8; |
| |
| pt_debug(dev, DL_INFO, |
| "%s: report desc len:%d, max_input_len:%d, max_output_len:%d\n", |
| __func__, |
| cd->hid_core.hid_report_desc_len, |
| cd->hid_core.hid_max_input_len, |
| cd->hid_core.hid_max_output_len); |
| |
| if (le16_to_cpu(desc->bcd_version) != HID_VERSION) { |
| pt_debug(dev, DL_ERROR, "%s: Unsupported HID version\n", |
| __func__); |
| rc = -ENODEV; |
| goto exit; |
| } |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, cd->response_buf, hid_len, "HIDDesc="); |
| #endif |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_hid_descriptor |
| * |
| * SUMMARY: Protected call to pt_get_hid_descriptor_() |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *desc - pointer to the HID descriptor data read back from DUT |
| ******************************************************************************/ |
| static int pt_get_hid_descriptor(struct pt_core_data *cd, |
| struct pt_hid_desc *desc) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_get_hid_descriptor_(cd, desc); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_version_ |
| * |
| * SUMMARY: Sends a PIP2 VERSION command to the DUT and stores the data in |
| * cd-ttdata |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_pip2_get_version_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| int status; |
| u8 read_buf[64]; |
| u16 actual_read_len; |
| |
| rc = _pt_request_pip2_send_cmd(cd->dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_VERSION, |
| NULL, 0, read_buf, &actual_read_len); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error Sending PIP2 VERSION Cmd rc=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (status == 0) { |
| /* Parse the PIP2 VERSION response into ttdata */ |
| pt_pip2_ver_load_ttdata(cd, actual_read_len); |
| } else { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error in PIP2 VERSION Cmd status=%d\n", |
| __func__, status); |
| return status; |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_get_version |
| * |
| * SUMMARY: Protected call to pt_pip2_get_version_ by exclusive |
| * access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_pip2_get_version(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip2_get_version_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_active_pip_protocol |
| * |
| * SUMMARY: Get active PIP protocol version using the PIP2 version command. |
| * Function will return PIP version of BL or application based on |
| * when it's called. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to run in protected mode |
| * *pip_version_major - pointer to store PIP major version |
| * *pip_version_minor - pointer to store PIP minor version |
| ******************************************************************************/ |
| int _pt_request_active_pip_protocol(struct device *dev, int protect, |
| u8 *pip_version_major, u8 *pip_version_minor) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| int rc = 0; |
| struct pt_pip1_cmd pip1_cmd = { |
| CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_SYSINFO), |
| .timeout_ms = PT_PIP1_CMD_GET_SYSINFO_TIMEOUT, |
| }; |
| |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| /* Skip PIP2 command if DUT generation is confirmed */ |
| if (cd->active_dut_generation == DUT_PIP1_ONLY) |
| goto skip_pip2_command; |
| |
| rc = pt_pip2_get_version_(cd); |
| if (!rc) { |
| *pip_version_major = ttdata->pip_ver_major; |
| *pip_version_minor = ttdata->pip_ver_minor; |
| pt_debug(dev, DL_INFO, |
| "%s: pip_version = %d.%d\n", __func__, |
| *pip_version_major, *pip_version_minor); |
| } else { |
| /* |
| * Legacy products do not support the pip2 protocol to get |
| * pip version. However, they do support the "get sysinfo" |
| * command to get pip version from FW, but the bootloader |
| * does not support it. This function will try "get sysinfo" |
| * command if the pip2 command failed but this cmd could also |
| * fail if DUT is stuck in bootloader mode. |
| */ |
| pt_debug(dev, DL_INFO, |
| "%s: PIP2 no response rc = %d, try legacy cmd\n", |
| __func__, rc); |
| |
| skip_pip2_command: |
| rc = pt_pip1_send_and_wait_(cd, &pip1_cmd); |
| if (!rc) { |
| *pip_version_minor = |
| cd->response_buf[PIP1_SYSINFO_TTDATA_OFFSET + 1]; |
| *pip_version_major = |
| cd->response_buf[PIP1_SYSINFO_TTDATA_OFFSET]; |
| |
| pt_debug(dev, DL_INFO, |
| "%s: pip_version = %d.%d\n", __func__, |
| *pip_version_major, *pip_version_minor); |
| } else { |
| *pip_version_major = 0; |
| *pip_version_minor = 0; |
| pt_debug(dev, DL_ERROR, |
| "%s: pip_version Not Detected\n", __func__); |
| } |
| } |
| |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(_pt_request_active_pip_protocol); |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_detect_dut_by_pip1 |
| * |
| * SUMMARY: Detect DUT by PIP1 HID_DESC command. |
| * Packet_ID in BL = HID_BL_REPORT_ID (0xFF) |
| * Packet_ID in FW = HID_APP_REPORT_ID (0xF7) |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *status - pointer to status bitmask |
| * *dut_gen - pointer to store the dut_generation |
| * *mode - pointer to store the pt_mode |
| * *prot_mode - pointer to store the protocol_mode |
| ******************************************************************************/ |
| static int _pt_detect_dut_by_pip1(struct device *dev, |
| u32 *enum_status, u8 *dut_gen, enum pt_mode *mode, |
| u8 *prot_mode) |
| { |
| int rc = 0; |
| u8 index = 0; |
| u8 dut_gen_tmp = DUT_UNKNOWN; |
| u8 mode_tmp = PT_MODE_UNKNOWN; |
| u8 prot_mode_tmp = PT_PROTOCOL_MODE_UNKNOWN; |
| u8 write_buf[2]; |
| u8 read_buf[PT_MAX_PIP1_MSG_SIZE]; |
| u16 actual_read_len; |
| u32 enum_status_tmp = ENUM_STATUS_START; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| /* Flush Bus to clear pending report */ |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| /* Suppose to be PIP1 BL/Operation and detect it by report decriptor. */ |
| memset(write_buf, 0, ARRAY_SIZE(write_buf)); |
| memset(read_buf, 0, ARRAY_SIZE(read_buf)); |
| write_buf[index++] = 0x01; |
| write_buf[index++] = 0x00; |
| rc = pt_hid_output_user_cmd_(cd, PT_MAX_PIP1_MSG_SIZE, read_buf, index, |
| write_buf, &actual_read_len); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to get PIP1 hid descriptor, rc=%d\n", |
| __func__, rc); |
| /* Flush Bus to clear pending report */ |
| pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, NULL); |
| goto exit; |
| } |
| |
| if (read_buf[PIP1_RESP_REPORT_ID_OFFSET] == HID_BL_REPORT_ID) { |
| dut_gen_tmp = DUT_PIP1_ONLY; /* Gen5/6 BL */ |
| mode_tmp = PT_MODE_BOOTLOADER; |
| prot_mode_tmp = PT_PROTOCOL_MODE_PIP; |
| enum_status_tmp = ENUM_STATUS_BL_RESET_SENTINEL; |
| } else if (read_buf[PIP1_RESP_REPORT_ID_OFFSET] == |
| HID_APP_REPORT_ID) { |
| dut_gen_tmp = DUT_PIP1_ONLY; /* Gen5/6 FW */ |
| mode_tmp = PT_MODE_OPERATIONAL; |
| prot_mode_tmp = PT_PROTOCOL_MODE_PIP; |
| enum_status_tmp = ENUM_STATUS_FW_RESET_SENTINEL; |
| } else { |
| pt_debug(dev, DL_INFO, "%s:Invalid report ID=%d\n", __func__, |
| read_buf[PIP1_RESP_REPORT_ID_OFFSET]); |
| rc = -EINVAL; |
| } |
| |
| exit: |
| mutex_lock(&cd->system_lock); |
| if (dut_gen) |
| *dut_gen = dut_gen_tmp; |
| if (mode) |
| *mode = mode_tmp; |
| if (prot_mode) |
| *prot_mode = prot_mode_tmp; |
| if (enum_status) |
| *enum_status = enum_status_tmp; |
| mutex_unlock(&cd->system_lock); |
| |
| #ifdef TTDL_DIAGNOSTICS |
| pt_debug(dev, DL_INFO, "%s: Generation=%d Mode=%d Prot_mode=%d\n", |
| __func__, dut_gen_tmp, mode_tmp, prot_mode_tmp); |
| #endif /* TTDL_DIAGNOSTICS */ |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_detect_dut_by_pip2 |
| * |
| * SUMMARY: Detect DUT by use of the PIP2 STATUS command. If the DUT responds |
| * correctly the DUT generation is known however the protocol mode is only |
| * known if the FW application responds because the ROM is shared with DUTs |
| * that use different application protocols. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *status - pointer to status bitmask |
| * *dut_gen - pointer to store the dut_generation |
| * *mode - pointer to store the pt_mode |
| * *prot_mode - pointer to store the protocol_mode |
| ******************************************************************************/ |
| static int _pt_detect_dut_by_pip2(struct device *dev, |
| u32 *enum_status, u8 *dut_gen, enum pt_mode *mode, |
| u8 *prot_mode) |
| { |
| int rc = 0; |
| u8 index = 0; |
| u8 dut_gen_tmp = DUT_UNKNOWN; |
| u8 mode_tmp = PT_MODE_UNKNOWN; |
| u8 prot_mode_tmp = PT_PROTOCOL_MODE_UNKNOWN; |
| u8 write_buf[8]; |
| u8 read_buf[PT_MAX_PIP2_MSG_SIZE]; |
| u8 status, boot; |
| u32 enum_status_tmp = ENUM_STATUS_START; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_pip2_cmd pip2_cmd = { |
| .cmd_id = PIP2_CMD_ID_STATUS, |
| .cmd_tag_seq = 0x08, |
| .read_buf = read_buf, |
| }; |
| |
| /* Flush Bus to clear pending report */ |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| /* |
| * Suppose to be PIP2_CABABLE/BL-ROM/HYBRIDE. |
| * NOTE: If BL-ROM, DUT_GEN can't determined. |
| */ |
| memset(write_buf, 0, ARRAY_SIZE(write_buf)); |
| memset(read_buf, 0, ARRAY_SIZE(read_buf)); |
| write_buf[index++] = 0x01; /* LSB, PIP2 CMD Register */ |
| write_buf[index++] = 0x01; /* MSB, PIP2 CMD Register */ |
| write_buf[index++] = 0x06; /* LSB, Length */ |
| write_buf[index++] = 0x00; /* MSB, Length */ |
| write_buf[index++] = 0x08; /* SEQ */ |
| write_buf[index++] = PIP2_CMD_ID_STATUS; /* CMD_ID */ |
| write_buf[index++] = 0x3A; /* MSB, CRC */ |
| write_buf[index++] = 0xD1; /* LSB, CRC */ |
| /* |
| * TBD: Below function will be replaced with pt_pip2_send_and_wait_() or |
| * equalling one after protocol mode switch is removed from lower level. |
| */ |
| rc = pt_hid_output_user_cmd_(cd, PT_MAX_PIP2_MSG_SIZE, read_buf, index, |
| write_buf, &pip2_cmd.actual_read_len); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to send PIP2 STATUS, rc=%d\n", __func__, |
| rc); |
| goto exit; |
| } |
| |
| /* Validate STATUS response */ |
| rc = pt_pip2_validate_response(cd, &pip2_cmd); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to validate PIP2 STATUS, rc=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| boot = read_buf[PIP2_RESP_BODY_OFFSET] & 0x01; |
| if (status == PIP2_RSP_ERR_INIT_FAILURE) { |
| dut_gen_tmp = DUT_PIP2_CAPABLE; |
| mode_tmp = PT_MODE_OPERATIONAL; |
| prot_mode_tmp = PT_PROTOCOL_MODE_PIP; |
| enum_status_tmp = ENUM_STATUS_FW_RESET_SENTINEL; |
| } else if (status == PIP2_RSP_ERR_NONE) { |
| if (boot == 0x00) { |
| dut_gen_tmp = DUT_PIP2_CAPABLE; |
| mode_tmp = PT_MODE_BOOTLOADER; |
| prot_mode_tmp = PT_PROTOCOL_MODE_PIP; |
| enum_status_tmp = ENUM_STATUS_BL_RESET_SENTINEL; |
| } else { |
| mode_tmp = PT_MODE_OPERATIONAL; |
| /* protocol mode need a mask: 0x07 */ |
| prot_mode_tmp = |
| read_buf[PIP2_RESP_BODY_OFFSET + 2] & 0x07; |
| dut_gen_tmp = DUT_PIP2_CAPABLE; |
| enum_status_tmp = ENUM_STATUS_FW_RESET_SENTINEL; |
| } |
| } |
| |
| exit: |
| mutex_lock(&cd->system_lock); |
| if (dut_gen) |
| *dut_gen = dut_gen_tmp; |
| if (mode) |
| *mode = mode_tmp; |
| if (prot_mode) |
| *prot_mode = prot_mode_tmp; |
| if (enum_status) |
| *enum_status = enum_status_tmp; |
| mutex_unlock(&cd->system_lock); |
| |
| #ifdef TTDL_DIAGNOSTICS |
| pt_debug(dev, DL_INFO, "%s: Generation=%d Mode=%d Prot_mode=%d\n", |
| __func__, dut_gen_tmp, mode_tmp, prot_mode_tmp); |
| #endif /* TTDL_DIAGNOSTICS */ |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_detect_dut_by_pip3 |
| * |
| * SUMMARY: Detect DUT by PIP3 STATUS command. If responsed correctly, the |
| * protocol mode known as HID and mode could be Operation or Secondary Image. |
| * |
| * NOTE: PIP3 device belongs to DUT_PIP2_CAPABLE generation temporarily. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *status - pointer to status bitmask |
| * *dut_gen - pointer to store the dut_generation |
| * *mode - pointer to store the pt_mode |
| * *prot_mode - pointer to store the protocol_mode |
| ******************************************************************************/ |
| static int _pt_detect_dut_by_pip3(struct device *dev, |
| u32 *enum_status, u8 *dut_gen, enum pt_mode *mode, |
| u8 *prot_mode) |
| { |
| int rc = 0; |
| u8 index = 0; |
| u8 dut_gen_tmp = DUT_UNKNOWN; |
| u8 mode_tmp = PT_MODE_UNKNOWN; |
| u8 prot_mode_tmp = PT_PROTOCOL_MODE_UNKNOWN; |
| u8 write_buf[7]; |
| u8 read_buf[PT_MAX_PIP2_MSG_SIZE]; |
| u8 status, boot; |
| u16 actual_read_len = 0; |
| u32 enum_status_tmp = ENUM_STATUS_START; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| /* Flush Bus to clear pending report */ |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| /* Suppose to be PIP3. */ |
| memset(write_buf, 0, ARRAY_SIZE(write_buf)); |
| memset(read_buf, 0, ARRAY_SIZE(read_buf)); |
| write_buf[index++] = 0x04; /* report ID */ |
| write_buf[index++] = 0x06; /* LSB, Length */ |
| write_buf[index++] = 0x00; /* MSB, Length */ |
| write_buf[index++] = 0x08; /* SEQ */ |
| write_buf[index++] = HID_CMD_ID_STATUS; /* CMD_ID */ |
| write_buf[index++] = 0x3A; /* MSB, CRC */ |
| write_buf[index++] = 0xD1; /* LSB, CRC */ |
| /* Set report type to PIP3 */ |
| cd->pt_hid_buf_op.report_type = PIP3_CMD_REPORT; |
| rc = pt_hid_user_cmd_send_(cd, write_buf, index); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, "%s: Failed to send PIP3 STATUS\n", |
| __func__); |
| goto exit; |
| } else { |
| actual_read_len = get_unaligned_le16(&cd->response_buf[0]); |
| if (actual_read_len == 0) |
| actual_read_len = 2; |
| /* Payload len of STATUS is 11 */ |
| if (actual_read_len != 11) { |
| pt_debug(dev, DL_ERROR, "%s: Response len(%d) error\n", |
| __func__, actual_read_len); |
| rc = -EINVAL; |
| goto exit; |
| } |
| /* Copy the payload to read_buf */ |
| memcpy(read_buf, cd->response_buf, actual_read_len); |
| } |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| boot = read_buf[PIP2_RESP_BODY_OFFSET] & 0x01; |
| if (status == PIP2_RSP_ERR_INIT_FAILURE) { |
| dut_gen_tmp = DUT_PIP2_CAPABLE; |
| mode_tmp = PT_MODE_OPERATIONAL; |
| prot_mode_tmp = PT_PROTOCOL_MODE_HID; |
| enum_status_tmp = ENUM_STATUS_FW_RESET_SENTINEL; |
| } else if (status == PIP2_RSP_ERR_NONE) { |
| if (boot == 0x00) { |
| pt_debug(dev, DL_ERROR, |
| "%s: PIP3 should not see this bit to be 0\n", |
| __func__); |
| goto exit; |
| } else { |
| dut_gen_tmp = DUT_PIP2_CAPABLE; |
| prot_mode_tmp = read_buf[PIP2_RESP_BODY_OFFSET + 2]; |
| mode_tmp = read_buf[PIP2_RESP_BODY_OFFSET + 1]; |
| if (mode_tmp == FW_SYS_MODE_SECONDARY_IMAGE) |
| mode_tmp = PT_MODE_SECONDARY_IMAGE; |
| else |
| mode_tmp = PT_MODE_OPERATIONAL; |
| enum_status_tmp = ENUM_STATUS_FW_RESET_SENTINEL; |
| } |
| } |
| |
| exit: |
| mutex_lock(&cd->system_lock); |
| if (dut_gen) |
| *dut_gen = dut_gen_tmp; |
| if (mode) |
| *mode = mode_tmp; |
| if (prot_mode) |
| *prot_mode = prot_mode_tmp; |
| if (enum_status) |
| *enum_status = enum_status_tmp; |
| mutex_unlock(&cd->system_lock); |
| |
| #ifdef TTDL_DIAGNOSTICS |
| pt_debug(dev, DL_INFO, "%s: Generation=%d Mode=%d Prot_mode=%d\n", |
| __func__, dut_gen_tmp, mode_tmp, prot_mode_tmp); |
| #endif /* TTDL_DIAGNOSTICS */ |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_detect_dut_generation |
| * |
| * SUMMARY: Determine the generation and protocol mode of device. |
| * This function will Detect DUT: |
| * 1) By STATUS command with PIP2; |
| * 2) If 1) failed, it will use PIP3 STATUS command to dectect; |
| * 3) If 2) failed, it will use PIP1 REPORT_DESC command to detect. |
| * |
| * NOTE: |
| * If PIP2 BL is detected, this method can't identify the protocol_mode. It |
| * should be called again in ttdl_restart process. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *status - pointer to status bitmask |
| * *dut_gen - pointer to store the dut_generation |
| * *mode - pointer to store the pt_mode |
| * *prot_mode - pointer to store the protocol_mode |
| ******************************************************************************/ |
| static int _pt_detect_dut_generation(struct device *dev, |
| u32 *status, u8 *dut_gen, enum pt_mode *mode, |
| u8 *prot_mode) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| int rc = 0; |
| |
| /* |
| * If the protocol is set to HID, only attempt to detect the |
| * generation based on the HID protocol. |
| */ |
| if (cd->set_protocol_mode && |
| cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| rc = _pt_detect_dut_by_pip3(dev, status, dut_gen, mode, |
| prot_mode); |
| if (!rc) { |
| pt_debug(dev, DL_INFO, "%s: Success with PIP3 detect\n", |
| __func__); |
| goto exit; |
| } |
| } |
| |
| /* |
| * If the protocol is set to PIP, only attempt to detect the |
| * generation based on the PIP protocol. |
| */ |
| if (cd->set_protocol_mode && |
| (cd->protocol_mode == PT_PROTOCOL_MODE_PIP || |
| cd->protocol_mode == PT_PROTOCOL_MODE_HYBRID_HID)) { |
| rc = _pt_detect_dut_by_pip2(dev, status, dut_gen, mode, |
| prot_mode); |
| if (!rc) { |
| pt_debug(dev, DL_INFO, "%s: Success with PIP2 detect\n", |
| __func__); |
| goto exit; |
| } |
| rc = _pt_detect_dut_by_pip1(dev, status, dut_gen, mode, |
| prot_mode); |
| if (!rc) { |
| pt_debug(dev, DL_INFO, "%s: Success with PIP1 detect\n", |
| __func__); |
| goto exit; |
| } |
| pt_debug(dev, DL_WARN, "%s: Failed to detect DUT generation\n", |
| __func__); |
| goto exit; |
| } |
| |
| /* |
| * If neither the protocol mode or generation were set by host, |
| * then try to detect the generation by any protocol |
| */ |
| rc = _pt_detect_dut_by_pip3(dev, status, dut_gen, mode, prot_mode); |
| if (!rc) { |
| pt_debug(dev, DL_INFO, "%s: Success with PIP3 detect\n", |
| __func__); |
| goto exit; |
| } |
| |
| rc = _pt_detect_dut_by_pip2(dev, status, dut_gen, mode, prot_mode); |
| if (!rc) { |
| pt_debug(dev, DL_INFO, "%s: Success with PIP2 detect\n", |
| __func__); |
| goto exit; |
| } |
| |
| rc = _pt_detect_dut_by_pip1(dev, status, dut_gen, mode, prot_mode); |
| if (!rc) { |
| pt_debug(dev, DL_INFO, "%s: Success with PIP1 detect\n", |
| __func__); |
| goto exit; |
| } |
| |
| pt_debug(dev, DL_WARN, "%s: Failed to detect DUT generation\n", |
| __func__); |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_dut_generation |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to get current dut generation. |
| * |
| * NOTE: This function WILL NOT try to determine dut generation. |
| * |
| * RETURN: |
| * The current dut generation. |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int _pt_request_dut_generation(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return cd->active_dut_generation; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_detect_dut_mode() |
| * |
| * SUMMARY: Detect dut mode by PIP2/PIP3 STATUS command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *mode - pointer to store the pt_mode |
| ******************************************************************************/ |
| static int _pt_detect_dut_mode(struct device *dev, u8 *mode) |
| { |
| int rc = 0; |
| enum pt_mode tmp_mode; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| cd->mode != PT_MODE_BOOTLOADER) { |
| rc = _pt_detect_dut_by_pip3(dev, NULL, NULL, &tmp_mode, NULL); |
| if (rc) |
| rc = _pt_detect_dut_by_pip2(dev, NULL, NULL, &tmp_mode, |
| NULL); |
| } else if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| rc = _pt_detect_dut_by_pip2(dev, NULL, NULL, &tmp_mode, NULL); |
| if (rc) |
| rc = _pt_detect_dut_by_pip3(dev, NULL, NULL, &tmp_mode, |
| NULL); |
| } else { |
| rc = _pt_detect_dut_by_pip2(dev, NULL, NULL, &tmp_mode, NULL); |
| } |
| |
| if (!rc && mode) |
| *mode = tmp_mode; |
| |
| return rc; |
| } |
| |
| #define HW_VERSION_LEN_MAX 13 |
| /******************************************************************************* |
| * FUNCTION: _legacy_generate_hw_version |
| * |
| * SUMMARY: Format chip infomation from struct ttdata (maintained by PIP1 |
| * SYSINFO command) or struct bl_info (maintained by PIP1 BL INFOMATION |
| * command) to the hw_version. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hw_version - pointer to store the hardware version |
| ******************************************************************************/ |
| static int _legacy_generate_hw_version(struct pt_core_data *cd, |
| char *hw_version) |
| { |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| |
| if (cd->sysinfo.ready) { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, "%04X.FFFF.%02X", |
| ttdata->jtag_id_h, cd->pid_for_loader); |
| return 0; |
| } else if (cd->bl_info.ready) { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, "%04X.FFFF.%02X", |
| cd->bl_info.chip_id, cd->pid_for_loader); |
| return 0; |
| } else { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: SYSINFO and BL_INFO are not ready\n", __func__); |
| return -ENODATA; |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pip2_generate_hw_version |
| * |
| * SUMMARY: Format chip infomation from struct ttdata (maintained by PIP2 |
| * VERSION command) to the hw_version. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *hw_version - pointer to store the hardware version |
| ******************************************************************************/ |
| static int _pip2_generate_hw_version(struct pt_core_data *cd, char *hw_version) |
| { |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| |
| if (cd->app_pip_ver_ready | cd->bl_pip_ver_ready) { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, "%04X.%04X.%02X", |
| ttdata->chip_id, ttdata->chip_rev, cd->pid_for_loader); |
| return 0; |
| } else { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: PIP Version are not ready\n", __func__); |
| return -ENODATA; |
| } |
| } |
| |
| /******************************************************************************* |
| * SUMMARY: Attempt to retrieve the HW version of the connected DUT |
| * |
| * NOTE: The calling function must ensure to free *hw_version |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = Failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *hw_version - pointer to where the hw_version string will be stored |
| ******************************************************************************/ |
| static int _pt_request_hw_version(struct device *dev, char *hw_version) |
| { |
| int rc = 0; |
| u16 actual_read_len; |
| u16 pip_ver; |
| u8 rd_buf[256]; |
| u8 status; |
| u8 index = PIP2_RESP_STATUS_OFFSET; |
| u8 return_data[8]; |
| u8 panel_id; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; |
| |
| if (!hw_version) |
| return -ENOMEM; |
| |
| if (!cd->hw_detected) { |
| /* No HW detected */ |
| rc = -ENODEV; |
| pt_debug(dev, DL_ERROR, "%s: no hardware is detected!\n", |
| __func__); |
| goto exit_error; |
| } |
| |
| /* For Parade TC or TT parts */ |
| if (cd->active_dut_generation == DUT_PIP2_CAPABLE) { |
| rc = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_VERSION, |
| NULL, 0, rd_buf, &actual_read_len); |
| |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to send PIP2 VERSION cmd\n", |
| __func__); |
| goto exit_error; |
| } |
| |
| status = rd_buf[index]; |
| if (status == 0) { |
| pip_ver = 256 * rd_buf[index + 2] + rd_buf[index + 1]; |
| |
| /* |
| * BL PIP 2.02 and greater the version fields are |
| * swapped |
| */ |
| if (pip_ver >= 0x0202) { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, |
| "%02X%02X.%02X%02X.FF", |
| rd_buf[index + 10], rd_buf[index + 9], |
| rd_buf[index + 8], rd_buf[index + 7]); |
| } else { |
| snprintf(hw_version, HW_VERSION_LEN_MAX, |
| "%02X%02X.%02X%02X.FF", |
| rd_buf[index + 8], rd_buf[index + 7], |
| rd_buf[index + 10], rd_buf[index + 9]); |
| } |
| return STATUS_SUCCESS; |
| } else { |
| rc = status; |
| pt_debug(dev, DL_WARN, |
| "%s: PIP2 VERSION cmd response error\n", |
| __func__); |
| } |
| } else if (cd->active_dut_generation == DUT_PIP1_ONLY) { |
| /* |
| * For Parade/Cypress legacy parts the RevID and FamilyID are |
| * hard coded to FFFF |
| */ |
| if (cd->mode == PT_MODE_OPERATIONAL) { |
| rc = pt_hid_output_get_sysinfo(cd); |
| if (!rc) { |
| panel_id = |
| cd->sysinfo.sensing_conf_data.panel_id; |
| } else { |
| panel_id = PANEL_ID_NOT_ENABLED; |
| } |
| /* In FW - simply retrieve from ttdata struct */ |
| snprintf(hw_version, HW_VERSION_LEN_MAX, |
| "%04X.FFFF.%02X", |
| ttdata->jtag_id_h, |
| panel_id); |
| return STATUS_SUCCESS; |
| } else { |
| /* |
| * Return the stored value if PT_PANEL_ID_BY_BL |
| * is not supported while other feature is. |
| */ |
| if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { |
| rc = pt_hid_output_bl_get_information( |
| cd, return_data); |
| if (!rc) { |
| rc = pt_hid_output_bl_get_panel_id( |
| cd, &panel_id); |
| } |
| } else |
| panel_id = cd->pid_for_loader; |
| if (!rc) { |
| snprintf(hw_version, |
| HW_VERSION_LEN_MAX, |
| "%02X%02X.FFFF.%02X", |
| return_data[3], return_data[2], |
| panel_id); |
| return STATUS_SUCCESS; |
| } |
| } |
| } else { |
| /* Unknown generation */ |
| rc = -ENODEV; |
| pt_debug(dev, DL_ERROR, "%s: generation is unkown!\n", |
| __func__); |
| } |
| |
| exit_error: |
| snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_start_wd_timer |
| * |
| * SUMMARY: Starts the TTDL watchdog timer if the timer interval is > 0 |
| * |
| * RETURN: void |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static void pt_start_wd_timer(struct pt_core_data *cd) |
| { |
| if (cd->watchdog_interval < 100) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: WARNING: Invalid watchdog interval: %d\n", |
| __func__, cd->watchdog_interval); |
| return; |
| } |
| |
| if (cd->watchdog_force_stop) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TTDL WD Forced stop\n", __func__); |
| return; |
| } |
| |
| mod_timer(&cd->watchdog_timer, jiffies + |
| msecs_to_jiffies(cd->watchdog_interval)); |
| cd->watchdog_enabled = 1; |
| pt_debug(cd->dev, DL_DEBUG, "%s: TTDL WD Started\n", __func__); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_stop_wd_timer |
| * |
| * SUMMARY: Stops the TTDL watchdog timer if the timer interval is > 0 |
| * |
| * RETURN: void |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static void pt_stop_wd_timer(struct pt_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); |
| cd->watchdog_enabled = 0; |
| pt_debug(cd->dev, DL_DEBUG, "%s: TTDL WD Stopped\n", __func__); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hw_soft_reset |
| * |
| * SUMMARY: Sends a PIP reset command to the DUT. Disable/re-enable the |
| * TTDL watchdog around the reset to ensure the WD doesn't happen to |
| * schedule an enum if it fires when the DUT is being reset. |
| * This can cause a double reset. |
| * |
| * NOTE: The WD MUST be stopped/restarted by the calling Function. Having |
| * the WD active could cause this function to fail! |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data struct |
| * protect - flag to call protected or non-protected |
| ******************************************************************************/ |
| static int pt_hw_soft_reset(struct pt_core_data *cd, int protect) |
| { |
| int rc = 0; |
| |
| mutex_lock(&cd->system_lock); |
| cd->enum_status = ENUM_STATUS_START; |
| pt_debug(cd->dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); |
| mutex_unlock(&cd->system_lock); |
| if (protect) |
| rc = pt_hid_cmd_reset(cd); |
| else |
| rc = pt_hid_cmd_reset_(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: FAILED to execute SOFT reset\n", __func__); |
| return rc; |
| } |
| pt_debug(cd->dev, DL_INFO, "%s: SOFT reset successful\n", |
| __func__); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_hw_hard_reset |
| * |
| * SUMMARY: Calls the platform xres function if it exists to perform a hard |
| * reset on the DUT by toggling the XRES gpio. Disable/re-enable the |
| * TTDL watchdog around the reset to ensure the WD doesn't happen to |
| * schedule an enum if it fires when the DUT is being reset. |
| * This can cause a double reset. |
| * |
| * NOTE: The WD MUST be stopped/restarted by the calling Function. Having |
| * the WD active could cause this function to fail! |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data struct |
| ******************************************************************************/ |
| static int pt_hw_hard_reset(struct pt_core_data *cd) |
| { |
| if (cd->cpdata->xres) { |
| cd->enum_status = ENUM_STATUS_START; |
| pt_debug(cd->dev, DL_DEBUG, "%s: Startup Status Reset\n", |
| __func__); |
| cd->cpdata->xres(cd->cpdata, cd->dev); |
| pt_debug(cd->dev, DL_WARN, "%s: executed HARD reset\n", |
| __func__); |
| return 0; |
| } |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: FAILED to execute HARD reset\n", __func__); |
| |
| return -ENODEV; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_dut_reset |
| * |
| * SUMMARY: Attempts to reset the DUT by a hard reset and if that fails a |
| * soft reset. |
| * |
| * NOTE: The WD MUST be stopped/restarted by the calling Function. Having |
| * the WD active could cause this function to fail! |
| * NOTE: "protect" flag is only used for soft reset. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * protect - flag to call protected or non-protected |
| ******************************************************************************/ |
| static int pt_dut_reset(struct pt_core_data *cd, int protect) |
| { |
| int rc = 0; |
| |
| pt_debug(cd->dev, DL_INFO, "%s: reset hw...\n", __func__); |
| mutex_lock(&cd->system_lock); |
| cd->hid_reset_cmd_state = 1; |
| rc = pt_hw_hard_reset(cd); |
| mutex_unlock(&cd->system_lock); |
| |
| if (rc == -ENODEV) { |
| mutex_lock(&cd->system_lock); |
| cd->hid_reset_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Hard reset failed, try soft reset\n", __func__); |
| rc = pt_hw_soft_reset(cd, protect); |
| } |
| |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, "%s: %s dev='%s' r=%d\n", |
| __func__, "Fail hw reset", dev_name(cd->dev), rc); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_dut_reset_and_wait |
| * |
| * SUMMARY: Wrapper function for pt_dut_reset that waits for the reset to |
| * complete |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int pt_dut_reset_and_wait(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| int t; |
| |
| rc = pt_dut_reset(cd, PT_CORE_CMD_UNPROTECTED); |
| if (rc < 0) |
| goto exit; |
| |
| if (cd->dual_mcu_available) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Dual MCU is enabled, no BL sentinel after reset\n", |
| __func__); |
| usleep_range(10000, 11000); |
| cd->hid_reset_cmd_state = 0; |
| goto exit; |
| } |
| |
| t = wait_event_timeout(cd->wait_q, |
| (cd->hid_reset_cmd_state == 0), |
| msecs_to_jiffies(PT_HID_CMD_DEFAULT_TIMEOUT)); |
| if (IS_TMO(t)) { |
| #ifdef TTDL_DIAGNOSTICS |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| #endif /* TTDL_DIAGNOSTICS */ |
| pt_debug(cd->dev, DL_ERROR, "%s: reset timed out\n", |
| __func__); |
| rc = -ETIME; |
| goto exit; |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| /* |
| * touch default parameters (from report descriptor) to resolve protocol for |
| * touch report |
| */ |
| const struct pt_tch_abs_params tch_hdr_default[PT_TCH_NUM_HDR] = { |
| /* byte offset, size, min, max, bit offset, report */ |
| {0x00, 0x02, 0x00, 0x10000, 0x00, 0x01}, /* SCAN TIME */ |
| {0x02, 0x01, 0x00, 0x20, 0x00, 0x01}, /* NUMBER OF RECORDS */ |
| {0x02, 0x01, 0x00, 0x02, 0x05, 0x01}, /* LARGE OBJECT */ |
| {0x03, 0x01, 0x00, 0x08, 0x00, 0x01}, /* NOISE EFFECT */ |
| {0x03, 0x01, 0x00, 0x04, 0x06, 0x01}, /* REPORT_COUNTER */ |
| }; |
| |
| /* |
| * button default parameters (from report descriptor) to resolve protocol for |
| * button report |
| */ |
| const struct pt_tch_abs_params tch_abs_default[PT_TCH_NUM_ABS] = { |
| /* byte offset, size, min, max, bit offset, report */ |
| {0x02, 0x02, 0x00, 0x10000, 0x00, 0x01}, /* X */ |
| {0x04, 0x02, 0x00, 0x10000, 0x00, 0x01}, /* Y */ |
| {0x06, 0x01, 0x00, 0x100, 0x00, 0x01}, /* P (Z) */ |
| {0x01, 0x01, 0x00, 0x20, 0x00, 0x01}, /* TOUCH ID */ |
| {0x01, 0x01, 0x00, 0x04, 0x05, 0x01}, /* EVENT ID */ |
| {0x00, 0x01, 0x00, 0x08, 0x00, 0x01}, /* OBJECT ID */ |
| {0x01, 0x01, 0x00, 0x02, 0x07, 0x01}, /* LIFTOFF */ |
| {0x07, 0x01, 0x00, 0x100, 0x00, 0x01}, /* TOUCH_MAJOR */ |
| {0x08, 0x01, 0x00, 0x100, 0x00, 0x01}, /* TOUCH_MINOR */ |
| {0x09, 0x01, 0x00, 0x100, 0x00, 0x01}, /* ORIENTATION */ |
| }; |
| |
| /******************************************************************************* |
| * FUNCTION: pt_init_pip_report_fields |
| * |
| * SUMMARY: Setup default values for touch/button report parsing. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_init_pip_report_fields(struct pt_core_data *cd) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| |
| memcpy(si->tch_hdr, tch_hdr_default, sizeof(tch_hdr_default)); |
| memcpy(si->tch_abs, tch_abs_default, sizeof(tch_abs_default)); |
| |
| si->desc.tch_report_id = PT_PIP_TOUCH_REPORT_ID; |
| si->desc.tch_record_size = TOUCH_REPORT_SIZE; |
| si->desc.tch_header_size = TOUCH_INPUT_HEADER_SIZE; |
| si->desc.btn_report_id = PT_PIP_CAPSENSE_BTN_REPORT_ID; |
| si->desc.pen_report_id = PT_HID_PEN_REPORT_ID; |
| si->desc.max_touch_num = MAX_TOUCH_NUM; |
| |
| cd->features.easywake = 1; |
| cd->features.noise_metric = 1; |
| cd->features.tracking_heatmap = 1; |
| cd->features.sensor_data = 1; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_hid_report_ |
| * |
| * SUMMARY: Get or create report. Must be called with cd->hid_report_lock |
| * acquired. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *index - pointer to report index |
| * report_type - type of hid report |
| * report_id - id of hid report |
| * create - true: will create a new report |
| * false: will not create a new report |
| ******************************************************************************/ |
| static int pt_get_hid_report_(struct pt_core_data *cd, u8 *index, |
| u8 report_type, u8 report_id, bool create) |
| { |
| struct pt_hid_report *report = NULL; |
| int i; |
| int rc = 0; |
| |
| /* 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) { |
| *index = i; |
| goto exit; |
| } |
| } |
| |
| if (create && cd->num_hid_reports >= PT_HID_MAX_REPORTS) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: num_hid_reports=%d max=%d\n", __func__, |
| cd->num_hid_reports, PT_HID_MAX_REPORTS); |
| rc = -EINVAL; |
| } else if (create && cd->num_hid_reports < PT_HID_MAX_REPORTS) { |
| /* Create a new report */ |
| report = kzalloc(sizeof(struct pt_hid_report), |
| GFP_KERNEL); |
| if (!report) |
| rc = -ENOMEM; |
| else { |
| report->type = report_type; |
| report->id = report_id; |
| *index = cd->num_hid_reports; |
| cd->hid_reports[cd->num_hid_reports++] = report; |
| } |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_free_hid_reports_ |
| * |
| * SUMMARY: Free HID report. Must be called with cd->hid_report_lock acquired. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_free_hid_reports_(struct pt_core_data *cd) |
| { |
| struct pt_hid_report *report; |
| int i, j; |
| |
| for (i = 0; i < cd->num_hid_reports; i++) { |
| report = cd->hid_reports[i]; |
| for (j = 0; j < report->num_fields; j++) |
| kfree(report->fields[j]); |
| kfree(report); |
| cd->hid_reports[i] = NULL; |
| } |
| |
| cd->num_hid_reports = 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_free_hid_reports |
| * |
| * SUMMARY: Protected call to pt_free_hid_reports_() by a mutex lock. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void pt_free_hid_reports(struct pt_core_data *cd) |
| { |
| mutex_lock(&cd->hid_report_lock); |
| pt_free_hid_reports_(cd); |
| mutex_unlock(&cd->hid_report_lock); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_create_hid_field_ |
| * |
| * SUMMARY: Create field for HID report.Must be called with cd->hid_report_lock |
| * acquired. |
| * |
| * RETURN: |
| * pointer to hid field structure |
| * |
| * PARAMETERS: |
| * *report - pointer to hid report structure |
| ******************************************************************************/ |
| static struct pt_hid_field *pt_create_hid_field_( |
| struct pt_hid_report *report) |
| { |
| struct pt_hid_field *field; |
| |
| if (!report) |
| return NULL; |
| |
| if (report->num_fields == PT_HID_MAX_FIELDS) |
| return NULL; |
| |
| field = kzalloc(sizeof(struct pt_hid_field), GFP_KERNEL); |
| if (!field) |
| return NULL; |
| |
| field->report = report; |
| |
| report->fields[report->num_fields++] = field; |
| |
| return field; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: get_hid_item_data |
| * |
| * SUMMARY: Get hid item data according to the item size. |
| * |
| * RETURN: |
| * 0 = no data |
| * !0 = data |
| * |
| * PARAMETERS: |
| * *data - pointer to item data |
| * item_size - the size of the item |
| ******************************************************************************/ |
| 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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: parse_report_descriptor |
| * |
| * SUMMARY: Parse report descriptor. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *report_desc - pointer to report descriptor structure |
| * len - length of the data buffer to be parsed |
| ******************************************************************************/ |
| static int parse_report_descriptor(struct pt_core_data *cd, |
| u8 *report_desc, size_t len) |
| { |
| struct pt_hid_report *report; |
| struct pt_hid_field *field; |
| u8 *buf = report_desc; |
| u8 *end = buf + len; |
| int rc = 0; |
| int offset = 0; |
| int i, j; |
| 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[PT_HID_MAX_NESTED_COLLECTIONS] = {0}; |
| u8 collection_types[PT_HID_MAX_NESTED_COLLECTIONS] = {0}; |
| u32 usages_pen[PT_HID_MAX_CONTINUOUS_USAGES] = {0}; |
| /* First collection for header, second for report */ |
| int logical_collection_count = 0; |
| int app_collection_count = 0; |
| int collection_nest = 0; |
| int usage_cnt = 0; |
| u8 report_index = 0; |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: Report descriptor length: %u\n", |
| __func__, (u32)len); |
| |
| mutex_lock(&cd->hid_report_lock); |
| pt_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: /* 1 */ |
| switch (item_tag) { |
| case HID_GLOBAL_ITEM_TAG_USAGE_PAGE: /* 0 */ |
| 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: /* 1 */ |
| 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: /* 2 */ |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| logical_max = get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM: /* 3 */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TAG Ignored - Physical Min\n", |
| __func__); |
| break; |
| case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM: /* 4 */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TAG Ignored - Physical Max\n", |
| __func__); |
| break; |
| case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT: /* 5 */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TAG Ignored - Unit Exponent\n", |
| __func__); |
| break; |
| case HID_GLOBAL_ITEM_TAG_UNIT: /* 6 */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TAG Ignored - Unit\n", |
| __func__); |
| break; |
| case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: /* 7 */ |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| report_size = get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_REPORT_ID: /* 8 */ |
| 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_REPORT_COUNT: /* 9 */ |
| if (item_size == 0) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| report_count = get_hid_item_data(data, |
| item_size); |
| break; |
| case HID_GLOBAL_ITEM_TAG_PUSH: /* A */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TAG Ignored - Push\n", |
| __func__); |
| break; |
| case HID_GLOBAL_ITEM_TAG_POP: /* B */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: TAG Ignored - Pop\n", |
| __func__); |
| break; |
| default: |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Unrecognized Global tag 0x%X\n", |
| __func__, item_tag); |
| } |
| break; |
| case HID_ITEM_TYPE_LOCAL: /* 2 */ |
| 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); |
| if (report_id == PT_HID_PEN_REPORT_ID) { |
| usages_pen[usage_cnt++] = |
| usage_page << 16 | usage; |
| } |
| 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: |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Unrecognized Local tag %d\n", |
| __func__, item_tag); |
| } |
| break; |
| case HID_ITEM_TYPE_MAIN: /* 0 */ |
| switch (item_tag) { |
| case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION: |
| usage_cnt = 0; |
| |
| if (item_size != 1) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| if (PT_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++; |
| app_collection_count = 0; |
| } |
| if (collection_types[collection_nest] == |
| HID_COLLECTION_APPLICATION) |
| app_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) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: %s=%d\n", |
| __func__, |
| "item_size", item_size); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| up_usage = usage_page << 16 | usage; |
| |
| /* Get or create report */ |
| rc = pt_get_hid_report_(cd, &report_index, |
| report_type, report_id, true); |
| if (rc) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: %s rc=%d\n", |
| __func__, |
| "get_hid_report failed", rc); |
| goto exit; |
| } else |
| report = cd->hid_reports[report_index]; |
| |
| if (!report->usage_page && collection_nest > 0) |
| report->usage_page = |
| collection_usages |
| [collection_nest - 1]; |
| |
| if (report->id == PT_HID_PEN_REPORT_ID |
| && report_count > 1) |
| goto continue_pen_usages; |
| |
| /* Create field */ |
| field = pt_create_hid_field_(report); |
| if (!field) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: %s\n", |
| __func__, |
| "create field failed"); |
| 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; |
| usages_pen[0] = 0; |
| for (j = 0; j < collection_nest; j++) { |
| field->collection_usage_pages |
| [collection_types[j]] = |
| collection_usages[j]; |
| } |
| |
| goto exit_main_item; |
| continue_pen_usages: |
| for (i = 0; i < report_count; i++) { |
| /* Create field */ |
| field = pt_create_hid_field_( |
| report); |
| if (!field) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Pen - %s\n", |
| __func__, |
| "create field failed"); |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| field->report_size = report_size; |
| field->size = report_count * |
| report_size; |
| field->report_count = 1; |
| field->offset = offset + i*report_size; |
| field->data_type = |
| get_hid_item_data(data, |
| item_size); |
| field->logical_min = logical_min; |
| field->logical_max = logical_max; |
| field->usage_page = usages_pen[i]; |
| usages_pen[i] = 0; |
| for (j = 0; j < collection_nest; j++) { |
| field->collection_usage_pages |
| [collection_types[j]] = |
| collection_usages[j]; |
| } |
| } |
| |
| exit_main_item: |
| /* Update report's header or record size */ |
| if (app_collection_count == 1) { |
| report->header_size += field->size; |
| } else if (logical_collection_count == 1) { |
| 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; |
| report->log_collection_num = |
| logical_collection_count; |
| |
| offset += field->size; |
| |
| usage_cnt = 0; |
| break; |
| default: |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Unrecognized Main tag %d\n", |
| __func__, item_tag); |
| } |
| |
| /* Reset all local items */ |
| usage = 0; |
| break; |
| } |
| } |
| |
| if (buf != end) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Report descriptor length invalid\n", |
| __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| if (collection_nest) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Unbalanced collection items (%d)\n", |
| __func__, collection_nest); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| exit: |
| if (rc) |
| pt_free_hid_reports_(cd); |
| mutex_unlock(&cd->hid_report_lock); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: find_report_desc_field |
| * |
| * SUMMARY: Find the corresponding field from report according to the usage page |
| * and collection usage page. |
| * |
| * RETURN: |
| * pointer to hid field structure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * usage_page - hid usage page |
| * collection_usage_page - hid collection usage page |
| * collection_type - hid collection type |
| ******************************************************************************/ |
| static struct pt_hid_field *find_report_desc_field( |
| struct pt_core_data *cd, u32 usage_page, |
| u32 collection_usage_page, u8 collection_type) |
| { |
| struct pt_hid_report *report = NULL; |
| struct pt_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 |
| [collection_type]; |
| field_up = field->usage_page; |
| if (field_cup == collection_usage_page |
| && field_up == usage_page) { |
| return field; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: fill_tch_abs |
| * |
| * SUMMARY: Fill touch abs with hid field. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *field - pointer to hid field structure |
| ******************************************************************************/ |
| static void fill_tch_abs(struct pt_tch_abs_params *tch_abs, |
| struct pt_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); |
| tch_abs->logical_max = field->logical_max; |
| tch_abs->logical_min = field->logical_min; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: find_report_desc |
| * |
| * SUMMARY: Find out the corresponding report based on the usage page. |
| * |
| * RETURN: |
| * pointer to hid report structure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * id - report id |
| * usage_page - hid usage page |
| ******************************************************************************/ |
| static struct pt_hid_report *find_report_desc(struct pt_core_data *cd, |
| u8 id, u32 usage_page) |
| { |
| struct pt_hid_report *report = NULL; |
| int i; |
| |
| for (i = 0; i < cd->num_hid_reports; i++) { |
| if (cd->hid_reports[i]->usage_page == usage_page && |
| cd->hid_reports[i]->id == id) { |
| report = cd->hid_reports[i]; |
| break; |
| } |
| } |
| |
| return report; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: setup_pen_report_from_report_desc |
| * |
| * SUMMARY: Setup values for pen report according to report descriptor. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void setup_pen_report_from_report_desc( |
| struct pt_core_data *cd) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| struct pt_hid_report *report; |
| struct pt_hid_field *field; |
| int i; |
| u32 pen_collection_usage_page = HID_PT_PEN_COL_USAGE_PG; |
| u8 id = PT_HID_PEN_REPORT_ID; |
| |
| /* |
| * Search each pen abs field. If found, fill the values into the |
| * pen struct.If not, mark this pen field as invalid (report = 0). |
| */ |
| for (i = PT_PEN_X; i < PT_PEN_NUM_ABS; i++) { |
| field = find_report_desc_field(cd, |
| pt_pen_abs_field_map[i], |
| pen_collection_usage_page, |
| HID_COLLECTION_PHYSICAL); |
| if (field) { |
| pt_debug(cd->dev, DL_DEBUG, |
| " Field %p: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", |
| field, field->report_count, field->report_size, |
| field->offset, field->data_type, |
| field->logical_min, field->logical_max, |
| field->usage_page); |
| fill_tch_abs(&si->pen_abs[i], field); |
| si->pen_abs[i].report = 2; |
| pt_debug(cd->dev, DL_DEBUG, "%s: ofs:%u size:%u min:%u max:%u bofs:%u report:%d\n", |
| pt_pen_abs_string[i], |
| (u32)si->pen_abs[i].ofs, |
| (u32)si->pen_abs[i].size, |
| (u32)si->pen_abs[i].min, |
| (u32)si->pen_abs[i].max, |
| (u32)si->pen_abs[i].bofs, |
| si->pen_abs[i].report); |
| |
| } else { |
| si->pen_abs[i].report = 0; |
| pt_debug(cd->dev, DL_DEBUG, "%s: report:%d\n", |
| pt_pen_abs_string[i], si->pen_abs[i].report); |
| } |
| } |
| |
| report = find_report_desc(cd, id, pen_collection_usage_page); |
| if (report) |
| si->desc.pen_report_id = report->id; |
| else |
| si->desc.pen_report_id = PT_HID_PEN_REPORT_ID; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: setup_finger_report_from_report_desc |
| * |
| * SUMMARY: Setup values for finger report according to report descriptor. |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static void setup_finger_report_from_report_desc( |
| struct pt_core_data *cd) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| struct pt_hid_report *report; |
| struct pt_hid_field *field; |
| u32 tch_collection_usage_page = HID_PT_TCH_COL_USAGE_PG; |
| u8 id = PT_HID_FINGER_REPORT_ID; |
| int i; |
| |
| /* |
| * Search each touch abs field. If found, fill the values into the |
| * abs struct. If not, mark this abs field as invalid (report = 0). |
| */ |
| for (i = PT_TCH_X; i < PT_TCH_NUM_ABS; i++) { |
| field = find_report_desc_field(cd, |
| pt_tch_abs_field_map[i], |
| tch_collection_usage_page, |
| HID_COLLECTION_APPLICATION); |
| if (field) { |
| pt_debug(cd->dev, DL_DEBUG, |
| " Field %p: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", |
| field, field->report_count, field->report_size, |
| field->offset, 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; |
| pt_debug(cd->dev, DL_DEBUG, "%s: ofs:%u size:%u min:%u max:%u bofs:%u report:%d\n", |
| pt_tch_abs_string[i], |
| (u32)si->tch_abs[i].ofs, |
| (u32)si->tch_abs[i].size, |
| (u32)si->tch_abs[i].min, |
| (u32)si->tch_abs[i].max, |
| (u32)si->tch_abs[i].bofs, |
| si->tch_abs[i].report); |
| } else { |
| si->tch_abs[i].report = 0; |
| } |
| } |
| |
| /* |
| * Search each touch header field. If found, fill the values into |
| * the header struct. If not, mark this header field as invalid |
| * (report = 0). |
| */ |
| for (i = PT_TCH_TIME; i < PT_TCH_NUM_HDR; i++) { |
| field = find_report_desc_field(cd, |
| pt_tch_hdr_field_map[i], |
| tch_collection_usage_page, |
| HID_COLLECTION_APPLICATION); |
| if (field) { |
| pt_debug(cd->dev, DL_DEBUG, |
| " Field %p: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", |
| field, field->report_count, field->report_size, |
| field->offset, 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; |
| pt_debug(cd->dev, DL_DEBUG, "%s: ofs:%u size:%u min:%u max:%u bofs:%u report:%d\n", |
| pt_tch_hdr_string[i], |
| (u32)si->tch_hdr[i].ofs, |
| (u32)si->tch_hdr[i].size, |
| (u32)si->tch_hdr[i].min, |
| (u32)si->tch_hdr[i].max, |
| (u32)si->tch_hdr[i].bofs, |
| si->tch_hdr[i].report); |
| } else { |
| si->tch_hdr[i].report = 0; |
| } |
| } |
| |
| si->desc.max_touch_num = si->tch_hdr[PT_TCH_NUM].max; |
| |
| report = find_report_desc(cd, id, tch_collection_usage_page); |
| if (report) { |
| si->desc.tch_report_id = report->id; |
| /* First, report->record_size and report->header_size are based |
| * on 'BIT', they need to be divided by 8 to get tch_record_size |
| * and tch_header_size, which are based on 'BYTE'. |
| * Second, tch_header_size needs to add 3 bytes: 2 bytes length, |
| * 1 byte report id. |
| */ |
| si->desc.tch_record_size = report->record_size / 8; |
| si->desc.tch_header_size = (report->header_size / 8) + 3; |
| si->desc.max_tch_per_packet = report->log_collection_num; |
| } else { |
| si->desc.tch_report_id = PT_HID_FINGER_REPORT_ID; |
| si->desc.tch_record_size = TOUCH_REPORT_SIZE; |
| si->desc.tch_header_size = TOUCH_INPUT_HEADER_SIZE; |
| si->desc.max_tch_per_packet = MAX_TOUCH_NUM; |
| } |
| |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: tch_record_size:%d, tch_header_size:%d, max_touch_num:%d\n", |
| __func__, |
| si->desc.tch_record_size, |
| si->desc.tch_header_size, |
| si->desc.max_touch_num); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: publish_report_desc |
| * |
| * SUMMARY: If TTHE_TUNER_SUPPORT is defined print the report descriptor data |
| * into the tthe_tuner sysfs node under the label "HIDRptDesc". |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * len - Report descriptor length |
| ******************************************************************************/ |
| static void publish_report_desc(struct pt_core_data *cd, u16 len) |
| { |
| int max_bytes = 300; |
| int pr_bytes = 0; |
| int size = len; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, cd->response_buf, size, |
| "HIDRptDesc="); |
| #endif |
| |
| while (size > max_bytes) { |
| pt_pr_buf(cd->dev, DL_DEBUG, |
| cd->response_buf + pr_bytes, |
| max_bytes, "<<< HIDRptDesc"); |
| pr_bytes += max_bytes; |
| size -= max_bytes; |
| } |
| if (size > 0) |
| pt_pr_buf(cd->dev, DL_DEBUG, |
| cd->response_buf + pr_bytes, |
| size, "<<< HIDRptDesc"); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_report_descriptor_ |
| * |
| * SUMMARY: Get and parse report descriptor. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_get_report_descriptor_(struct pt_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| struct pt_hid_cmd hid_cmd = { |
| .descriptor = cd->hid_desc.report_desc_register, |
| .read_length = cd->hid_core.hid_report_desc_len, |
| }; |
| int rc; |
| int t; |
| u8 *desc; |
| u16 desc_len; |
| |
| rc = pt_hid_send_command_(cd, &hid_cmd); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: failed to get HID report descriptor length, rc=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| desc = cd->response_buf; |
| desc_len = cd->hid_core.hid_report_desc_len; |
| |
| /* Remove lenth field and report id to prepare parse work */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_PIP) { |
| desc = cd->response_buf + 3; |
| desc_len -= 3; |
| } |
| |
| publish_report_desc(cd, cd->hid_core.hid_report_desc_len); |
| |
| rc = parse_report_descriptor(cd, desc, desc_len); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error parsing report descriptor rc=%d\n", |
| __func__, rc); |
| } |
| |
| pt_debug(cd->dev, DL_INFO, "%s: %d reports found in descriptor\n", |
| __func__, cd->num_hid_reports); |
| |
| for (t = 0; t < cd->num_hid_reports; t++) { |
| struct pt_hid_report *report = cd->hid_reports[t]; |
| int j; |
| |
| pt_debug(cd->dev, DL_DEBUG, |
| "Report %d: type:%d id:%02X size:%d fields:%d rec_fld_index:%d hdr_sz:%d rec_sz:%d usage_page:%08X\n", |
| t, report->type, report->id, |
| report->size, report->num_fields, |
| report->record_field_index, report->header_size, |
| report->record_size, report->usage_page); |
| |
| if (report->id == PT_HID_FINGER_REPORT_ID) |
| pt_debug(cd->dev, DL_INFO, "%s: logical collection number: %d\n", |
| __func__, report->log_collection_num); |
| |
| for (j = 0; j < report->num_fields; j++) { |
| struct pt_hid_field *field = report->fields[j]; |
| |
| pt_debug(cd->dev, DL_DEBUG, |
| " Field %d: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", |
| j, field->report_count, field->report_size, |
| field->offset, field->data_type, |
| field->logical_min, field->logical_max, |
| field->usage_page); |
| |
| pt_debug(cd->dev, DL_DEBUG, " 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]); |
| } |
| } |
| |
| setup_pen_report_from_report_desc(cd); |
| setup_finger_report_from_report_desc(cd); |
| |
| /* Free it for now */ |
| pt_free_hid_reports_(cd); |
| |
| pt_debug(cd->dev, DL_INFO, "%s: %d reports found in descriptor\n", |
| __func__, cd->num_hid_reports); |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_report_descriptor |
| * |
| * SUMMARY: Protected call to pt_get_report_descriptor_() |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_get_report_descriptor(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_get_report_descriptor_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_mode |
| * |
| * SUMMARY: Determine the current mode from the contents of a HID descriptor |
| * message |
| * |
| * RETURN: Enum of the current mode |
| * |
| * PARAMETERS: |
| * *cd - pointer to the Core Data structure |
| * protect - run command in protected mode |
| * *mode - pointer to store the retrieved mode |
| ******************************************************************************/ |
| static int pt_get_mode(struct pt_core_data *cd, struct pt_hid_desc *desc) |
| { |
| if (desc->packet_id == HID_APP_REPORT_ID) |
| return PT_MODE_OPERATIONAL; |
| else if (desc->packet_id == HID_BL_REPORT_ID) |
| return PT_MODE_BOOTLOADER; |
| |
| return PT_MODE_UNKNOWN; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_get_mode |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to determine the current mode of the DUT by use of the Get HID |
| * Descriptor command. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - run command in protected mode |
| * *mode - pointer to store the retrieved mode |
| ******************************************************************************/ |
| static int _pt_request_get_mode(struct device *dev, int protect, u8 *mode) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| struct pt_hid_desc hid_desc; |
| int rc = 0; |
| |
| memset(&hid_desc, 0, sizeof(hid_desc)); |
| if (protect) |
| rc = pt_get_hid_descriptor(cd, &hid_desc); |
| else |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| |
| if (rc) |
| *mode = PT_MODE_UNKNOWN; |
| else |
| *mode = pt_get_mode(cd, &hid_desc); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_queue_enum_ |
| * |
| * SUMMARY: Queues a TTDL enum by scheduling work with the pt_enum_with_dut() |
| * function. It won't try to add/delete sysfs node or modules. |
| * |
| * RETURN: void |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static void pt_queue_enum_(struct pt_core_data *cd) |
| { |
| if (cd->startup_state == STARTUP_NONE || |
| cd->startup_state == STARTUP_DONE) { |
| cd->startup_state = STARTUP_QUEUED; |
| |
| #ifdef TTDL_DIAGNOSTICS |
| if (!cd->bridge_mode) |
| schedule_work(&cd->enum_work); |
| else |
| cd->startup_state = STARTUP_NONE; |
| #else |
| schedule_work(&cd->enum_work); |
| #endif |
| pt_debug(cd->dev, DL_INFO, |
| "%s: enum_work queued\n", __func__); |
| } else { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Enum not queued - startup_state = %d\n", |
| __func__, cd->startup_state); |
| } |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_queue_enum |
| * |
| * SUMMARY: Queues a TTDL enum within a mutex lock |
| * |
| * RETURN: void |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static void pt_queue_enum(struct pt_core_data *cd) |
| { |
| mutex_lock(&cd->system_lock); |
| pt_queue_enum_(cd); |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| static void remove_sysfs_and_modules(struct device *dev); |
| /******************************************************************************* |
| * FUNCTION: pt_queue_restart |
| * |
| * SUMMARY: Queues a TTDL restart within a mutex lock |
| * |
| * RETURN: void |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * remove_sysfs_module - True: remove all DUT relative sysfs nodes and modules |
| * False: will not perform remove action |
| ******************************************************************************/ |
| static void pt_queue_restart(struct pt_core_data *cd) |
| { |
| mutex_lock(&cd->system_lock); |
| if (cd->startup_state == STARTUP_NONE || |
| cd->startup_state == STARTUP_DONE) { |
| cd->startup_state = STARTUP_QUEUED; |
| |
| schedule_work(&cd->ttdl_restart_work); |
| pt_debug(cd->dev, DL_INFO, |
| "%s: pt_ttdl_restart queued\n", __func__); |
| } else { |
| pt_debug(cd->dev, DL_INFO, "%s: startup_state = %d\n", |
| __func__, cd->startup_state); |
| } |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: call_atten_cb |
| * |
| * SUMMARY: Iterate over attention list call the function that registered. |
| * |
| * RETURN: void |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * type - type of attention list |
| * mode - condition for execution |
| ******************************************************************************/ |
| static void call_atten_cb(struct pt_core_data *cd, |
| enum pt_atten_type type, int mode) |
| { |
| struct atten_node *atten, *atten_n; |
| |
| pt_debug(cd->dev, DL_DEBUG, "%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); |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: attention for '%s'", |
| __func__, dev_name(atten->dev)); |
| atten->func(atten->dev); |
| spin_lock(&cd->spinlock); |
| } |
| } |
| spin_unlock(&cd->spinlock); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: start_fw_upgrade |
| * |
| * SUMMARY: Calling "PT_ATTEN_LOADER" attention list that loader registered to |
| * start firmware upgrade. |
| * |
| * RETURN: |
| * 0 = success |
| * |
| * PARAMETERS: |
| * *data - pointer to core data |
| ******************************************************************************/ |
| static int start_fw_upgrade(void *data) |
| { |
| struct pt_core_data *cd = (struct pt_core_data *)data; |
| |
| call_atten_cb(cd, PT_ATTEN_LOADER, 0); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_put_device_into_easy_wakeup_ |
| * |
| * SUMMARY: Call the enter_easywake_state function and set the device into easy |
| * wake up state. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_put_device_into_easy_wakeup_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| u8 status = 0; |
| |
| mutex_lock(&cd->system_lock); |
| cd->wait_until_wake = 0; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = pt_hid_output_enter_easywake_state_(cd, |
| cd->easy_wakeup_gesture, &status); |
| if (rc || status == 0) |
| return -EBUSY; |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_put_device_into_deep_sleep_ |
| * |
| * SUMMARY: Call the set_power function and set the DUT to deep sleep |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = error |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_put_device_into_deep_sleep_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| rc = pt_hid_cmd_set_power_(cd, HID_POWER_SLEEP); |
| if (rc) |
| rc = -EBUSY; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_put_device_into_deep_standby_ |
| * |
| * SUMMARY: Call the set_power function and set the DUT to Deep Standby |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = error |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_put_device_into_deep_standby_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| rc = pt_hid_cmd_set_power_(cd, HID_POWER_STANDBY); |
| if (rc) |
| rc = -EBUSY; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_poweroff_device_ |
| * |
| * SUMMARY: Disable IRQ and HW power down the device. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_poweroff_device_(struct pt_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, 0); |
| if (rc < 0) |
| pt_debug(cd->dev, DL_ERROR, "%s: HW Power down fails r=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_sleep_ |
| * |
| * SUMMARY: Suspend the device with power off or deep sleep based on the |
| * configuration in the core platform data structure. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_sleep_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| /* |
| * Do nothing if system already sleeping or in progress of |
| * entering sleep. Proceed if awake or waking. |
| */ |
| if (cd->sleep_state == SS_SLEEP_ON || cd->sleep_state == SS_SLEEPING) |
| goto exit; |
| |
| mutex_lock(&cd->system_lock); |
| cd->sleep_state = SS_SLEEPING; |
| mutex_unlock(&cd->system_lock); |
| |
| /* Ensure watchdog and startup works stopped */ |
| pt_stop_wd_timer(cd); |
| cancel_work_sync(&cd->enum_work); |
| pt_stop_wd_timer(cd); |
| |
| // Ensure device is awake to send i2c command |
| pm_stay_awake(cd->dev); |
| |
| if (IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) |
| rc = pt_put_device_into_easy_wakeup_(cd); |
| else if (cd->cpdata->flags & PT_CORE_FLAG_POWEROFF_ON_SLEEP) |
| rc = pt_core_poweroff_device_(cd); |
| else if (cd->cpdata->flags & PT_CORE_FLAG_DEEP_STANDBY) |
| rc = pt_put_device_into_deep_standby_(cd); |
| else |
| rc = pt_put_device_into_deep_sleep_(cd); |
| |
| pm_relax(cd->dev); |
| |
| mutex_lock(&cd->system_lock); |
| if (rc == 0) { |
| cd->sleep_state = SS_SLEEP_ON; |
| } else { |
| cd->sleep_state = SS_SLEEP_OFF; |
| pt_start_wd_timer(cd); |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_sleep |
| * |
| * SUMMARY: Protected call to pt_core_sleep_ by exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_sleep(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_core_sleep_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail to release exclusive\n", __func__); |
| else |
| pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", |
| __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_wakeup_host |
| * |
| * SUMMARY: Check wake up report and call the PT_ATTEN_WAKE attention list. |
| * |
| * NOTE: TSG5 EasyWake and TSG6 EasyWake use different protocol. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_wakeup_host(struct pt_core_data *cd) |
| { |
| #ifndef EASYWAKE_TSG6 |
| /* TSG5 EasyWake */ |
| int rc = 0; |
| int event_id; |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| /* Validate report */ |
| if (size != 4 || cd->input_buf[2] != 4) |
| rc = -EINVAL; |
| |
| event_id = cd->input_buf[3]; |
| |
| pt_debug(cd->dev, DL_INFO, "%s: e=%d, rc=%d\n", |
| __func__, event_id, rc); |
| |
| if (rc) { |
| pt_core_sleep_(cd); |
| goto exit; |
| } |
| |
| /* attention WAKE */ |
| call_atten_cb(cd, PT_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] != PT_PIP_WAKEUP_REPORT_ID) { |
| rc = -EINVAL; |
| pt_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; |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%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, PT_ATTEN_WAKE, 0); |
| exit: |
| return rc; |
| #endif |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_touch_field |
| * |
| * SUMMARY: Function to calculate touch fields. The field refers to each element |
| * in the input report, such as "Number of Records", "X-axis". This function |
| * can calculate the value of element based on the bit offset, size and the |
| * max value of the element. |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *field - pointer to field calculation result |
| * size - size in bytes |
| * max - max value of result |
| * *data - pointer to input data to be parsed |
| * bofs - bit offset |
| ******************************************************************************/ |
| void pt_get_touch_field(struct device *dev, |
| int *field, int size, int max, u8 *data, int bofs) |
| { |
| int nbyte; |
| int next; |
| |
| for (nbyte = 0, *field = 0, next = 0; nbyte < size; nbyte++) { |
| pt_debug(dev, DL_DEBUG, |
| "%s: *field=%02X(%d) size=%d max=%08X data=%p data[%d]=%02X(%d) bofs=%d\n", |
| __func__, *field, *field, size, max, data, next, |
| data[next], data[next], bofs); |
| *field = *field + ((data[next] >> bofs) << (nbyte * 8)); |
| next++; |
| } |
| |
| if (max > 0) |
| *field &= max - 1; |
| |
| pt_debug(dev, DL_DEBUG, |
| "%s: *field=%02X(%d) size=%d max=%08X data=%p data[%d]=%02X(%d)\n", |
| __func__, *field, *field, size, max, data, next, |
| data[next], data[next]); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_tracking_heatmap_data |
| * |
| * SUMMARY: Move the valid tracking heatmap data from the input buffer into the |
| * system information structure, xy_mode and xy_data. |
| * - If TTHE_TUNER_SUPPORT is defined print the raw sensor data into |
| * the tthe_tuner sysfs node under the label "THM" |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_tracking_heatmap_data(struct pt_core_data *cd, |
| struct pt_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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_sensor_data |
| * |
| * SUMMARY: Move the valid sensor data from the input buffer into the system |
| * system information structure, xy_mode and xy_data. |
| * - If TTHE_TUNER_SUPPORT is defined print the raw sensor data into |
| * the tthe_tuner sysfs node under the label "sensor_monitor" |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_sensor_data(struct pt_core_data *cd, |
| struct pt_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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: hid_async_report_validate_crc |
| * |
| * SUMMARY: Validate CRC for pure hid asynchronous reports. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int hid_async_report_validate_crc(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| int hid_pl_len; |
| int hdr_sz = HID_RESP_HEADER_SIZE; |
| unsigned short calc_crc = 0; |
| unsigned short resp_crc = 0; |
| |
| hid_pl_len = get_unaligned_le16( |
| &cd->pt_async_rpt.buf[HID_RESP_PAYLOAD_LEN_OFFSET]); |
| |
| calc_crc = crc_ccitt_calculate( |
| &cd->pt_async_rpt.buf[HID_RESP_PAYLOAD_LEN_OFFSET], |
| hid_pl_len - 2); |
| |
| resp_crc = cd->pt_async_rpt.buf[hid_pl_len + hdr_sz - 2] << 8; |
| resp_crc |= cd->pt_async_rpt.buf[hid_pl_len + hdr_sz - 1]; |
| |
| if (resp_crc != calc_crc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: RTSD CRC ERR: calc=0x%04X rsp=0x%04X\n", |
| __func__, calc_crc, resp_crc); |
| rc = -EINVAL; |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: stitch_hid_async_report |
| * |
| * SUMMARY: Stitch the asynchronous reports in pure hid mode. HID reports can be |
| * segmented due to wMaxInputReportLength being less than the size of a full |
| * PIP3 response. When needed TTDL can stitch these segmented reports back into |
| * a contiguous PIP3 report. The segmented reports MUST begin with the First |
| * Report (FRpt) flag being set to 1 and TTDL will continue stitching until the |
| * More Report (MRpt) flag is 0. The PIP3 Length filed must reflect the length |
| * of the stitched report and the PIP3 CRC must match or the report is ignored. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *data_buf - pointer to the async reports buffer |
| * mrpt - the value of more reports bit |
| ******************************************************************************/ |
| static int stitch_hid_async_report(struct pt_core_data *cd) |
| { |
| static int input_sz; |
| int hdr_sz = HID_RESP_HEADER_SIZE; |
| int pkt_sz = cd->hid_core.hid_max_input_len; |
| int rc = 0; |
| u8 frpt = cd->pt_async_rpt.frpt; |
| static int hid_pl_len; |
| int data_len = 0; |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| if (frpt) { |
| if (input_sz) |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Unexpected start of new async report detected, previous report discarded\n", |
| __func__); |
| |
| /* Field Check: packet size */ |
| if (size != pkt_sz) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: bad message, first pcket size (%d) error\n", |
| __func__, size); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| memset(cd->pt_async_rpt.buf, 0, sizeof(cd->pt_async_rpt.buf)); |
| memcpy(cd->pt_async_rpt.buf, cd->input_buf, pkt_sz); |
| hid_pl_len = get_unaligned_le16( |
| &cd->pt_async_rpt.buf[HID_RESP_PAYLOAD_LEN_OFFSET]); |
| input_sz = pkt_sz; |
| } else { |
| if (input_sz == 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Start of async report was not detected. Current report discarded\n", |
| __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| data_len = input_sz + size - hdr_sz; |
| if (data_len > PT_MAX_PIP2_MSG_SIZE) { |
| pt_debug(cd->dev, DL_ERROR, "%s: error data length (%d)\n", |
| __func__, data_len); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| memcpy(&cd->pt_async_rpt.buf[input_sz], &cd->input_buf[hdr_sz], |
| size - hdr_sz); |
| input_sz = data_len; |
| } |
| |
| if (cd->pt_async_rpt.mrpt && |
| ((input_sz - hdr_sz) >= hid_pl_len)) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Extra report detected, stitched size greater than payload length\n", |
| __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| if (cd->pt_async_rpt.mrpt == 0) { |
| cd->pt_async_rpt.buf[0] = LOW_BYTE(input_sz); |
| cd->pt_async_rpt.buf[1] = HI_BYTE(input_sz); |
| |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->pt_async_rpt.buf, |
| input_sz, "async_sensor_data"); |
| |
| input_sz = 0; |
| hid_pl_len = 0; |
| |
| rc = hid_async_report_validate_crc(cd); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, "%s: CRC error\n", |
| __func__); |
| } |
| |
| exit: |
| if (rc) { |
| input_sz = 0; |
| hid_pl_len = 0; |
| memset(cd->pt_async_rpt.buf, 0, sizeof(cd->pt_async_rpt.buf)); |
| } |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: rebuild_hid_sdm_to_pip1 |
| * |
| * SUMMARY: Rebuild async_data_buf with PIP1 data format for SENSOR DATA MODE |
| * response. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int rebuild_hid_sdm_to_pip1(struct pt_core_data *cd) |
| { |
| int hid_pl_len = 0; |
| u8 cmd_id = 0; |
| int dp_count = 0; |
| u8 data_buf[PT_MAX_PIP1_MSG_SIZE]; |
| int pip1_pl_len; |
| int i = 0; |
| int rc = 0; |
| |
| hid_pl_len = get_unaligned_le16( |
| &cd->pt_async_rpt.buf[HID_RESP_PAYLOAD_LEN_OFFSET]); |
| cmd_id = cd->pt_async_rpt.buf[HID_RESP_CMD_ID_OFFSET] & 0x7F; |
| if (cmd_id != HID_CMD_ID_START_SENSOR_DATA_MODE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Cmd ID error. cmd_id=0x%02X\n", |
| __func__, cmd_id); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| memset(data_buf, 0, sizeof(data_buf)); |
| |
| /* |
| * Data point count is calculated by: |
| * ( Length of Payload - |
| * Payload header size (4) - |
| * Timestamp (2) - |
| * CRC(2)) / 6 |
| */ |
| dp_count = (hid_pl_len - 8) / 6; |
| pt_debug(cd->dev, DL_INFO, "%s: dp_count=%d\n", |
| __func__, dp_count); |
| if (dp_count > 25) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Overflow! Data Points Count=%d\n", |
| __func__, dp_count); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| pip1_pl_len = SENSOR_HEADER_SIZE + |
| PIP1_SENSOR_DATA_MODE_DP_COUNT * 6; |
| /* Set Length Field */ |
| cd->pt_async_rpt.buf[0] = LOW_BYTE(pip1_pl_len); |
| cd->pt_async_rpt.buf[1] = HI_BYTE(pip1_pl_len); |
| /* Report ID */ |
| cd->pt_async_rpt.buf[2] = PT_PIP_SENSOR_DATA_REPORT_ID; |
| /* Reserved */ |
| cd->pt_async_rpt.buf[3] = 0x00; |
| |
| /* Pad zeros between each set of data points */ |
| for (i = 0; i < 3; i++) |
| memcpy(&data_buf[PIP1_SENSOR_DATA_MODE_DP_COUNT * 2 * i], |
| &cd->pt_async_rpt.buf[10 + dp_count * 2 * i], |
| dp_count * 2); |
| |
| memcpy(&cd->pt_async_rpt.buf[SENSOR_HEADER_SIZE], data_buf, |
| PIP1_SENSOR_DATA_MODE_DP_COUNT * 6); |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_hid_sdm |
| * |
| * SUMMARY: Print the raw sensor data into the tthe_tuner sysfs node. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int move_hid_sdm(struct pt_core_data *cd) |
| { |
| u8 mrpt = 0; |
| int pip1_pl_len; |
| int rc = 0; |
| int length; |
| u8 cmd_id = cd->pt_async_rpt.cmd_id; |
| |
| if (cmd_id != HID_CMD_ID_START_SENSOR_DATA_MODE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Cmd ID error. cmd_id=0x%02X\n", |
| __func__, cmd_id); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| length = cd->hid_core.hid_max_input_len; |
| /* |
| * HID over USB does not require the two byte length field, so |
| * this should print from input_buf[2] but to keep both finger |
| * and pen reports the same the length is included |
| */ |
| if (cd->tthe_hid_usb_format == PT_TTHE_TUNER_FORMAT_HID_USB) |
| tthe_print(cd, &(cd->input_buf[2]), length - 2, |
| "HID-USB-SDM="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_I2C) |
| tthe_print(cd, cd->input_buf, length, |
| "HID-I2C-SDM="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP || |
| cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP) { |
| mrpt = cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x01; |
| pt_debug(cd->dev, DL_DEBUG, "%s: mrpt=%d\n", |
| __func__, mrpt); |
| |
| rc = stitch_hid_async_report(cd); |
| if (rc || mrpt) |
| goto exit; |
| |
| rc = rebuild_hid_sdm_to_pip1(cd); |
| if (rc) |
| goto exit; |
| pip1_pl_len = |
| get_unaligned_le16(&cd->pt_async_rpt.buf[0]); |
| tthe_print(cd, cd->pt_async_rpt.buf, |
| pip1_pl_len, "sensor_monitor="); |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_hid_thm |
| * |
| * SUMMARY: Print the Tracking Heatmap data into the tthe_tuner sysfs node. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int move_hid_thm(struct pt_core_data *cd) |
| { |
| u8 mrpt = 0; |
| int pip1_pl_len; |
| int rc = 0; |
| int length; |
| u8 cmd_id = cd->pt_async_rpt.cmd_id; |
| u8 data_buf[PT_MAX_PIP1_MSG_SIZE]; |
| |
| if (cmd_id != HID_CMD_ID_START_TRACKING_HEATMAP_MODE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Cmd ID error. cmd_id=0x%02X\n", |
| __func__, cmd_id); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| length = cd->hid_core.hid_max_input_len; |
| /* |
| * HID over USB does not require the two byte length field, so |
| * this should print from input_buf[2] but to keep both finger |
| * and pen reports the same the length is included |
| */ |
| if (cd->tthe_hid_usb_format == PT_TTHE_TUNER_FORMAT_HID_USB) |
| tthe_print(cd, &(cd->input_buf[2]), length - 2, |
| "HID-USB-THM="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_I2C) |
| tthe_print(cd, cd->input_buf, length, |
| "HID-I2C-THM="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP || |
| cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP) { |
| mrpt = cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x01; |
| pt_debug(cd->dev, DL_DEBUG, "%s: mrpt=%d\n", |
| __func__, mrpt); |
| |
| rc = stitch_hid_async_report(cd); |
| if (rc || mrpt) |
| goto exit; |
| |
| memset(data_buf, 0, sizeof(data_buf)); |
| /* |
| * PIP1 payload length is calculated by: |
| * ( HID Payload Length - |
| * Timestamp (2) - |
| * CRC(2)) |
| */ |
| pip1_pl_len = get_unaligned_le16( |
| &cd->pt_async_rpt.buf[HID_RESP_PAYLOAD_LEN_OFFSET]) |
| - 4; |
| memcpy(data_buf, |
| &cd->pt_async_rpt.buf[HID_RESP_SENSOR_DATA_OFFSET], |
| pip1_pl_len - SENSOR_HEADER_SIZE); |
| |
| memset(cd->pt_async_rpt.buf, 0, sizeof(cd->pt_async_rpt.buf)); |
| |
| /* Set Length Field */ |
| cd->pt_async_rpt.buf[0] = LOW_BYTE(PIP1_TRACKING_HEATMAP_SIZE); |
| cd->pt_async_rpt.buf[1] = HI_BYTE(PIP1_TRACKING_HEATMAP_SIZE); |
| /* Report ID */ |
| cd->pt_async_rpt.buf[2] = PT_PIP_TRACKING_HEATMAP_REPORT_ID; |
| /* Reserved */ |
| cd->pt_async_rpt.buf[3] = 0x00; |
| memcpy(&cd->pt_async_rpt.buf[SENSOR_HEADER_SIZE], data_buf, |
| pip1_pl_len - SENSOR_HEADER_SIZE); |
| tthe_print(cd, cd->pt_async_rpt.buf, |
| PIP1_TRACKING_HEATMAP_SIZE, "THM="); |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_hid_rtsd |
| * |
| * SUMMARY: Print the Realtime Signal Data into the tthe_tuner sysfs node. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int move_hid_rtsd(struct pt_core_data *cd) |
| { |
| u8 mrpt = 0; |
| int rc = 0; |
| int length; |
| u8 cmd_id = cd->pt_async_rpt.cmd_id; |
| |
| if (cmd_id != HID_CMD_ID_REALTIME_SIGNAL_MODE) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Cmd ID error. cmd_id=0x%02X\n", |
| __func__, cmd_id); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| length = cd->hid_core.hid_max_input_len; |
| /* |
| * HID over USB does not require the two byte length field, so |
| * this should print from input_buf[2] but to keep both finger |
| * and pen reports the same the length is included |
| */ |
| if (cd->tthe_hid_usb_format == PT_TTHE_TUNER_FORMAT_HID_USB) |
| tthe_print(cd, &(cd->input_buf[2]), length - 2, |
| "HID-USB-RTSD="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_I2C) |
| tthe_print(cd, cd->input_buf, length, |
| "HID-I2C-RTSD="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP || |
| cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP) { |
| mrpt = cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x01; |
| pt_debug(cd->dev, DL_DEBUG, "%s: mrpt=%d\n", |
| __func__, mrpt); |
| |
| rc = stitch_hid_async_report(cd); |
| if (rc || mrpt) |
| goto exit; |
| |
| tthe_print(cd, cd->pt_async_rpt.buf, length, "RTSD="); |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_hid_async_sensor_data |
| * |
| * SUMMARY: If TTHE_TUNER_SUPPORT is defined print the raw sensor data into |
| * the tthe_tuner sysfs node. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_hid_async_sensor_data(struct pt_core_data *cd) |
| { |
| u8 mrpt = 0; |
| u8 frpt = 0; |
| int rc = 0; |
| u8 cmd_id; |
| |
| mrpt = cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x01; |
| frpt = (cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x02) >> 1; |
| cd->pt_async_rpt.mrpt = mrpt; |
| cd->pt_async_rpt.frpt = frpt; |
| if (frpt) { |
| cmd_id = cd->input_buf[HID_RESP_CMD_ID_OFFSET] & 0x7F; |
| cd->pt_async_rpt.cmd_id = cmd_id; |
| } else |
| cmd_id = cd->pt_async_rpt.cmd_id; |
| |
| pt_debug(cd->dev, DL_INFO, "%s: Cmd[0x%02X], frpt=%d, mrpt=%d\n", |
| __func__, cmd_id, frpt, mrpt); |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| if (cmd_id == HID_CMD_ID_START_SENSOR_DATA_MODE) |
| rc = move_hid_sdm(cd); |
| else if (cmd_id == HID_CMD_ID_START_TRACKING_HEATMAP_MODE) |
| rc = move_hid_thm(cd); |
| else if (cmd_id == HID_CMD_ID_REALTIME_SIGNAL_MODE) |
| rc = move_hid_rtsd(cd); |
| else { |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: Wrong command id [0x%02X]\n", |
| __func__, cmd_id); |
| rc = -EINVAL; |
| } |
| #endif |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_button_data |
| * |
| * SUMMARY: Move the valid button data from the input buffer into the system |
| * system information structure, xy_mode and xy_data. |
| * - If TTHE_TUNER_SUPPORT is defined print the raw button data into |
| * the tthe_tuner sysfs node under the label "OpModeData" |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_button_data(struct pt_core_data *cd, |
| struct pt_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); |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)si->xy_mode, BTN_INPUT_HEADER_SIZE, |
| "xy_mode"); |
| |
| memcpy(si->xy_data, &cd->input_buf[BTN_INPUT_HEADER_SIZE], |
| BTN_REPORT_SIZE); |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)si->xy_data, BTN_REPORT_SIZE, |
| "xy_data"); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_touch_data_pip |
| * |
| * SUMMARY: Move the valid touch data from the input buffer into the system |
| * system information structure, xy_mode and xy_data. |
| * - If TTHE_TUNER_SUPPORT is defined print the raw touch data into |
| * the tthe_tuner sysfs node under the label "OpModeData" |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_touch_data_pip(struct pt_core_data *cd, struct pt_sysinfo *si) |
| { |
| int max_tch = si->sensing_conf_data.max_tch; |
| int num_cur_tch = 0; |
| int length; |
| int actual_tch_num; |
| struct pt_tch_abs_params *tch = &si->tch_hdr[PT_TCH_NUM]; |
| |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| #ifdef TTHE_TUNER_SUPPORT |
| if (size > 0) |
| tthe_print(cd, cd->input_buf, size, "OpModeData="); |
| #endif |
| |
| memcpy(si->xy_mode, cd->input_buf, si->desc.tch_header_size); |
| pt_pr_buf(cd->dev, DL_DEBUG, (u8 *)si->xy_mode, si->desc.tch_header_size, "xy_mode"); |
| |
| pt_get_touch_field(cd->dev, &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; |
| |
| actual_tch_num = (size - si->desc.tch_header_size) |
| / si->desc.tch_record_size; |
| |
| if (actual_tch_num < num_cur_tch) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: ATM - Malformed touch packet. actual_tch_num=%d, num_cur_tch=%d\n", |
| __func__, actual_tch_num, num_cur_tch); |
| num_cur_tch = actual_tch_num; |
| } |
| |
| length = num_cur_tch * si->desc.tch_record_size; |
| |
| memcpy(si->xy_data, &cd->input_buf[si->desc.tch_header_size], length); |
| pt_pr_buf(cd->dev, DL_DEBUG, (u8 *)si->xy_data, length, "xy_data"); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_touch_data_hid |
| * |
| * SUMMARY: Move the valid touch data from the input buffer into the system |
| * system information structure, xy_mode and xy_data. |
| * - If TTHE_TUNER_SUPPORT is defined print the raw touch data into |
| * the tthe_tuner sysfs node under the label "HID-USB", "HID-I2C" or |
| * "OpModeData" |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_touch_data_hid(struct pt_core_data *cd, |
| struct pt_sysinfo *si) |
| { |
| int max_tch = si->sensing_conf_data.max_tch; |
| int num_cur_tch = 0; |
| u16 hdr_sz; |
| u16 rec_sz; |
| int max_tch_per_packet; |
| static u8 remain_tch; |
| static u8 packet_no; |
| static u8 input_sz; |
| int length; |
| int rc = 0; |
| struct pt_tch_abs_params *tch = &si->tch_hdr[PT_TCH_NUM]; |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| hdr_sz = si->desc.tch_header_size; |
| rec_sz = si->desc.tch_record_size; |
| max_tch_per_packet = si->desc.max_tch_per_packet; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| length = hdr_sz + rec_sz * max_tch_per_packet; |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: touch report size=%d, len=%d, record=%d, max_tch=%d\n", |
| __func__, size, length, rec_sz, max_tch_per_packet); |
| /* |
| * HID over USB does not require the two byte length field, so |
| * this should print from input_buf[2] but to keep both finger |
| * and pen reports the same the length is included |
| */ |
| if (cd->tthe_hid_usb_format == PT_TTHE_TUNER_FORMAT_HID_USB) |
| tthe_print(cd, &(cd->input_buf[2]), length - 2, |
| "HID-USB-TOUCH="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_I2C) |
| tthe_print(cd, &(cd->input_buf[0]), length, |
| "HID-I2C-TOUCH="); |
| #endif |
| |
| /* |
| * The fifth parameter points to the location of "Number of Records": |
| * Pointer "xy_mode" points to start address of touch report header. |
| * The byte offset of "Number of Records" is 2 (tch->ofs, retrieved |
| * from report descriptor). Then the pointer "xy_mode + 3 (skip the |
| * two bytes length and 1 byte report ID) + tch->ofs" points to the |
| * location of "Number of Records". |
| */ |
| pt_get_touch_field(cd->dev, &num_cur_tch, tch->size, |
| tch->max, cd->input_buf + 3 + tch->ofs, tch->bofs); |
| |
| if (unlikely(num_cur_tch > max_tch)) |
| num_cur_tch = max_tch; |
| |
| /* According to Hybrid Mode touch report defined by Microsoft, |
| * if the touch number exceeds the max touch per packet (defined |
| * in report descriptor), the touch packet will be broken up into |
| * to multiple reports. Timestamp will be consistent across all |
| * touches in a single frame. The "Number of records" will have the |
| * total in the first report and be 0 in all subsequent reports that |
| * belong to the same frame. |
| */ |
| pt_debug(cd->dev, DL_INFO, "%s: max_tch=%d, packet_no=%d, num_cur_tch=%d\n", |
| __func__, max_tch, packet_no, num_cur_tch); |
| |
| if (packet_no == 0) { |
| input_sz = hdr_sz + num_cur_tch * rec_sz; |
| memset(cd->touch_buf, 0, sizeof(cd->touch_buf)); |
| memcpy(cd->touch_buf, cd->input_buf, input_sz); |
| if (num_cur_tch > max_tch_per_packet) { |
| remain_tch = num_cur_tch - max_tch_per_packet; |
| packet_no++; |
| rc = -EINVAL; |
| goto exit; |
| } |
| } else { |
| if (remain_tch <= max_tch_per_packet) { |
| memcpy(&cd->touch_buf[hdr_sz + |
| max_tch_per_packet * packet_no * rec_sz], |
| &(cd->input_buf[hdr_sz]), |
| rec_sz * remain_tch); |
| remain_tch = 0; |
| packet_no = 0; |
| rc = 0; |
| } else { |
| memcpy(&cd->touch_buf[hdr_sz + |
| max_tch_per_packet * packet_no * rec_sz], |
| &(cd->input_buf[hdr_sz]), |
| rec_sz * max_tch_per_packet); |
| remain_tch -= max_tch_per_packet; |
| packet_no++; |
| rc = -EINVAL; |
| goto exit; |
| } |
| } |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| /* Update pip packet length */ |
| cd->touch_buf[0] = input_sz & 0xff; |
| cd->touch_buf[1] = (input_sz & 0xff00) >> 8; |
| |
| /* |
| * For PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP and |
| * PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP mode, all touches |
| * should be combined into a single row for the tthe_tuner node. |
| */ |
| if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP || |
| cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP) |
| tthe_print(cd, cd->touch_buf, input_sz, |
| "OpModeData="); |
| #endif |
| memcpy(si->xy_mode, cd->touch_buf, hdr_sz); |
| pt_pr_buf(cd->dev, DL_DEBUG, (u8 *)si->xy_mode, hdr_sz, "xy_mode"); |
| |
| memcpy(si->xy_data, &cd->touch_buf[hdr_sz], input_sz - hdr_sz); |
| pt_pr_buf(cd->dev, DL_DEBUG, (u8 *)si->xy_data, input_sz - hdr_sz, "xy_data"); |
| |
| input_sz = 0; |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: move_hid_pen_data |
| * |
| * SUMMARY: TODO Move the valid pen data from the input buffer into the system |
| * system information structure, xy_mode and xy_data. |
| * - If TTHE_TUNER_SUPPORT is defined print the raw pen data into |
| * the tthe_tuner sysfs node under the label "HID" starting with the |
| * report ID. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * *si - pointer to the system information structure |
| ******************************************************************************/ |
| static int move_hid_pen_data(struct pt_core_data *cd, struct pt_sysinfo *si) |
| { |
| int size = get_unaligned_le16(&cd->input_buf[0]); |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| enum pt_pen_abs abs; |
| struct pt_pen pen; |
| int packet_len = 17; |
| int report_id = 1; |
| static int report_counter; |
| int event_id = 2; |
| int touch_id = 0; |
| int i = 0; |
| |
| if (size) { |
| /* |
| * HID over USB does not require the two byte length field, so |
| * this should print from input_buf[2] but to keep both finger |
| * and pen reports the same the length is included |
| */ |
| if (cd->tthe_hid_usb_format == PT_TTHE_TUNER_FORMAT_HID_USB) |
| tthe_print(cd, &(cd->input_buf[2]), size - 2, |
| "HID-USB-TOUCH="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_I2C || |
| cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP) |
| tthe_print(cd, &(cd->input_buf[0]), size, |
| "HID-I2C-TOUCH="); |
| else if (cd->tthe_hid_usb_format == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP) { |
| memset(cd->touch_buf, 0, sizeof(cd->touch_buf)); |
| |
| for (abs = PT_PEN_X; abs < PT_PEN_NUM_ABS; abs++) { |
| if (!si->pen_abs[abs].report) |
| continue; |
| pt_get_touch_field(cd->dev, &pen.abs[abs], |
| si->pen_abs[abs].size, |
| si->pen_abs[abs].max, |
| cd->input_buf + 3 + |
| si->pen_abs[abs].ofs, |
| si->pen_abs[abs].bofs); |
| pt_debug(cd->dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", |
| __func__, pt_pen_abs_string[abs], |
| pen.abs[abs], pen.abs[abs]); |
| } |
| /* pip packet length: 17 */ |
| cd->touch_buf[i++] = packet_len & 0xff; |
| cd->touch_buf[i++] = (packet_len & 0xff00) >> 8; |
| /* pip finger report id: 1*/ |
| cd->touch_buf[i++] = report_id; |
| /* Timestamp: 0 */ |
| cd->touch_buf[i++] = 0; |
| cd->touch_buf[i++] = 0; |
| /* LO: 0; Number of Records: 1*/ |
| cd->touch_buf[i++] = 1; |
| /* Report Counter:[0-3]; Noise Effects:0 */ |
| cd->touch_buf[i++] = (report_counter & 0x03) << 6; |
| /* Touch Type: Stylus (2)*/ |
| cd->touch_buf[i++] = PT_OBJ_STYLUS; |
| /* Tip: pen tip switch; Event ID: 2; Touch ID: 0 */ |
| cd->touch_buf[i++] = |
| ((pen.abs[PT_PEN_TS] & 0x01) << 7) | |
| ((event_id & 0x03) << 5) | |
| (touch_id & 0x1f); |
| /* X: Pen X */ |
| cd->touch_buf[i++] = pen.abs[PT_PEN_X] & 0xff; |
| cd->touch_buf[i++] = (pen.abs[PT_PEN_X] & 0xff00) >> 8; |
| /* Y: Pen Y */ |
| cd->touch_buf[i++] = pen.abs[PT_PEN_Y] & 0xff; |
| cd->touch_buf[i++] = (pen.abs[PT_PEN_Y] & 0xff00) >> 8; |
| /* Pressure: Pen pressure drop the least 4 bits */ |
| cd->touch_buf[i++] = (pen.abs[PT_PEN_P] & 0xff0) >> 4; |
| /* Major: Pen Tilt_X*/ |
| cd->touch_buf[i++] = pen.abs[PT_PEN_X_TILT] & 0xff; |
| /* Minor: Pen Tilt_Y*/ |
| cd->touch_buf[i++] = pen.abs[PT_PEN_Y_TILT] & 0xff; |
| /* Orientation: Btn2, Btn1*/ |
| cd->touch_buf[i++] = |
| (pen.abs[PT_PEN_BS] & 0x01) | |
| ((pen.abs[PT_PEN_2ND_BS] & 0x01) << 1); |
| |
| if (++report_counter > 3) |
| report_counter = 0; |
| |
| tthe_print(cd, cd->touch_buf, packet_len, |
| "OpModeData="); |
| } |
| } |
| #endif |
| memcpy(si->xy_mode, cd->input_buf, HID_PEN_INPUT_HEADER_SIZE); |
| memcpy(si->xy_data, cd->input_buf, size); |
| pt_pr_buf(cd->dev, DL_INFO, (u8 *)&(cd->input_buf[0]), size, "HID Pen"); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: parse_touch_input |
| * |
| * SUMMARY: Parse the touch report and take action based on the touch |
| * report_id. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * size - size of touch record |
| ******************************************************************************/ |
| static int parse_touch_input(struct pt_core_data *cd, int size) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| int report_id = cd->input_buf[2]; |
| int rc = -EINVAL; |
| |
| pt_debug(cd->dev, DL_DEBUG, "%s: Received touch report\n", |
| __func__); |
| if (!si->ready) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Need system information to parse touches\n", |
| __func__); |
| return 0; |
| } |
| |
| if (!si->xy_mode || !si->xy_data) |
| return rc; |
| |
| if (report_id == PT_PIP_TOUCH_REPORT_ID || |
| report_id == PT_HID_VS_FINGER_REPORT_ID) { |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_PIP) |
| rc = move_touch_data_pip(cd, si); |
| else { |
| rc = move_touch_data_hid(cd, si); |
| if (rc) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Skip report for the first touch packet\n", |
| __func__); |
| return 0; |
| } |
| } |
| } else if ((report_id == PT_HID_PEN_REPORT_ID || |
| report_id == PT_HID_VS_PEN_REPORT_ID) && |
| cd->protocol_mode != PT_PROTOCOL_MODE_PIP) |
| rc = move_hid_pen_data(cd, si); |
| else if (report_id == PT_PIP_CAPSENSE_BTN_REPORT_ID) |
| rc = move_button_data(cd, si); |
| else if (report_id == PT_PIP_SENSOR_DATA_REPORT_ID) |
| rc = move_sensor_data(cd, si); |
| else if (report_id == PT_PIP_TRACKING_HEATMAP_REPORT_ID) |
| rc = move_tracking_heatmap_data(cd, si); |
| else if (report_id == PT_HID_ASYNC_SENSOR_DATA_REPORT_ID) |
| rc = move_hid_async_sensor_data(cd); |
| |
| if (rc) |
| return rc; |
| |
| /* attention IRQ */ |
| call_atten_cb(cd, PT_ATTEN_IRQ, cd->mode); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: parse_command_input |
| * |
| * SUMMARY: Move the response data from the input buffer to the response buffer |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| * size - size of response data |
| ******************************************************************************/ |
| static int parse_command_input(struct pt_core_data *cd, int size) |
| { |
| pt_debug(cd->dev, DL_DEBUG, "%s: Received cmd interrupt\n", |
| __func__); |
| |
| memcpy(cd->response_buf, cd->input_buf, size); |
| #if defined(TTHE_TUNER_SUPPORT) && defined(TTDL_DIAGNOSTICS) |
| if (size && cd->show_tt_data) { |
| if (cd->pip2_prot_active) |
| tthe_print(cd, cd->input_buf, size, "TT_DATA_PIP2="); |
| else |
| tthe_print(cd, cd->input_buf, size, "TT_DATA="); |
| } |
| #endif |
| |
| mutex_lock(&cd->system_lock); |
| cd->hid_cmd_state = 0; |
| mutex_unlock(&cd->system_lock); |
| wake_up(&cd->wait_q); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_allow_enumeration |
| * |
| * SUMMARY: Determine if an enumeration or fully re-probe should perform when |
| * FW sentinel is seen. |
| * |
| * RETURN: |
| * true = allow enumeration or fully re-probe |
| * false = skip enumeration and fully re-probe |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static inline bool pt_allow_enumeration(struct pt_core_data *cd) |
| { |
| if ((cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) && |
| (!cd->hid_reset_cmd_state) && |
| (cd->core_probe_complete) && |
| (cd->hid_cmd_state != PIP1_CMD_ID_START_BOOTLOADER + 1) && |
| (cd->hid_cmd_state != PIP1_BL_CMD_ID_LAUNCH_APP + 1) && |
| (cd->hid_cmd_state != HID_CMD_ID_SWITCH_IMAGE + 1) && |
| (cd->hid_cmd_state != HID_CMD_ID_START_BOOTLOADER + 1) && |
| (cd->mode == PT_MODE_OPERATIONAL)) { |
| return true; |
| } |
| |
| if ((!cd->hid_reset_cmd_state) && |
| (cd->core_probe_complete) && |
| (cd->hid_cmd_state != PIP1_CMD_ID_START_BOOTLOADER + 1) && |
| (cd->hid_cmd_state != PIP1_BL_CMD_ID_LAUNCH_APP + 1) && |
| (cd->hid_cmd_state != HID_CMD_ID_SWITCH_IMAGE + 1) && |
| (cd->hid_cmd_state != HID_CMD_ID_START_BOOTLOADER + 1) && |
| (cd->active_dut_generation != DUT_PIP1_ONLY)) { |
| return true; |
| } |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Dissallow - %s=%d %s=%d %s=0x%02X %s=%d\n", |
| __func__, |
| "hid_reset_cmd_state(0)", cd->hid_reset_cmd_state, |
| "core_probe_complete(1)", cd->core_probe_complete, |
| "hid_cmd_state(Not 0x02 or 0x39)", cd->hid_cmd_state, |
| "active_dut_gen(0,2)", cd->active_dut_generation); |
| return false; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_is_touch_report |
| * |
| * SUMMARY: Determine if a report ID should be treated as a touch report |
| * |
| * RETURN: |
| * true = report ID is a touch report |
| * false = report ID is not a touch report |
| * |
| * PARAMETERS: |
| * id - Report ID |
| ******************************************************************************/ |
| static bool pt_is_touch_report(int id) |
| { |
| if (id == PT_PIP_TOUCH_REPORT_ID || |
| id == PT_HID_PEN_REPORT_ID || |
| id == PT_HID_VS_FINGER_REPORT_ID || |
| id == PT_HID_VS_PEN_REPORT_ID || |
| id == PT_PIP_CAPSENSE_BTN_REPORT_ID || |
| id == PT_PIP_SENSOR_DATA_REPORT_ID || |
| id == PT_PIP_TRACKING_HEATMAP_REPORT_ID || |
| id == PT_HID_ASYNC_SENSOR_DATA_REPORT_ID) |
| return true; |
| else |
| return false; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_parse_hid_report_to_pip |
| * |
| * SUMMARY: Parse the input report of HID SET_REPORT command to normal |
| * PIP1/PIP2 response and overwrite it to cd->input_buf. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * *remain_packets - pointer to store how many packets left to be received |
| * *report_type - pointer to store report type |
| ******************************************************************************/ |
| static int pt_parse_hid_report_to_pip(struct pt_core_data *cd, |
| u16 *remain_packets, int *report_type) |
| { |
| int rc = 0; |
| int cmd_id = 0; |
| u16 size = get_unaligned_le16(&cd->input_buf[0]); |
| u16 hid_buf_index = 0; |
| u16 packet_len = 0; |
| u16 pip_index = 0; |
| u16 calc_crc = 0; |
| u16 resp_crc = 0; |
| u16 calc_crc_ofs = 0; |
| u16 cur_payload_len = 0; |
| u16 actual_payload_len = 0; |
| static int hid_pl_len; |
| u8 rsp_bit = 0; |
| u8 mrpt = 0; |
| u8 frpt = 0; |
| u8 status = 0; |
| u16 pkt_num = 0; |
| enum pt_report_type cur_report_type = UNKNOWN_REPORT; |
| |
| mrpt = cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x01; |
| frpt = (cd->input_buf[HID_RESP_MRPT_OFFSET] & 0x02) >> 1; |
| |
| /* Should be first packet */ |
| if (frpt) { |
| if (cd->pt_hid_buf_op.offset) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Unexpected start of new report detected, previous report discarded\n", |
| __func__); |
| /* Clear structure pt_hid_report_buf_op */ |
| memset(&cd->pt_hid_buf_op, 0, |
| sizeof(struct pt_hid_report_buf_op)); |
| } |
| |
| hid_pl_len = get_unaligned_le16( |
| &cd->input_buf[HID_RESP_PAYLOAD_LEN_OFFSET]); |
| /* Both PIP1 & PIP2 cmd ID mask are 0x7F */ |
| cmd_id = cd->input_buf[HID_RESP_CMD_ID_OFFSET] & 0x7F; |
| /* Confirm report_type */ |
| if (cd->pt_hid_buf_op.report_type != UNKNOWN_REPORT) |
| cur_report_type = cd->pt_hid_buf_op.report_type; |
| else { |
| if (cd->force_pip3_report_type) |
| cd->report_type = PIP3_CMD_REPORT; |
| |
| cur_report_type = cd->report_type; |
| } |
| } else { |
| if (cd->pt_hid_buf_op.offset == 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Start of the report was not detected. Current report discarded\n", |
| __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| /* 'report_type' only can be confirmed by the first packet */ |
| cur_report_type = cd->pt_hid_buf_op.report_type; |
| } |
| |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: hid report type=%d, frpt_bit=%d, mrpt_bit=%d\n", |
| __func__, cur_report_type, frpt, mrpt); |
| |
| /* Bounds Checking */ |
| if ((size + cd->pt_hid_buf_op.offset) <= HID_BUF_OP_BUFFER_SIZE) { |
| memcpy(cd->pt_hid_buf_op.buf + cd->pt_hid_buf_op.offset, |
| &cd->input_buf[0], size); |
| cd->pt_hid_buf_op.offset += size; |
| } else { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Overflow! Packet size=%d, buf_offset=%d, buf_size=%d\n", |
| __func__, size, cd->pt_hid_buf_op.offset, |
| HID_BUF_OP_BUFFER_SIZE); |
| rc = -ENOMEM; |
| goto exit; |
| } |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: Packet size=%d, buf_offset=%d, buf_size=%d\n", |
| __func__, size, cd->pt_hid_buf_op.offset, |
| HID_BUF_OP_BUFFER_SIZE); |
| |
| /* Last packet/Only one packet */ |
| if (mrpt == 0) { |
| /* Total payload length */ |
| cur_payload_len = get_unaligned_le16( |
| &cd->pt_hid_buf_op.buf[HID_RESP_PAYLOAD_LEN_OFFSET]); |
| /* Parse HID Report and compose PIP response */ |
| memset(cd->input_buf, 0, sizeof(cd->input_buf)); |
| hid_buf_index = 0; /* Initial offset of pt_hid_buf_op */ |
| /* |
| * Confirm the initial offset(pip_index) of PIP response in |
| * 'input_buf', according to the spec 001-30009, 1 bytes offset |
| * for PIP1 message to rebuild: |
| * [ PIP1 ] [ PIP1 Embedded in HID ] |
| * 1 byte Length field(LSB) (Null) |
| *----------------------------------------------< offset |
| * 1 byte Length field(MSB) 1 byte length of payload(LSB) |
| * 1 byte REPORT_ID 1 byte length of payload(MSB) |
| * 1 byte RSVD <= 1 byte SEQ |
| * 1 byte command code 1 byte command code |
| * N byte Parameter Data N byte Parameter Data |
| * (Removed) 2 byte CRC |
| * |
| * No offset is needed for PIP2 message to rebuild: |
| * [ PIP2 ] [ PIP2 Embedded in HID ] |
| *----------------------------------------------< offset |
| * 2 byte Length field 2 byte length of payload |
| * 1 byte SEQ 1 byte SEQ |
| * 1 byte CMD ID <= 1 byte CMD ID |
| * N byte Report Body N byte Report Body |
| * 2 byte CRC 2 byte CRC2 byte CRC |
| */ |
| if (cur_report_type == PIP1_CMD_REPORT) |
| pip_index = 1; |
| else |
| pip_index = 0; |
| |
| calc_crc_ofs = pip_index; |
| |
| do { |
| packet_len = get_unaligned_le16( |
| &cd->pt_hid_buf_op.buf[hid_buf_index]); |
| if (((hid_buf_index + packet_len) > |
| cd->pt_hid_buf_op.offset) || |
| (packet_len <= HID_RESP_HEADER_SIZE)) { |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| /* |
| * 'Length of payload' is the entire payload. If the |
| * payload is greater than (wMaxInputLength - 7) and |
| * broken up across multiple reports. |
| */ |
| if ((packet_len - HID_RESP_HEADER_SIZE) < |
| cur_payload_len) |
| actual_payload_len = |
| packet_len - HID_RESP_HEADER_SIZE; |
| else |
| actual_payload_len = cur_payload_len; |
| |
| memcpy(&cd->input_buf[pip_index], |
| &cd->pt_hid_buf_op.buf[hid_buf_index + |
| HID_RESP_HEADER_SIZE], |
| actual_payload_len); |
| hid_buf_index += packet_len; |
| pip_index += actual_payload_len; |
| cur_payload_len -= actual_payload_len; |
| } while (hid_buf_index < cd->pt_hid_buf_op.offset); |
| |
| /* Verify HID response CRC */ |
| calc_crc = crc_ccitt_calculate(&cd->input_buf[calc_crc_ofs], |
| pip_index - calc_crc_ofs - PT_CRC_LEN); |
| resp_crc = cd->input_buf[pip_index - 2] << 8; |
| resp_crc |= cd->input_buf[pip_index - 1]; |
| if (resp_crc != calc_crc) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: CRC ERR: calc_crc=%04X, resp_crc=%04X\n", |
| __func__, calc_crc, resp_crc); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->pt_hid_buf_op.buf, |
| cd->pt_hid_buf_op.offset, "<<< ATM - HID buf"); |
| |
| status = cd->pt_hid_buf_op.buf[HID_RESP_STATUS_OFFSET]; |
| if (status) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error in PIP3 Cmd[0x%02X], status=%d\n", |
| __func__, cmd_id, status); |
| |
| if (cur_report_type == PIP1_CMD_REPORT) { |
| /* Set Length Field: Remove CRC field(2 bytes) */ |
| pip_index -= 2; |
| cd->input_buf[0] = LOW_BYTE(pip_index); |
| cd->input_buf[1] = HI_BYTE(pip_index); |
| /* Add Report ID */ |
| cd->input_buf[2] = PT_PIP_NON_HID_RESPONSE_ID; |
| /* Overwrite SEQ with RSVD(0x00) */ |
| cd->input_buf[3] = 0x00; |
| /* Get PIP cmd id from HID cmd id */ |
| cmd_id = cd->input_buf[4] & 0x7f; |
| cd->input_buf[4] = pt_get_pip_cmd_id_from_hid( |
| cmd_id, PIP1_TO_HID_CMD_ID_MAP); |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->input_buf, pip_index, |
| "<<< ATM - PIP1 buf"); |
| } else if (cur_report_type == PIP2_CMD_REPORT) { |
| /* Get PIP cmd_id from HID cmd_id */ |
| cmd_id = cd->input_buf[3] & 0x7f; |
| rsp_bit = cd->input_buf[3] & 0x80; |
| cd->input_buf[3] = pt_get_pip_cmd_id_from_hid( |
| cmd_id, PIP2_TO_HID_CMD_ID_MAP); |
| cd->input_buf[3] |= rsp_bit; |
| /* Update CRC(MSB) */ |
| calc_crc = |
| crc_ccitt_calculate(cd->input_buf, pip_index - 2); |
| cd->input_buf[pip_index - 2] = HI_BYTE(calc_crc); |
| cd->input_buf[pip_index - 1] = LOW_BYTE(calc_crc); |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->input_buf, pip_index, |
| "<<< ATM - PIP2 buf"); |
| } else if (cur_report_type == PIP3_CMD_REPORT) { |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->input_buf, pip_index, |
| "<<< ATM - PIP3 payload buf"); |
| } |
| } else { |
| cd->pt_hid_buf_op.report_type = cur_report_type; |
| cd->pt_hid_buf_op.last_remain_packet += 1; |
| pkt_num = cd->pt_hid_buf_op.last_remain_packet; |
| if ((cd->pt_hid_buf_op.offset - pkt_num * HID_RESP_HEADER_SIZE) |
| >= hid_pl_len) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Extra report detected, stitched size greater than payload length\n", |
| __func__); |
| rc = -EBADMSG; |
| goto exit; |
| } |
| } |
| |
| exit: |
| if (mrpt == 0 || rc) { |
| /* Clear structure pt_hid_report_buf_op */ |
| memset(&cd->pt_hid_buf_op, 0, |
| sizeof(struct pt_hid_report_buf_op)); |
| } |
| if (remain_packets) |
| *remain_packets = mrpt; |
| if (report_type) |
| *report_type = cur_report_type; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_parse_input |
| * |
| * SUMMARY: Parse the input data read from DUT due to IRQ. Handle data based |
| * on if its a response to a command or asynchronous touch data / reset |
| * sentinel. PIP2.x messages have additional error checking that is |
| * parsed (SEQ match from cmd to rsp, CRC valid). |
| * Look for special packets based on unique lengths: |
| * 0 bytes - APP(FW) reset sentinel or Gen5/6 BL sentinel |
| * 2 bytes - Empty buffer (PIP 1.7 and earlier) |
| * 11 bytes - possible PIP2.x reset sentinel (TAG and SEQ must = 0) |
| * 0xFFXX - Empty buffer (PIP 1.7+) |
| * Queue a startup after any asynchronous FW reset sentinel is seen, unless |
| * the initial probe has not yet been done. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_parse_input(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| int report_id = 0; |
| int tag_seq = 0; |
| int cmd_id = 0; |
| int is_command = 0; |
| int size = 0; |
| int report_type = UNKNOWN_REPORT; |
| bool touch_report = true; |
| unsigned short remain_packets = 0; |
| unsigned short calc_crc; |
| unsigned short resp_crc; |
| |
| cd->fw_sys_mode_in_standby_state = false; |
| size = get_unaligned_le16(&cd->input_buf[0]); |
| pt_debug(cd->dev, DL_DEBUG, "<<< %s: IRQ Triggered, read len [%d]\n", |
| __func__, size); |
| if (size <= PT_MAX_INPUT) |
| pt_pr_buf(cd->dev, DL_DEBUG, cd->input_buf, size, |
| "<<< Read buf"); |
| |
| if (size == 0 || IS_BL_SENTINEL(cd, size)) { |
| touch_report = false; |
| cd->hw_detected = true; |
| cd->bl_pip_ver_ready = false; |
| cd->app_pip_ver_ready = false; |
| if (size == 0) { |
| mutex_lock(&cd->system_lock); |
| cd->pip2_prot_active = false; |
| if (cd->active_dut_generation == DUT_PIP1_ONLY) { |
| /* |
| * For Gen5/6 this sentinel could be from |
| * the BL or FW. Attempt to set the correct |
| * mode based on the previous PIP command. |
| */ |
| if (cd->hid_cmd_state == |
| PIP1_BL_CMD_ID_LAUNCH_APP + 1) { |
| cd->mode = PT_MODE_OPERATIONAL; |
| cd->enum_status = |
| ENUM_STATUS_FW_RESET_SENTINEL; |
| } else if (cd->hid_cmd_state == |
| PIP1_CMD_ID_START_BOOTLOADER + 1 || |
| cd->hid_reset_cmd_state) { |
| cd->mode = PT_MODE_BOOTLOADER; |
| cd->enum_status = |
| ENUM_STATUS_BL_RESET_SENTINEL; |
| } else { |
| cd->mode = PT_MODE_UNKNOWN; |
| cd->enum_status = |
| ENUM_STATUS_START; |
| } |
| cd->fw_system_mode = FW_SYS_MODE_UNDEFINED; |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%s: ATM Gen5/6 %s sentinel received\n", |
| __func__, |
| (cd->mode == PT_MODE_OPERATIONAL ? |
| "FW" : |
| (cd->mode == PT_MODE_BOOTLOADER ? |
| "BL" : "Unknown"))); |
| } else { |
| cd->mode = PT_MODE_OPERATIONAL; |
| cd->fw_system_mode = FW_SYS_MODE_BOOT; |
| cd->enum_status = |
| ENUM_STATUS_FW_RESET_SENTINEL; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: ATM PT/TT FW sentinel received\n", |
| __func__); |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| if (pt_allow_enumeration(cd)) { |
| if (cd->active_dut_generation == DUT_UNKNOWN) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Queue Restart\n", __func__); |
| pt_queue_restart(cd); |
| } else { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Queue Enum\n", __func__); |
| pt_queue_enum(cd); |
| } |
| } else { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: ATM - Sentinel - No Queued Action\n", |
| __func__); |
| /* |
| * Sentinel "00 00" is expected for SWITCH_IMAGE |
| * command, so the code in this "if" is needed |
| * to avoid "timeout" issue after sending |
| * SWITCH_IMAGE command. |
| */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| cd->hid_cmd_state == |
| (HID_CMD_ID_SWITCH_IMAGE + 1)) { |
| if (cd->img_id == 1) { |
| mutex_lock(&cd->system_lock); |
| cd->mode = |
| PT_MODE_SECONDARY_IMAGE; |
| mutex_unlock(&cd->system_lock); |
| } |
| } |
| } |
| |
| } else { |
| /* Sentinel must be from TT/TC BL */ |
| mutex_lock(&cd->system_lock); |
| cd->pip2_prot_active = true; |
| cd->enum_status = ENUM_STATUS_BL_RESET_SENTINEL; |
| cd->mode = PT_MODE_BOOTLOADER; |
| cd->sysinfo.ready = false; |
| mutex_unlock(&cd->system_lock); |
| pt_debug(cd->dev, DL_INFO, |
| "%s: BL Reset sentinel received\n", __func__); |
| if (cd->flashless_dut && |
| cd->flashless_auto_bl == PT_ALLOW_AUTO_BL) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: BL to RAM for flashless DUT\n", |
| __func__); |
| kthread_run(start_fw_upgrade, cd, "pt_loader"); |
| } |
| } |
| mutex_lock(&cd->system_lock); |
| memcpy(cd->response_buf, cd->input_buf, 2); |
| if (!cd->hid_reset_cmd_state && !cd->hid_cmd_state) { |
| mutex_unlock(&cd->system_lock); |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Device Initiated Reset\n", __func__); |
| wake_up(&cd->wait_q); |
| return 0; |
| } |
| |
| cd->hid_reset_cmd_state = 0; |
| if (cd->hid_cmd_state == PIP1_CMD_ID_START_BOOTLOADER + 1 || |
| cd->hid_cmd_state == PIP1_BL_CMD_ID_LAUNCH_APP + 1 || |
| cd->hid_cmd_state == PIP1_CMD_ID_USER_CMD + 1 || |
| (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| (cd->hid_cmd_state == HID_CMD_ID_SWITCH_IMAGE + 1 || |
| cd->hid_cmd_state == HID_CMD_ID_START_BOOTLOADER + 1))) |
| cd->hid_cmd_state = 0; |
| wake_up(&cd->wait_q); |
| mutex_unlock(&cd->system_lock); |
| return 0; |
| } else if (size == 2 || size >= PT_PIP_1P7_EMPTY_BUF) { |
| /* |
| * This debug message below is used by PBATS to calculate the |
| * time from the last lift off IRQ to when FW exits LFT mode. |
| */ |
| touch_report = false; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: DUT - Empty buffer detected\n", __func__); |
| return 0; |
| } else if (size > PT_MAX_INPUT) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: DUT - Unexpected len field in active bus data!\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| /* Blindly initialize the report_id */ |
| report_id = cd->input_buf[HID_RESP_REPORT_ID_OFFSET]; |
| |
| /* Check if it is HID Wrapper report */ |
| if (PT_IS_HID_WRAP_PIP_REPORT(report_id)) { |
| #ifdef TTHE_TUNER_SUPPORT |
| if (cd->tthe_hid_usb_format == PT_TTHE_TUNER_FORMAT_HID_USB) |
| tthe_print(cd, &(cd->input_buf[2]), size - 2, |
| "HID-USB-RSP="); |
| else |
| tthe_print(cd, &(cd->input_buf[0]), size, |
| "HID-I2C-RSP="); |
| #endif |
| rc = pt_parse_hid_report_to_pip(cd, &remain_packets, |
| &report_type); |
| if (rc || (remain_packets != 0)) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Parse HID report rc=%d, remain=%d\n", |
| __func__, rc, remain_packets); |
| return rc; |
| } |
| |
| /* |
| * Update "report_id" & "size" since input_buf has been |
| * overwritten, however, PIP3 type only includes the payload |
| * and so the report_id does not need to be saved. |
| */ |
| if (report_type != PIP3_CMD_REPORT) |
| report_id = cd->input_buf[PIP1_RESP_REPORT_ID_OFFSET]; |
| size = get_unaligned_le16(&cd->input_buf[0]); |
| } |
| |
| if (cd->pip2_prot_active) { |
| /* |
| * Verify response is actually a PIP2 response. PIP2 does |
| * have a report_id so first decode it as PIP1 to verify if |
| * the incoming report was actually a touch report. Decoding |
| * the report_id however, is not enough as a PIP2 response |
| * could have a valid report_id in the TAG/SEQ byte. |
| */ |
| |
| tag_seq = cd->input_buf[PIP2_RESP_SEQUENCE_OFFSET] & 0x0F; |
| calc_crc = crc_ccitt_calculate(cd->input_buf, size - 2); |
| resp_crc = cd->input_buf[size - 2] << 8; |
| resp_crc |= cd->input_buf[size - 1]; |
| |
| if ((cd->pip2_cmd_tag_seq != tag_seq) && |
| (resp_crc != calc_crc) && |
| (pt_is_touch_report(report_id))) { |
| pt_debug(cd->dev, DL_INFO, "%s: %s 0x%02X %s\n", |
| __func__, "Received Touch report_id", |
| report_id, "when expecting a PIP2 report"); |
| touch_report = true; |
| } else { |
| /* PIP2 does not have a report id, hard code it */ |
| report_id = 0x00; |
| is_command = 1; |
| touch_report = false; |
| cmd_id = cd->input_buf[PIP2_RESP_COMMAND_ID_OFFSET]; |
| } |
| } else { |
| cmd_id = cd->input_buf[PIP1_RESP_COMMAND_ID_OFFSET]; |
| } |
| if (report_id == PT_PIP_WAKEUP_REPORT_ID) { |
| pt_wakeup_host(cd); |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, cd->input_buf, size, "TT_WAKEUP="); |
| #endif |
| return 0; |
| } |
| |
| mod_timer_pending(&cd->watchdog_timer, jiffies + |
| msecs_to_jiffies(cd->watchdog_interval)); |
| |
| /* |
| * If it is PIP2 response, the report_id has been set to 0, |
| * so it will not be parsed as a touch packet. |
| */ |
| if (!pt_is_touch_report(report_id)) { |
| is_command = 1; |
| touch_report = false; |
| } |
| |
| if (unlikely(is_command)) { |
| pt_debug(cd->dev, DL_DEBUG, |
| "%s: pip2=%d report_id=0x%02X, pip_cmd_code=0x%02X\n", |
| __func__, cd->pip2_prot_active, report_id, |
| (cmd_id & PIP2_CMD_COMMAND_ID_MASK)); |
| parse_command_input(cd, size); |
| return 0; |
| } |
| |
| if (touch_report) |
| parse_touch_input(cd, size); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_read_input |
| * |
| * SUMMARY: Reads incoming data off of the active bus |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_read_input(struct pt_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| int rc = 0; |
| int t; |
| int retry = PT_BUS_READ_INPUT_RETRY_COUNT; |
| |
| /* |
| * Workaround for easywake failure |
| * Interrupt for easywake, wait for bus controller to wake |
| */ |
| mutex_lock(&cd->system_lock); |
| if (IS_EASY_WAKE_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(2000)); |
| #ifdef TTDL_DIAGNOSTICS |
| if (IS_TMO(t)) { |
| cd->bus_transmit_error_count++; |
| pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); |
| pt_debug(dev, DL_ERROR, |
| "%s: !!!I2C Transmission Error %d\n", |
| __func__, |
| cd->bus_transmit_error_count); |
| } |
| #else |
| if (IS_TMO(t)) |
| pt_queue_enum(cd); |
| #endif /* TTDL_DIAGNOSTICS */ |
| goto read; |
| } |
| } |
| mutex_unlock(&cd->system_lock); |
| |
| read: |
| memset(cd->input_buf, 0, sizeof(cd->input_buf)); |
| /* Try reading up to 'retry' times */ |
| while (retry-- != 0) { |
| rc = pt_adap_read_default_nosize(cd, cd->input_buf, |
| PT_MAX_INPUT); |
| if (!rc) { |
| pt_debug(dev, DL_DEBUG, |
| "%s: Read input successfully\n", __func__); |
| goto read_exit; |
| } |
| usleep_range(1000, 2000); |
| } |
| pt_debug(dev, DL_ERROR, |
| "%s: Error getting report, rc=%d\n", __func__, rc); |
| |
| read_exit: |
| return rc; |
| } |
| |
| static irqreturn_t pt_top_irq(int irq, void *data) |
| { |
| struct pt_core_data *cd = data; |
| |
| cd->timestamp = ktime_get(); |
| return IRQ_WAKE_THREAD; |
| } |
| EXPORT_SYMBOL_GPL(pt_top_irq); |
| /******************************************************************************* |
| * FUNCTION: pt_bottom_irq |
| * |
| * SUMMARY: Process all detected interrupts |
| * |
| * RETURN: |
| * IRQ_HANDLED - Finished processing the interrupt |
| * |
| * PARAMETERS: |
| * irq - IRQ number |
| * *handle - pointer to core data struct |
| ******************************************************************************/ |
| irqreturn_t pt_bottom_irq(int irq, void *handle) |
| { |
| struct pt_core_data *cd = handle; |
| int rc = 0; |
| |
| #ifdef TTDL_PTVIRTDUT_SUPPORT |
| if (!cd->route_bus_virt_dut) { |
| if (!pt_check_irq_asserted(cd)) |
| return IRQ_HANDLED; |
| } |
| #else |
| if (!pt_check_irq_asserted(cd)) |
| return IRQ_HANDLED; |
| #endif /* TTDL_PTVIRTDUT_SUPPORT */ |
| |
| rc = pt_read_input(cd); |
| #ifdef TTDL_DIAGNOSTICS |
| cd->irq_count++; |
| |
| /* Used to calculate T-Refresh */ |
| if (cd->t_refresh_active) { |
| if (cd->t_refresh_count == 0) { |
| cd->t_refresh_time = jiffies; |
| cd->t_refresh_count++; |
| } else if (cd->t_refresh_count < cd->t_refresh_total) { |
| cd->t_refresh_count++; |
| } else { |
| cd->t_refresh_active = 0; |
| cd->t_refresh_time = |
| jiffies_to_msecs(jiffies - cd->t_refresh_time); |
| } |
| } |
| #endif /* TTDL_DIAGNOSTICS */ |
| |
| if (!rc) |
| pt_parse_input(cd); |
| |
| return IRQ_HANDLED; |
| } |
| EXPORT_SYMBOL_GPL(pt_bottom_irq); |
| |
| #ifdef PT_AUX_BRIDGE_ENABLED |
| /******************************************************************************* |
| * FUNCTION: pt_trigger_ttdl_irq |
| * |
| * SUMMARY: API for other kernel drivers to artificially trigger a TTDL IRQ |
| * |
| * IMPROVE - dpmux should likely call pt_get_core_data once and make a |
| * copy of cd so that the function only needs to be called once. |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: none |
| * |
| ******************************************************************************/ |
| int pt_trigger_ttdl_irq(void) |
| { |
| struct pt_core_data *cd; |
| char *core_name = PT_DEFAULT_CORE_ID; |
| |
| cd = pt_get_core_data(core_name); |
| if (!cd) { |
| pr_err("%s: No Device\n", __func__); |
| return -ENODEV; |
| } |
| pt_irq(cd->irq, (void *)cd); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(pt_trigger_ttdl_irq); |
| #endif |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_subscribe_attention |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to subscribe themselves into the TTDL attention list |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * type - attention type enum |
| * *id - ID of the module calling this function |
| * *func - callback function pointer to be called when notified |
| * mode - attention mode |
| ******************************************************************************/ |
| int _pt_subscribe_attention(struct device *dev, |
| enum pt_atten_type type, char *id, int (*func)(struct device *), |
| int mode) |
| { |
| struct pt_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; |
| |
| pt_debug(cd->dev, DL_INFO, "%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); |
| pt_debug(cd->dev, DL_INFO, "%s: %s=%p %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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_unsubscribe_attention |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to unsubscribe themselves from the TTDL attention list |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * type - attention type enum |
| * *id - ID of the module calling this function |
| * *func - function pointer |
| * mode - attention mode |
| ******************************************************************************/ |
| int _pt_unsubscribe_attention(struct device *dev, |
| enum pt_atten_type type, char *id, int (*func)(struct device *), |
| int mode) |
| { |
| struct pt_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) { |
| pt_debug(cd->dev, DL_DEBUG, "%s: %s=%p %s=%d\n", |
| __func__, |
| "unsub for atten->dev", atten->dev, |
| "atten->mode", atten->mode); |
| list_del(&atten->node); |
| spin_unlock(&cd->spinlock); |
| kfree(atten); |
| return 0; |
| } |
| } |
| spin_unlock(&cd->spinlock); |
| |
| return -ENODEV; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_exclusive |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request exclusive access |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * timeout_ms - timeout to wait for exclusive access |
| ******************************************************************************/ |
| static int _pt_request_exclusive(struct device *dev, |
| int timeout_ms) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return request_exclusive(cd, (void *)dev, timeout_ms); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_release_exclusive |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to release exclusive access |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int _pt_release_exclusive(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return release_exclusive(cd, (void *)dev); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_reset |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request the DUT to be reset. Function returns err if refused or |
| * timeout occurs (Note: core uses fixed timeout period). |
| * |
| * NOTE: Function blocks until ISR occurs. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * protect - flag to call protected or non-protected |
| ******************************************************************************/ |
| static int _pt_request_reset(struct device *dev, int protect) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| int rc; |
| |
| rc = pt_dut_reset(cd, protect); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, "%s: Error on h/w reset r=%d\n", |
| __func__, rc); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_enum |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request TTDL to queue an enum. This function will return err |
| * if refused; if no error then enum has completed and system is in |
| * normal operation mode. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * wait - boolean to determine if to wait for startup event |
| ******************************************************************************/ |
| static int _pt_request_enum(struct device *dev, bool wait) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_queue_enum(cd); |
| |
| if (wait) |
| wait_event_timeout(cd->wait_q, |
| cd->startup_state == STARTUP_DONE, |
| msecs_to_jiffies(PT_REQUEST_ENUM_TIMEOUT)); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_sysinfo |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request the pointer to the system information structure. This |
| * function will return NULL if sysinfo has not been acquired from the |
| * DUT yet. |
| * |
| * RETURN: Pointer to the system information struct |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| struct pt_sysinfo *_pt_request_sysinfo(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| int rc = 0; |
| |
| /* Attempt to get sysinfo if not ready and panel_id is from XY pin */ |
| if ((cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) && |
| !cd->sysinfo.ready) { |
| rc = pt_hid_output_get_sysinfo_(cd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error getting sysinfo rc=%d\n", |
| __func__, rc); |
| } |
| } |
| |
| if (cd->sysinfo.ready) |
| return &cd->sysinfo; |
| |
| return NULL; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_loader_pdata |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request the pointer to the loader platform data |
| * |
| * RETURN: Pointer to the loader platform data struct |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static struct pt_loader_platform_data *_pt_request_loader_pdata( |
| struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| return cd->pdata->loader_pdata; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_start_wd |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request to start the TTDL watchdog |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int _pt_request_start_wd(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_start_wd_timer(cd); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_stop_wd |
| * |
| * SUMMARY: Function pointer included in core_cmds to allow other modules |
| * to request to stop the TTDL watchdog |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| static int _pt_request_stop_wd(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_stop_wd_timer(cd); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_launch_app |
| * |
| * SUMMARY: Sends the PIP2 EXECUTE command to launch the APP and then wait for |
| * the FW reset sentinel to indicate the function succeeded. |
| * |
| * NOTE: Calling this function when the DUT is in Application mode WILL result |
| * in a timeout delay and with the DUT being reset with an XRES. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * protect - flag to call protected or non-protected |
| ******************************************************************************/ |
| static int pt_pip2_launch_app(struct device *dev, int protect) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| u16 actual_read_len; |
| u16 tmp_enum_status = cd->enum_status; |
| u8 read_buf[12]; |
| u8 status; |
| int time = 0; |
| int rc = 0; |
| |
| mutex_lock(&cd->system_lock); |
| cd->enum_status = ENUM_STATUS_START; |
| pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); |
| mutex_unlock(&cd->system_lock); |
| |
| rc = _pt_request_pip2_send_cmd(dev, protect, |
| PIP2_CMD_ID_EXECUTE, NULL, 0, read_buf, |
| &actual_read_len); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: PIP2 EXECUTE cmd failed rc = %d\n", |
| __func__, rc); |
| } else { |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| |
| /* Test for no or invalid image in FLASH, no point to reset */ |
| if (status == PIP2_RSP_ERR_INVALID_IMAGE) { |
| rc = status; |
| goto exit; |
| } |
| |
| /* Any other boot failure */ |
| if (status != 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: FW did not EXECUTE, status = %d\n", |
| __func__, status); |
| rc = status; |
| } |
| } |
| |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to launch APP, XRES DUT rc = %d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| while ((cd->enum_status == ENUM_STATUS_START) && time < 240) { |
| msleep(20); |
| pt_debug(cd->dev, DL_INFO, "%s: wait for %d for enum=0x%04X\n", |
| __func__, time, cd->enum_status); |
| time += 20; |
| } |
| if (cd->enum_status == ENUM_STATUS_START) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: TMO waiting for FW reset sentinel\n", __func__); |
| rc = -ETIME; |
| } |
| |
| exit: |
| if (cd->enum_status == ENUM_STATUS_START) { |
| /* Reset to original state because we could be stuck in BL */ |
| mutex_lock(&cd->system_lock); |
| cd->enum_status = tmp_enum_status; |
| mutex_unlock(&cd->system_lock); |
| } |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip2_launch_app |
| * |
| * SUMMARY: Calls pt_pip2_launch_app() when configured to. A small delay is |
| * inserted to ensure the reset has allowed the BL reset sentinel to be |
| * consumed. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| ******************************************************************************/ |
| static int _pt_request_pip2_launch_app(struct device *dev, int protect) |
| { |
| return pt_pip2_launch_app(dev, protect); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_wait_for_enum_state |
| * |
| * SUMMARY: Loops for up to timeout waiting for the startup_status to reach |
| * the state passed in or STARTUP_STATUS_COMPLETE whichever comes first |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data structure |
| * timeout - timeout for how long to wait |
| * state - enum state to wait for |
| ******************************************************************************/ |
| static int _pt_request_wait_for_enum_state(struct device *dev, int timeout, |
| int state) |
| { |
| int rc = 0; |
| int t; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| t = wait_event_timeout(cd->wait_q, |
| (cd->enum_status & state) || (cd->enum_status & 0x0100), |
| msecs_to_jiffies(timeout)); |
| if (IS_TMO(t)) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: TMO waiting for enum state 0x%04X in %dms\n", |
| __func__, state, timeout); |
| pt_debug(cd->dev, DL_WARN, |
| "%s: enum state reached 0x%04X\n", |
| __func__, cd->enum_status); |
| rc = -ETIME; |
| } else if (cd->enum_status & state) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Enum state reached: enum=0x%04X in %dms\n", |
| __func__, cd->enum_status, |
| (t == 1) ? timeout : (timeout - jiffies_to_msecs(t))); |
| } else { |
| if (t == 1) { |
| pt_debug( |
| cd->dev, DL_ERROR, |
| "%s: TMO waiting for enum state 0x%04X in %dms\n", |
| __func__, state, timeout); |
| rc = -ETIME; |
| } else { |
| pt_debug( |
| cd->dev, DL_ERROR, |
| "%s: Enum state 0x%04X not reached in %dms\n", |
| __func__, state, timeout - jiffies_to_msecs(t)); |
| rc = -EINVAL; |
| } |
| pt_debug(cd->dev, DL_INFO, "%s: enum state reached 0x%04X\n", |
| __func__, cd->enum_status); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_wake_device_from_deep_sleep_ |
| * |
| * SUMMARY: Call the set_power function and set the DUT to wake up from |
| * deep sleep. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = error |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_wake_device_from_deep_sleep_( |
| struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = pt_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; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_wake_device_from_easy_wake_ |
| * |
| * SUMMARY: Wake up device from Easy-Wake state. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_wake_device_from_easy_wake_(struct pt_core_data *cd) |
| { |
| mutex_lock(&cd->system_lock); |
| cd->wait_until_wake = 1; |
| mutex_unlock(&cd->system_lock); |
| wake_up(&cd->wait_q); |
| msleep(20); |
| |
| return pt_core_wake_device_from_deep_sleep_(cd); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_restore_parameters_ |
| * |
| * SUMMARY: This function sends all RAM parameters stored in the linked list |
| * back to the DUT |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| ******************************************************************************/ |
| static int pt_restore_parameters_(struct pt_core_data *cd) |
| { |
| struct param_node *param; |
| int rc = 0; |
| |
| if (!(cd->cpdata->flags & PT_CORE_FLAG_RESTORE_PARAMETERS)) |
| goto exit; |
| |
| spin_lock(&cd->spinlock); |
| list_for_each_entry(param, &cd->param_list, node) { |
| spin_unlock(&cd->spinlock); |
| pt_debug(cd->dev, DL_INFO, "%s: Parameter id:%d value:%d\n", |
| __func__, param->id, param->value); |
| rc = pt_pip_set_param_(cd, param->id, |
| param->value, param->size); |
| if (rc) |
| goto exit; |
| spin_lock(&cd->spinlock); |
| } |
| spin_unlock(&cd->spinlock); |
| exit: |
| return rc; |
| } |
| |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_exit_bl_ |
| * |
| * SUMMARY: Attempt to exit the BL and run the application, taking into account |
| * a DUT that may not have flash and will need FW to be loaded into RAM |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * *status_str - pointer to optional status string buffer |
| * buf_size - size of status_str buffer |
| ******************************************************************************/ |
| int pt_pip2_exit_bl_(struct pt_core_data *cd, u8 *status_str, int buf_size) |
| { |
| int rc; |
| int wait_time = 0; |
| u8 mode = PT_MODE_UNKNOWN; |
| bool load_status_str = false; |
| |
| /* Check and correct "mode" if need when protocol mode is HID */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| rc = _pt_detect_dut_mode(cd->dev, &mode); |
| if (!rc && cd->mode != mode) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: cd->mode(%d) doesn't match detected(%d)\n", |
| __func__, cd->mode, mode); |
| cd->mode = mode; |
| } |
| } |
| |
| /* |
| * Below function has protective call to ensure no enum is still on |
| * going, while this kind of protection should be applied widely in |
| * future (TODO). |
| */ |
| rc = pt_pip2_get_mode_sysmode(cd, &mode, NULL); |
| |
| if (status_str && buf_size >= 50) |
| load_status_str = true; |
| |
| if (mode == PT_MODE_BOOTLOADER) { |
| if (cd->flashless_dut == 1) { |
| rc = pt_hw_hard_reset(cd); |
| } else { |
| rc = pt_pip2_launch_app(cd->dev, |
| PT_CORE_CMD_UNPROTECTED); |
| if (rc == PIP2_RSP_ERR_INVALID_IMAGE) { |
| pt_debug(cd->dev, DL_ERROR, "%s: %s = %d\n", |
| __func__, "Invalid image in FLASH rc", rc); |
| } else if (rc) { |
| pt_debug(cd->dev, DL_ERROR, "%s: %s = %d\n", |
| __func__, "Failed to launch app rc", rc); |
| } |
| } |
| |
| if (!rc) { |
| if (cd->flashless_dut == 1) { |
| /* Wait for BL to complete before enum */ |
| rc = _pt_request_wait_for_enum_state(cd->dev, |
| 4000, ENUM_STATUS_FW_RESET_SENTINEL); |
| if (rc && load_status_str) { |
| strcpy(status_str, |
| "No FW sentinel after BL"); |
| goto exit; |
| } |
| } |
| |
| /* |
| * If the host wants to interact with the FW or do a |
| * forced calibration, the FW must be out of BOOT mode |
| * and the system information must have been retrieved. |
| * Reaching the FW_OUT_OF_BOOT state guarantees both. |
| * If, however, the enumeration does not reach this |
| * point, the DUT may still be in APP mode so test |
| * for all conditions. |
| */ |
| rc = _pt_request_wait_for_enum_state(cd->dev, 4500, |
| ENUM_STATUS_FW_OUT_OF_BOOT); |
| if (!rc || cd->enum_status >= |
| ENUM_STATUS_FW_RESET_SENTINEL) { |
| mutex_lock(&cd->system_lock); |
| cd->mode = PT_MODE_OPERATIONAL; |
| mutex_unlock(&cd->system_lock); |
| } |
| if (rc) { |
| pt_debug(cd->dev, DL_WARN, "%s: %s: 0x%04X\n", |
| __func__, "Failed to enum with DUT", |
| cd->enum_status); |
| if (load_status_str && !(cd->enum_status & |
| ENUM_STATUS_FW_OUT_OF_BOOT)) { |
| strcpy(status_str, |
| "FW Stuck in Boot mode"); |
| goto exit; |
| } |
| } |
| |
| /* |
| * The comming FW sentinel could wake up the event |
| * queue, which has chance to be taken by next command |
| * wrongly. Following delay is a workaround to avoid |
| * this issue for most situations. |
| */ |
| msleep(20); |
| |
| pt_start_wd_timer(cd); |
| } |
| if (load_status_str) { |
| if (rc == PIP2_RSP_ERR_INVALID_IMAGE) |
| strcpy(status_str, |
| "Failed - Invalid image in FLASH"); |
| else if (!rc) |
| strcpy(status_str, |
| "Entered APP from BL mode"); |
| else |
| strcpy(status_str, |
| "Failed to enter APP from BL mode"); |
| } |
| } else if (mode == PT_MODE_OPERATIONAL) { |
| mutex_lock(&cd->system_lock); |
| cd->mode = mode; |
| mutex_unlock(&cd->system_lock); |
| rc = pt_poll_for_fw_exit_boot_mode(cd, 1500, &wait_time); |
| if (load_status_str) { |
| if (!rc) |
| strcpy(status_str, |
| "Already in APP mode"); |
| else |
| strcpy(status_str, |
| "Already in APP mode - FW stuck in Boot mode"); |
| } |
| } else if (rc || mode == PT_MODE_UNKNOWN) { |
| mutex_lock(&cd->system_lock); |
| cd->mode = mode; |
| mutex_unlock(&cd->system_lock); |
| if (load_status_str) |
| strcpy(status_str, "Failed to determine active mode"); |
| } |
| |
| exit: |
| if (!rc) |
| pt_start_wd_timer(cd); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_exit_bl |
| * |
| * SUMMARY: Wrapper function for _pt_pip2_exit_bl that guarantees exclusive |
| * access. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * *status_str - pointer to optional status string buffer |
| * buf_size - size of status_str buffer |
| ******************************************************************************/ |
| int pt_pip2_exit_bl(struct pt_core_data *cd, u8 *status_str, int buf_size) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", __func__, |
| cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_pip2_exit_bl_(cd, status_str, buf_size); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", |
| __func__); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _fast_startup |
| * |
| * SUMMARY: Perform fast startup after resume device by power on/off stratergy. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| ******************************************************************************/ |
| static int _fast_startup(struct pt_core_data *cd) |
| { |
| int retry = PT_CORE_STARTUP_RETRY_COUNT; |
| int rc = 0; |
| u8 mode = PT_MODE_UNKNOWN; |
| struct pt_hid_desc hid_desc; |
| int wait_time = 0; |
| |
| memset(&hid_desc, 0, sizeof(hid_desc)); |
| |
| reset: |
| if (retry != PT_CORE_STARTUP_RETRY_COUNT) |
| pt_debug(cd->dev, DL_INFO, "%s: Retry %d\n", __func__, |
| PT_CORE_STARTUP_RETRY_COUNT - retry); |
| |
| if (cd->active_dut_generation == DUT_PIP1_ONLY) { |
| pt_debug(cd->dev, DL_INFO, "%s: PIP1 Enumeration start\n", |
| __func__); |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on getting HID descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = pt_get_mode(cd, &hid_desc); |
| |
| if (cd->mode == PT_MODE_BOOTLOADER) { |
| pt_debug(cd->dev, DL_INFO, "%s: Bootloader mode\n", |
| __func__); |
| rc = pt_hid_output_bl_launch_app_(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on launch app r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on getting HID descriptor r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = pt_get_mode(cd, &hid_desc); |
| if (cd->mode == PT_MODE_BOOTLOADER) { |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| } |
| |
| cd->enum_status |= ENUM_STATUS_GET_DESC; |
| cd->enum_status |= ENUM_STATUS_FW_OUT_OF_BOOT; |
| } else { |
| pt_debug(cd->dev, DL_INFO, "%s: PIP2 Enumeration start\n", |
| __func__); |
| |
| if (retry == PT_CORE_STARTUP_RETRY_COUNT) { |
| /* Wait for any sentinel before first try */ |
| rc = _pt_request_wait_for_enum_state( |
| cd->dev, 150, |
| ENUM_STATUS_BL_RESET_SENTINEL | |
| ENUM_STATUS_FW_RESET_SENTINEL); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: No Sentinel detected rc = %d\n", |
| __func__, rc); |
| } else |
| pt_flush_bus_if_irq_asserted(cd, |
| PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| rc = pt_pip2_get_mode_sysmode_(cd, &mode, NULL); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Get mode failed, mode unknown\n", |
| __func__); |
| mode = PT_MODE_UNKNOWN; |
| } |
| cd->mode = mode; |
| |
| if (cd->mode == PT_MODE_BOOTLOADER) { |
| pt_debug(cd->dev, DL_INFO, "%s: Bootloader mode\n", |
| __func__); |
| rc = pt_pip2_exit_bl_(cd, NULL, 0); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s Failed to exit bootloader\n", |
| __func__); |
| msleep(50); |
| rc = -ENODEV; |
| if (retry--) |
| goto reset; |
| goto exit; |
| } else { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Exit bootloader succesfully\n", |
| __func__); |
| } |
| |
| if (cd->mode != PT_MODE_OPERATIONAL) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: restore mode failure mode = %d\n", |
| __func__, cd->mode); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| } |
| cd->enum_status |= ENUM_STATUS_GET_DESC; |
| } |
| |
| /* FW cannot handle most PIP cmds when it is still in BOOT mode */ |
| rc = _pt_poll_for_fw_exit_boot_mode(cd, 500, &wait_time); |
| if (!rc) { |
| cd->enum_status |= ENUM_STATUS_FW_OUT_OF_BOOT; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Exit FW BOOT Mode after %dms\n", |
| __func__, wait_time); |
| } else { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: FW stuck in BOOT Mode after %dms\n", |
| __func__, wait_time); |
| goto exit; |
| } |
| |
| if (!cd->sysinfo.ready) { |
| rc = pt_hid_output_get_sysinfo_(cd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on getting sysinfo r=%d\n", |
| __func__, rc); |
| if (retry--) |
| goto reset; |
| goto exit; |
| } |
| } |
| cd->enum_status |= ENUM_STATUS_GET_SYS_INFO; |
| |
| rc = pt_restore_parameters_(cd); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: failed to restore parameters rc=%d\n", |
| __func__, rc); |
| else |
| cd->enum_status |= ENUM_STATUS_RESTORE_PARM; |
| |
| exit: |
| cd->enum_status |= ENUM_STATUS_COMPLETE; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_poweron_device_ |
| * |
| * SUMMARY: Power on device, enable IRQ, and then perform a fast startup. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_poweron_device_(struct pt_core_data *cd) |
| { |
| struct device *dev = cd->dev; |
| int rc = 0; |
| |
| /* |
| * After power on action, the chip can general FW sentinel. It can |
| * trigger an enumeration without hid_reset_cmd_state flag. Since the |
| * _fast_startup() can perform a quick enumeration too, here doesn't |
| * need another enumeration. |
| */ |
| mutex_lock(&cd->system_lock); |
| cd->enum_status = ENUM_STATUS_START; |
| cd->hid_reset_cmd_state = 1; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = cd->cpdata->power(cd->cpdata, 1, dev, 0); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, "%s: HW Power up fails r=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| if (!cd->irq_enabled) { |
| cd->irq_enabled = true; |
| enable_irq(cd->irq); |
| } |
| |
| /* TBD: following function doesn't update startup_status */ |
| rc = _fast_startup(cd); |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_wake_device_from_deep_standby_ |
| * |
| * SUMMARY: Reset device, and then trigger a full enumeration. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_wake_device_from_deep_standby_(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = pt_dut_reset_and_wait(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, "%s: Error on h/w reset r=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| |
| rc = _fast_startup(cd); |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_wake_ |
| * |
| * SUMMARY: Resume the device with a power on or wake from deep sleep based on |
| * the configuration in the core platform data structure. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_wake_(struct pt_core_data *cd) |
| { |
| int rc = 0; |
| |
| /* |
| * Do nothing if system already awake or in progress of waking. |
| * Proceed if sleeping or entering sleep state. |
| */ |
| if (cd->sleep_state == SS_SLEEP_OFF || cd->sleep_state == SS_WAKING) |
| goto exit; |
| |
| mutex_lock(&cd->system_lock); |
| cd->sleep_state = SS_WAKING; |
| mutex_unlock(&cd->system_lock); |
| |
| // Ensure device is awake to send i2c command |
| pm_stay_awake(cd->dev); |
| |
| if (!(cd->cpdata->flags & PT_CORE_FLAG_SKIP_RESUME)) { |
| if (IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) |
| rc = pt_core_wake_device_from_easy_wake_(cd); |
| else if (cd->cpdata->flags & PT_CORE_FLAG_POWEROFF_ON_SLEEP) |
| rc = pt_core_poweron_device_(cd); |
| else if (cd->cpdata->flags & PT_CORE_FLAG_DEEP_STANDBY) |
| rc = pt_core_wake_device_from_deep_standby_(cd); |
| else /* Default action to exit DeepSleep */ |
| rc = pt_core_wake_device_from_deep_sleep_(cd); |
| } |
| |
| pm_relax(cd->dev); |
| |
| mutex_lock(&cd->system_lock); |
| if (rc == 0) |
| cd->sleep_state = SS_SLEEP_OFF; |
| else |
| cd->sleep_state = SS_SLEEP_ON; |
| mutex_unlock(&cd->system_lock); |
| |
| pt_start_wd_timer(cd); |
| |
| exit: |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_wake |
| * |
| * SUMMARY: Protected call to pt_core_wake_ by exclusive access to the DUT. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer to core data |
| ******************************************************************************/ |
| static int pt_core_wake(struct pt_core_data *cd) |
| { |
| int rc; |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| return rc; |
| } |
| |
| rc = pt_core_wake_(cd); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", |
| __func__); |
| else |
| pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", |
| __func__); |
| |
| return rc; |
| } |
| |
| static void on_mcu_display_state_change(const struct touch_callback_data *data) |
| { |
| struct pt_core_data *core_data = pt_get_core_data(PT_DEFAULT_CORE_ID); |
| |
| pr_info("[pt_i2c_adapter] %s: received messages from mcu\n", __func__); |
| |
| if (core_data == NULL) { |
| pr_err("[pt_i2c_adapter] %s: Core data is NULL\n", __func__); |
| return; |
| } |
| |
| if (unlikely(!data)) { |
| pr_err("[pt_i2c_adapter] %s: Callback data is NULL\n", __func__); |
| return; |
| } |
| |
| switch (data->display_mode) { |
| case MCU_DISPLAY_ON: { |
| mutex_lock(&core_data->md.mt_lock); |
| core_data->md.touch_state = WAKING_BY_ACTIVE_TOUCH; |
| mutex_unlock(&core_data->md.mt_lock); |
| pt_core_wake(core_data); |
| break; |
| } |
| case MCU_DISPLAY_IDLE: |
| case MCU_DISPLAY_OFF: |
| case MCU_DISPLAY_SLEEP: |
| pt_core_sleep(core_data); |
| break; |
| default: |
| pr_err("[pt_i2c_adapter] %s: no action for display_mode:%d\n", __func__, |
| data->display_mode); |
| break; |
| } |
| } |
| |
| void pt_release_queued_input_event(struct work_struct *work) |
| { |
| struct pt_input_event *event; |
| struct pt_input_event *next_event; |
| struct pt_core_data *core_data = pt_get_core_data(PT_DEFAULT_CORE_ID); |
| |
| mutex_lock(&core_data->md.mt_lock); |
| if (core_data->md.touch_state == QUEUEING_TOUCH_EVENTS) { |
| pt_debug(core_data->dev, DL_INFO, "%s: releasing queued input events\n", __func__); |
| list_for_each_entry_safe(event, next_event, &touch_event_list_head.list, list) { |
| if (event->type == EV_SYNC_FRAME) |
| input_mt_sync_frame(core_data->md.input); |
| else if (event->type == EV_TIMESTAMP) |
| input_set_timestamp(core_data->md.input, event->timestamp); |
| else if (event->type == EV_SLOT_STATE) |
| input_mt_report_slot_state(core_data->md.input, event->code, |
| event->value); |
| else |
| input_event(core_data->md.input, event->type, event->code, |
| event->value); |
| |
| list_del(&event->list); |
| kfree(event); |
| } |
| } |
| core_data->md.touch_state = NORMAL; |
| mutex_unlock(&core_data->md.mt_lock); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_get_ic_crc_ |
| * |
| * SUMMARY: This function retrieves the config block CRC |
| * |
| * NOTE: The post condition of calling this function will be that the DUT will |
| * be in SCANNINING mode if no failures occur |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * ebid - enumerated block ID |
| ******************************************************************************/ |
| static int pt_get_ic_crc_(struct pt_core_data *cd, u8 ebid) |
| { |
| struct pt_sysinfo *si = &cd->sysinfo; |
| int rc = 0; |
| u8 status; |
| u16 calculated_crc = 0; |
| u16 stored_crc = 0; |
| |
| rc = pt_pip_suspend_scanning_(cd); |
| if (rc) |
| goto error; |
| |
| rc = pt_pip_verify_config_block_crc_(cd, ebid, &status, |
| &calculated_crc, &stored_crc); |
| if (rc) |
| goto exit; |
| |
| if (status) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID && |
| calculated_crc != stored_crc) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| si->ttconfig.crc = stored_crc; |
| |
| exit: |
| pt_pip_resume_scanning_(cd); |
| error: |
| pt_debug(cd->dev, DL_INFO, |
| "%s: CRC: ebid:%d, calc:0x%04X, stored:0x%04X, rc=%d\n", |
| __func__, ebid, calculated_crc, stored_crc, rc); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_read_gpio |
| * |
| * SUMMARY: Sends a PIP2 READ_GPIO command to the DUT and stores the 32 gpio |
| * bits into the passed in variable |
| * |
| * NOTE: PIP2 READ_GPIO command is only supported in bootloader |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *status - pointer to where the command response status is stored |
| * *gpio - pointer to device attributes structure |
| ******************************************************************************/ |
| static int pt_pip2_read_gpio(struct device *dev, u8 *status, u32 *gpio) |
| { |
| int rc = 0; |
| u16 actual_read_len; |
| u8 read_buf[12]; |
| u8 tmp_status = 0; |
| u8 index = PIP2_RESP_STATUS_OFFSET; |
| |
| memset(read_buf, 0, ARRAY_SIZE(read_buf)); |
| rc = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_READ_GPIO, |
| NULL, 0, read_buf, &actual_read_len); |
| |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Failed to send PIP2 READ_GPIO cmd\n", __func__); |
| rc = -ECOMM; |
| } else { |
| tmp_status = read_buf[index]; |
| } |
| |
| if (status) |
| *status = tmp_status; |
| |
| if (!rc && gpio && (tmp_status == 0)) { |
| *gpio = ((read_buf[index + 4] << 24) | |
| (read_buf[index + 3] << 16) | |
| (read_buf[index + 2] << 8) | |
| (read_buf[index + 1])); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_get_panel_id_by_gpio |
| * |
| * SUMMARY: Wrapper function to call pt_pip2_read_gpio() to get panel ID |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * *pid - pointer to store panel ID |
| ******************************************************************************/ |
| static int _pt_pip2_get_panel_id_by_gpio(struct pt_core_data *cd, u8 *pid) |
| { |
| u32 gpio_value = 0; |
| u8 status = 0; |
| u8 panel_id = PANEL_ID_NOT_ENABLED; |
| int rc = 0; |
| |
| if (!pid) |
| return -ENOMEM; |
| |
| rc = pt_pip2_read_gpio(cd->dev, &status, &gpio_value); |
| if (!rc) { |
| if (status == 0) { |
| panel_id = (gpio_value & PT_PANEL_ID_BITMASK) >> |
| PT_PANEL_ID_SHIFT; |
| pt_debug(cd->dev, DL_INFO, "%s: %s=0x%02X %s=0x%08X\n", |
| __func__, |
| "BL mode PID", panel_id, "gpio", gpio_value); |
| *pid = panel_id; |
| } else { |
| pt_debug(cd->dev, DL_ERROR, "%s: %s=%d\n", |
| __func__, |
| "BL read gpio failed status", status); |
| } |
| } else { |
| pt_debug(cd->dev, DL_ERROR, "%s: %s=%d\n", |
| __func__, |
| "BL read gpio failed status", status); |
| } |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_enum_with_pip1_dut_ |
| * |
| * SUMMARY: This function does the full enumeration of the PIP1 DUT with TTDL. |
| * The core data (cd) startup_status will store, as a bitmask, each |
| * state of the enumeration process. The startup will be attempted |
| * PT_CORE_STARTUP_RETRY_COUNT times before giving up. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * reset - Flag to reset the PIP1 only DUT before attempting to enumerate |
| * *status - poionter to store the enum status bitmask flags |
| ******************************************************************************/ |
| static int pt_enum_with_pip1_dut_(struct pt_core_data *cd, bool reset, |
| u32 *enum_status) |
| { |
| int try = 1; |
| int rc = 0; |
| bool detected = false; |
| u8 return_data[8]; |
| u8 pid = PANEL_ID_NOT_ENABLED; |
| struct pt_hid_desc hid_desc; |
| |
| memset(&hid_desc, 0, sizeof(hid_desc)); |
| pt_debug(cd->dev, DL_INFO, "%s: Start enum... 0x%04X, reset=%d\n", |
| __func__, cd->enum_status, reset); |
| pt_stop_wd_timer(cd); |
| |
| reset: |
| if (try > 1) { |
| pt_debug(cd->dev, DL_WARN, "%s: DUT Enum Attempt %d\n", |
| __func__, try); |
| |
| /* Clear enum status except BL/FW sentinel status */ |
| cd->enum_status &= (ENUM_STATUS_BL_RESET_SENTINEL | |
| ENUM_STATUS_FW_RESET_SENTINEL); |
| } |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| |
| pt_debug(cd->dev, DL_INFO, |
| "%s: PIP1 Enumeration start\n", __func__); |
| |
| /* Only reset the DUT after the first try */ |
| if (reset || (try > 1)) { |
| /* |
| * Reset hardware only for Legacy parts. Skip for TT/TC |
| * parts because if the FW image was loaded directly |
| * to SRAM issueing a reset ill wipe out what was just |
| * loaded. |
| */ |
| rc = pt_dut_reset_and_wait(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on h/w reset r=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| /* sleep to allow FW to be launched if available */ |
| msleep(120); |
| } |
| |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error getting HID Descriptor r=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| rc = -EIO; |
| goto exit; |
| } |
| detected = true; |
| cd->mode = pt_get_mode(cd, &hid_desc); |
| |
| /* |
| * Most systems do not use an XY pin as the panel_id and so |
| * the BL is used to retrieve the panel_id, however for |
| * systems that do use an XY pin, the panel_id MUST be |
| * retrieved from the system information when running FW |
| * (done below) and so this section of code is skipped. |
| * Entering the BL here is only needed on XY_PIN systems. |
| */ |
| if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { |
| if (cd->mode == PT_MODE_OPERATIONAL) { |
| rc = pt_pip_start_bootloader_(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on start bootloader r=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| cd->mode = PT_MODE_BOOTLOADER; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Bootloader mode\n", __func__); |
| } |
| |
| rc = pt_hid_output_bl_get_information_(cd, return_data); |
| if (!rc) { |
| cd->bl_info.ready = true; |
| cd->bl_info.chip_id = |
| get_unaligned_le16(&return_data[2]); |
| pt_debug(cd->dev, DL_INFO, "%s: chip ID %04X\n", |
| __func__, cd->bl_info.chip_id); |
| } else { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: failed to get chip ID, r=%d\n", |
| __func__, rc); |
| } |
| |
| rc = pt_hid_output_bl_get_panel_id_(cd, &pid); |
| mutex_lock(&cd->system_lock); |
| if (!rc) { |
| cd->pid_for_loader = pid; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Panel ID: 0x%02X\n", |
| __func__, cd->pid_for_loader); |
| } else { |
| cd->pid_for_loader = PANEL_ID_NOT_ENABLED; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Read Failed, disable Panel ID: 0x%02X\n", |
| __func__, cd->pid_for_loader); |
| } |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| /* Exit BL due to XY_PIN case or any other cause to be in BL */ |
| if (cd->mode == PT_MODE_BOOTLOADER) { |
| rc = pt_hid_output_bl_launch_app_(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on launch app r=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| |
| /* sleep to allow FW to be launched if available */ |
| msleep(120); |
| |
| /* Ensure the DUT is now in Application mode */ |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error getting HID Desc r=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| rc = -EIO; |
| goto exit; |
| } |
| cd->mode = pt_get_mode(cd, &hid_desc); |
| if (cd->mode == PT_MODE_BOOTLOADER) { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Error confiming exit BL\n", |
| __func__); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| rc = -EINVAL; |
| goto exit; |
| } |
| } |
| pt_debug(cd->dev, DL_INFO, "%s: Operational mode\n", __func__); |
| cd->mode = PT_MODE_OPERATIONAL; |
| |
| /* |
| * PIP1 only DUTs currently use a hard coded HID Desc and there |
| * is no PIP support to query when FW exits BOOT mode. |
| */ |
| *enum_status |= ENUM_STATUS_GET_DESC; |
| *enum_status |= ENUM_STATUS_FW_OUT_OF_BOOT; |
| |
| pt_init_pip_report_fields(cd); |
| *enum_status |= ENUM_STATUS_GET_RPT_DESC; |
| |
| if (!cd->features.easywake) |
| cd->easy_wakeup_gesture = PT_CORE_EWG_NONE; |
| |
| pt_debug(cd->dev, DL_INFO, "%s: Reading sysinfo\n", __func__); |
| rc = pt_hid_output_get_sysinfo_(cd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on getting sysinfo r=%d\n", __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| *enum_status |= ENUM_STATUS_GET_SYS_INFO; |
| pt_debug(cd->dev, DL_INFO, "%s pt Prot Version: %d.%d\n", |
| __func__, |
| cd->sysinfo.ttdata.pip_ver_major, |
| cd->sysinfo.ttdata.pip_ver_minor); |
| |
| rc = pt_get_ic_crc_(cd, PT_TCH_PARM_EBID); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: DUT Config block CRC failure rc=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| } else { |
| *enum_status |= ENUM_STATUS_GET_CFG_CRC; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Config CRC Retrieved (0x%04X)\n", |
| __func__, *enum_status); |
| } |
| |
| rc = pt_restore_parameters_(cd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Failed to restore parameters rc=%d\n", |
| __func__, rc); |
| } else { |
| *enum_status |= ENUM_STATUS_RESTORE_PARM; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: RAM parameters restored (0x%04X)\n", |
| __func__, *enum_status); |
| } |
| |
| if (cd->panel_id_support & PT_PANEL_ID_BY_MFG_DATA) { |
| rc = pt_get_pid_from_mfg_data_(cd, &pid); |
| mutex_lock(&cd->system_lock); |
| if (!rc) { |
| cd->pid_for_loader = pid; |
| pt_debug(cd->dev, DL_INFO, "%s: Panel ID: 0x%02X\n", __func__, |
| cd->pid_for_loader); |
| } else { |
| pt_debug(cd->dev, DL_WARN, "%s: Read Failed, disable Panel ID: 0x%02X\n", |
| __func__, cd->pid_for_loader); |
| } |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| call_atten_cb(cd, PT_ATTEN_STARTUP, 0); |
| cd->watchdog_interval = PT_WATCHDOG_TIMEOUT; |
| cd->startup_retry_count = 0; |
| |
| exit: |
| /* Generate the HW Version of the connected DUT and store in cd */ |
| _legacy_generate_hw_version(cd, cd->hw_version); |
| pt_debug(cd->dev, DL_WARN, "%s: HW Version: %s\n", __func__, |
| cd->hw_version); |
| |
| if (!detected) |
| rc = -ENODEV; |
| |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Completed Enumeration rc=%d On Attempt %d\n", |
| __func__, rc, try); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_enum_with_pip2_dut_ |
| * |
| * SUMMARY: This function does the full enumeration of the PIP2 DUT with TTDL. |
| * The core data (cd) startup_status will store, as a bitmask, each |
| * state of the enumeration process. The startup will be attempted |
| * PT_CORE_STARTUP_RETRY_COUNT times before giving up. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * reset - Flag to reset the PIP1 only DUT before attempting to enumerate |
| * *status - poionter to store the enum status bitmask flags |
| ******************************************************************************/ |
| static int pt_enum_with_pip2_dut_(struct pt_core_data *cd, bool reset, |
| u32 *enum_status) |
| { |
| int try = 1; |
| int rc = 0; |
| int wait_time = 0; |
| bool detected = false; |
| u8 mode = PT_MODE_UNKNOWN; |
| u8 protocol_mode = PT_PROTOCOL_MODE_PIP; |
| u8 pid = PANEL_ID_NOT_ENABLED; |
| u8 sys_mode = FW_SYS_MODE_UNDEFINED; |
| struct pt_hid_desc hid_desc; |
| bool get_hid_desc = false; |
| |
| memset(&hid_desc, 0, sizeof(hid_desc)); |
| pt_debug(cd->dev, DL_INFO, "%s: Start enum... 0x%04X, reset=%d\n", |
| __func__, cd->enum_status, reset); |
| pt_stop_wd_timer(cd); |
| |
| reset: |
| if (try > 1) { |
| pt_debug(cd->dev, DL_WARN, "%s: DUT Enum Attempt %d\n", |
| __func__, try); |
| |
| /* Clear enum status except BL/FW sentinel status */ |
| cd->enum_status &= (ENUM_STATUS_BL_RESET_SENTINEL | |
| ENUM_STATUS_FW_RESET_SENTINEL); |
| } |
| pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); |
| |
| /* Generation is PIP2 Capable */ |
| pt_debug(cd->dev, DL_INFO, |
| "%s: PIP2 Enumeration start\n", __func__); |
| |
| if (cd->protocol_mode != PT_PROTOCOL_MODE_PIP) { |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| if (rc) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Get hid descriptor failed\n", |
| __func__); |
| else |
| get_hid_desc = true; |
| } |
| |
| rc = pt_pip2_get_status_(cd); |
| if (!rc || (rc == PIP2_RSP_ERR_INIT_FAILURE)) { |
| mode = cd->dut_status.mode; |
| detected = true; |
| |
| if (cd->dut_status.fw_system_mode == |
| FW_SYS_MODE_SECONDARY_IMAGE) { |
| cd->mode = mode; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Enumeration not supported from secondary image\n", |
| __func__); |
| goto exit; |
| } |
| } else { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Get mode failed, mode unknown\n", |
| __func__); |
| mode = PT_MODE_UNKNOWN; |
| } |
| |
| cd->mode = mode; |
| |
| switch (cd->mode) { |
| case PT_MODE_OPERATIONAL: |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Operational mode\n", __func__); |
| protocol_mode = cd->protocol_mode; |
| if (cd->app_pip_ver_ready == false) { |
| rc = pt_pip2_get_version_(cd); |
| if (!rc) |
| cd->app_pip_ver_ready = true; |
| else { |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| } |
| break; |
| case PT_MODE_BOOTLOADER: |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Bootloader mode\n", __func__); |
| if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { |
| rc = _pt_pip2_get_panel_id_by_gpio(cd, &pid); |
| mutex_lock(&cd->system_lock); |
| if (!rc) { |
| cd->pid_for_loader = pid; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Panel ID: 0x%02X\n", |
| __func__, cd->pid_for_loader); |
| } else { |
| cd->pid_for_loader = |
| PANEL_ID_NOT_ENABLED; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Read Failed, disable Panel ID: 0x%02X\n", |
| __func__, cd->pid_for_loader); |
| } |
| mutex_unlock(&cd->system_lock); |
| } |
| |
| if (cd->bl_pip_ver_ready == false) { |
| rc = pt_pip2_get_version_(cd); |
| if (!rc) |
| cd->bl_pip_ver_ready = true; |
| else { |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| } |
| |
| /* |
| * Launch app command will fail in flashless mode. |
| * Skip launch app command here to save time for |
| * enumeration flow. |
| */ |
| if (cd->flashless_dut) |
| goto exit; |
| |
| /* |
| * pt_pip2_launch_app() is needed here instead of |
| * pt_pip2_exit_bl() because exit_bl will cause several |
| * hw_resets to occur and the auto BL on a flashless |
| * DUT will fail. |
| */ |
| rc = pt_pip2_launch_app(cd->dev, |
| PT_CORE_CMD_UNPROTECTED); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on launch app r=%d\n", |
| __func__, rc); |
| msleep(50); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| |
| /* |
| * IRQ thread can be delayed if the serial log print is |
| * enabled. It causes next command to get wrong response |
| * Here the delay is to ensure pt_parse_input() to be |
| * finished. |
| */ |
| msleep(60); |
| /* Check and update the mode */ |
| rc = pt_pip2_get_status_(cd); |
| if (!rc || (rc == PIP2_RSP_ERR_INIT_FAILURE)) |
| mode = cd->dut_status.mode; |
| else { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Get mode failed, mode unknown\n", |
| __func__); |
| mode = PT_MODE_UNKNOWN; |
| } |
| cd->mode = mode; |
| if (cd->mode == PT_MODE_OPERATIONAL) { |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Launched to Operational mode\n", |
| __func__); |
| protocol_mode = cd->protocol_mode; |
| } else if (cd->mode == PT_MODE_BOOTLOADER) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Launch failed, Bootloader mode\n", |
| __func__); |
| goto exit; |
| } else if (cd->mode == PT_MODE_UNKNOWN) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Launch failed, Unknown mode\n", |
| __func__); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| |
| if (cd->app_pip_ver_ready == false) { |
| rc = pt_pip2_get_version_(cd); |
| if (!rc) |
| cd->app_pip_ver_ready = true; |
| else { |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| } |
| break; |
| default: |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Unknown mode\n", __func__); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| if (cd->protocol_mode != PT_PROTOCOL_MODE_PIP && |
| get_hid_desc == false) { |
| rc = pt_get_hid_descriptor_(cd, &hid_desc); |
| if (!rc) |
| *enum_status |= ENUM_STATUS_GET_DESC; |
| } else { |
| *enum_status |= ENUM_STATUS_GET_DESC; |
| } |
| |
| pt_init_pip_report_fields(cd); |
| if (protocol_mode != PT_PROTOCOL_MODE_PIP) { |
| rc = pt_get_report_descriptor_(cd); |
| if (rc < 0) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on getting report descriptor r=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| } |
| |
| *enum_status |= ENUM_STATUS_GET_RPT_DESC; |
| |
| if (!cd->features.easywake) |
| cd->easy_wakeup_gesture = PT_CORE_EWG_NONE; |
| |
| pt_debug(cd->dev, DL_INFO, "%s: Reading sysinfo\n", __func__); |
| rc = pt_hid_output_get_sysinfo_(cd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error on getting sysinfo r=%d\n", __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| goto exit; |
| } |
| *enum_status |= ENUM_STATUS_GET_SYS_INFO; |
| |
| /* FW cannot handle most PIP cmds when it is still in BOOT mode */ |
| rc = _pt_poll_for_fw_exit_boot_mode(cd, 10000, &wait_time); |
| if (!rc) { |
| *enum_status |= ENUM_STATUS_FW_OUT_OF_BOOT; |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Exit FW BOOT Mode after %dms, (0x%04X)\n", |
| __func__, wait_time, *enum_status); |
| } else { |
| pt_debug(cd->dev, DL_WARN, |
| "%s: FW stuck in BOOT Mode after %dms\n", |
| __func__, wait_time); |
| goto exit; |
| } |
| |
| pt_debug(cd->dev, DL_INFO, "%s pt Prot Version: %d.%d\n", |
| __func__, |
| cd->sysinfo.ttdata.pip_ver_major, |
| cd->sysinfo.ttdata.pip_ver_minor); |
| |
| rc = pt_get_ic_crc_(cd, PT_TCH_PARM_EBID); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: DUT Config block CRC failure rc=%d\n", |
| __func__, rc); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| } else { |
| _pt_get_fw_sys_mode(cd, &sys_mode, NULL); |
| if (sys_mode != FW_SYS_MODE_SCANNING) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: scan state: %d, retry: %d\n", |
| __func__, sys_mode, try); |
| if (try++ < PT_CORE_STARTUP_RETRY_COUNT) |
| goto reset; |
| } else { |
| *enum_status |= ENUM_STATUS_GET_CFG_CRC; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Config CRC Retrieved (0x%04X)\n", |
| __func__, *enum_status); |
| } |
| } |
| |
| rc = pt_restore_parameters_(cd); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Failed to restore parameters rc=%d\n", |
| __func__, rc); |
| } else { |
| *enum_status |= ENUM_STATUS_RESTORE_PARM; |
| pt_debug(cd->dev, DL_INFO, |
| "%s: RAM parameters restored (0x%04X)\n", |
| __func__, *enum_status); |
| } |
| |
| call_atten_cb(cd, PT_ATTEN_STARTUP, 0); |
| cd->watchdog_interval = PT_WATCHDOG_TIMEOUT; |
| cd->startup_retry_count = 0; |
| |
| exit: |
| /* Generate the HW Version of the connected DUT and store in cd */ |
| _pip2_generate_hw_version(cd, cd->hw_version); |
| pt_debug(cd->dev, DL_WARN, "%s: HW Version: %s\n", __func__, |
| cd->hw_version); |
| |
| if (!detected) |
| rc = -ENODEV; |
| |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Completed Enumeration rc=%d On Attempt %d\n", |
| __func__, rc, try); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_enum_with_dut_ |
| * |
| * SUMMARY: This function does the full enumeration of the PIP1 or PIP2 DUT with |
| * TTDL according to the DUT generation. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * reset - Flag to reset the DUT before attempting to enumerate |
| * *status - pointer to store the enum status bitmask flags |
| ******************************************************************************/ |
| static int pt_enum_with_dut_(struct pt_core_data *cd, bool reset, |
| u32 *enum_status) |
| { |
| int rc = 0; |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, NULL, 0, "enter startup"); |
| #endif |
| pt_stop_wd_timer(cd); |
| |
| /* Clear enum status except BL/FW sentinel status */ |
| cd->enum_status &= (ENUM_STATUS_BL_RESET_SENTINEL | |
| ENUM_STATUS_FW_RESET_SENTINEL); |
| |
| if (cd->active_dut_generation == DUT_PIP1_ONLY) |
| rc = pt_enum_with_pip1_dut_(cd, reset, enum_status); |
| else |
| rc = pt_enum_with_pip2_dut_(cd, reset, enum_status); |
| |
| pt_start_wd_timer(cd); |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| tthe_print(cd, NULL, 0, "exit startup"); |
| #endif |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_enum_with_dut |
| * |
| * SUMMARY: This is the safe function wrapper for pt_enum_with_dut_() by |
| * requesting exclusive access around the call. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *cd - pointer the core data structure |
| * reset - Flag to reset the DUT before attempting to enumerate |
| * *status - pointer to store the enum status bitmask flags |
| ******************************************************************************/ |
| static int pt_enum_with_dut(struct pt_core_data *cd, bool reset, u32 *status) |
| { |
| int rc = 0; |
| |
| mutex_lock(&cd->system_lock); |
| cd->startup_state = STARTUP_RUNNING; |
| mutex_unlock(&cd->system_lock); |
| |
| rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); |
| if (rc) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: fail get exclusive ex=%p own=%p\n", |
| __func__, cd->exclusive_dev, cd->dev); |
| goto exit; |
| } |
| |
| rc = pt_enum_with_dut_(cd, reset, status); |
| |
| if (release_exclusive(cd, cd->dev) < 0) |
| /* Don't return fail code, mode is already changed. */ |
| pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", |
| __func__); |
| else |
| pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", |
| __func__); |
| |
| exit: |
| mutex_lock(&cd->system_lock); |
| /* Complete startup state for any tasks waiting for startup done */ |
| cd->startup_state = STARTUP_DONE; |
| mutex_unlock(&cd->system_lock); |
| |
| /* Set STATUS_COMPLETE bit to indicate the status is ready to be read */ |
| *status |= ENUM_STATUS_COMPLETE; |
| |
| /* Wake the waiters for end of startup */ |
| wake_up(&cd->wait_q); |
| |
| return rc; |
| } |
| |
| #ifndef TTDL_KERNEL_SUBMISSION |
| static int add_sysfs_interfaces(struct device *dev); |
| static void remove_sysfs_interfaces(struct device *dev); |
| static void remove_sysfs_and_modules(struct device *dev); |
| #endif /*!TTDL_KERNEL_SUBMISSION */ |
| static void pt_release_modules(struct pt_core_data *cd); |
| static void pt_probe_modules(struct pt_core_data *cd); |
| /******************************************************************************* |
| * FUNCTION: _pt_ttdl_restart |
| * |
| * SUMMARY: Restarts TTDL enumeration with the DUT and re-probes all modules |
| * |
| * NOTE: The function DOSE NOT remove sysfs and modulues. Trying to create |
| * existing sysfs nodes will produce an error. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int _pt_ttdl_restart(struct device *dev) |
| { |
| int rc = 0; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_I2C |
| struct i2c_client *client = |
| (struct i2c_client *)container_of(dev, struct i2c_client, dev); |
| #endif |
| |
| /* |
| * Make sure the device is awake, pt_mt_release function will |
| * cause pm sleep function and lead to deadlock. |
| */ |
| pm_runtime_get_sync(dev); |
| /* Use ttdl_restart_lock to avoid reentry */ |
| mutex_lock(&cd->ttdl_restart_lock); |
| |
| remove_sysfs_and_modules(cd->dev); |
| |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_I2C |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| pt_debug(dev, DL_ERROR, |
| "%s I2C functionality not Supported\n", __func__); |
| rc = -EIO; |
| goto ttdl_no_error; |
| } |
| #endif |
| |
| if (cd->active_dut_generation == DUT_UNKNOWN) { |
| rc = _pt_detect_dut_generation(cd->dev, |
| &cd->enum_status, &cd->active_dut_generation, |
| &cd->mode, &cd->protocol_mode); |
| if ((cd->active_dut_generation == DUT_UNKNOWN) || (rc)) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, Unknown DUT Generation rc=%d\n", |
| __func__, rc); |
| } |
| } |
| |
| rc = add_sysfs_interfaces(cd->dev); |
| if (rc < 0) |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Error, failed sysfs nodes rc=%d\n", |
| __func__, rc); |
| |
| if (!(cd->enum_status & ENUM_STATUS_BL_RESET_SENTINEL) |
| && !cd->dual_mcu_available) { |
| pt_debug(dev, DL_INFO, "%s: Call pt_enum_with_dut\n", __func__); |
| rc = pt_enum_with_dut(cd, true, &cd->enum_status); |
| if (rc < 0) |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, Failed to Enumerate\n", __func__); |
| } |
| |
| rc = pt_mt_probe(dev); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, fail mt probe\n", __func__); |
| } |
| |
| rc = pt_pen_probe(dev); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, fail pen probe\n", __func__); |
| } |
| |
| rc = pt_btn_probe(dev); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Error, fail btn probe\n", __func__); |
| } |
| |
| pt_probe_modules(cd); |
| |
| pt_debug(cd->dev, DL_WARN, |
| "%s: Well Done! TTDL Restart Completed. (0x%04X)\n", |
| __func__, cd->enum_status); |
| rc = 0; |
| |
| #ifdef CONFIG_TOUCHSCREEN_PARADE_I2C |
| ttdl_no_error: |
| #endif |
| mutex_unlock(&cd->ttdl_restart_lock); |
| |
| mutex_lock(&cd->system_lock); |
| cd->startup_state = STARTUP_DONE; |
| mutex_unlock(&cd->system_lock); |
| |
| pm_runtime_put(dev); |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_restart_work_function |
| * |
| * SUMMARY: This is the wrapper function placed in a work queue to call |
| * _pt_ttdl_restart() |
| * |
| * RETURN: none |
| * |
| * PARAMETERS: |
| * *work - pointer to the work_struct |
| ******************************************************************************/ |
| static void pt_restart_work_function(struct work_struct *work) |
| { |
| struct pt_core_data *cd = container_of(work, |
| struct pt_core_data, ttdl_restart_work); |
| int rc = 0; |
| |
| rc = _pt_ttdl_restart(cd->dev); |
| if (rc < 0) |
| pt_debug(cd->dev, DL_ERROR, "%s: Fail queued startup r=%d\n", |
| __func__, rc); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_enum_work_function |
| * |
| * SUMMARY: This is the wrapper function placed in a work queue to call |
| * pt_enum_with_dut() |
| * |
| * RETURN: none |
| * |
| * PARAMETERS: |
| * *work - pointer to the work_struct |
| ******************************************************************************/ |
| static void pt_enum_work_function(struct work_struct *work) |
| { |
| struct pt_core_data *cd = container_of(work, |
| struct pt_core_data, enum_work); |
| int rc; |
| |
| rc = pt_enum_with_dut(cd, false, &cd->enum_status); |
| if (rc < 0) |
| pt_debug(cd->dev, DL_ERROR, "%s: Fail queued startup r=%d\n", |
| __func__, rc); |
| } |
| |
| #if (KERNEL_VERSION(3, 19, 0) <= LINUX_VERSION_CODE) |
| #define KERNEL_VER_GT_3_19 |
| #endif |
| |
| #if defined(CONFIG_PM_RUNTIME) || defined(KERNEL_VER_GT_3_19) |
| /* CONFIG_PM_RUNTIME option is removed in 3.19.0 */ |
| #if defined(CONFIG_PM_SLEEP) |
| /******************************************************************************* |
| * FUNCTION: pt_core_rt_suspend |
| * |
| * SUMMARY: Wrapper function with PM Runtime stratergy to call pt_core_sleep. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int pt_core_rt_suspend(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| int rc = 0; |
| |
| if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) |
| return 0; |
| |
| rc = pt_core_sleep(cd); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, "%s: Error on sleep\n", __func__); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_rt_resume |
| * |
| * SUMMARY: Wrapper function with PM Runtime stratergy to call pt_core_wake. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int pt_core_rt_resume(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| int rc = 0; |
| |
| if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) |
| return 0; |
| |
| rc = pt_core_wake(cd); |
| if (rc < 0) { |
| pt_debug(dev, DL_ERROR, "%s: Error on wake\n", __func__); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_PM_SLEEP */ |
| #endif /* CONFIG_PM_RUNTIME || LINUX_VERSION_CODE */ |
| |
| #if defined(CONFIG_PM_SLEEP) |
| static void pt_irq_enable(struct pt_core_data *cd, bool enable) |
| { |
| struct irq_desc *desc; |
| |
| if (enable) { |
| if (!cd->irq_enabled) { |
| enable_irq(cd->irq); |
| cd->irq_enabled = true; |
| } |
| } else { |
| if (cd->irq_enabled) { |
| disable_irq(cd->irq); |
| cd->irq_enabled = false; |
| } |
| } |
| |
| desc = irq_to_desc(cd->irq); |
| pt_debug(cd->dev, DL_WARN, "%s: enable=%d, desc->depth=%d\n", __func__, enable, |
| desc->depth); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_suspend_ |
| * |
| * SUMMARY: Wrapper function with device suspend/resume stratergy to call |
| * pt_core_sleep. This function may disable IRQ during sleep state. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int pt_core_suspend_(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_debug(dev, DL_INFO, "%s start\n", __func__); |
| |
| pt_core_sleep(cd); |
| |
| if (!IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) |
| return 0; |
| |
| pt_debug(dev, DL_INFO, "%s end\n", __func__); |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_suspend |
| * |
| * SUMMARY: Wrapper function of pt_core_suspend_() to help avoid TP from being |
| * woke up or put to sleep based on Kernel power state even when the display |
| * is off based on the check of TTDL core platform flag. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int pt_core_suspend(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_irq_enable(cd, false); |
| |
| if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_SYS_SLEEP) |
| return 0; |
| |
| return pt_core_suspend_(dev); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_resume_ |
| * |
| * SUMMARY: Wrapper function with device suspend/resume stratergy to call |
| * pt_core_wake. This function may enable IRQ before wake up. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int pt_core_resume_(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_debug(dev, DL_INFO, "%s start\n", __func__); |
| |
| pt_core_wake(cd); |
| |
| pt_debug(dev, DL_INFO, "%s end\n", __func__); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_core_resume |
| * |
| * SUMMARY: Wrapper function of pt_core_resume_() to avoid TP to be waken/slept |
| * along with kernel power state even the display is off based on the check of |
| * TTDL core platform flag. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to core device |
| ******************************************************************************/ |
| static int pt_core_resume(struct device *dev) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_irq_enable(cd, true); |
| |
| if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_SYS_SLEEP) |
| return 0; |
| |
| return pt_core_resume_(dev); |
| } |
| #endif |
| |
| #ifdef NEED_SUSPEND_NOTIFIER |
| /******************************************************************************* |
| * FUNCTION: pt_pm_notifier |
| * |
| * SUMMARY: This function is registered to notifier chain and will perform |
| * suspend operation if match event PM_SUSPEND_PREPARE. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *nb - pointer to notifier_block structure |
| * action - notifier event type |
| * *data - void pointer |
| ******************************************************************************/ |
| static int pt_pm_notifier(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct pt_core_data *cd = container_of(nb, |
| struct pt_core_data, pm_notifier); |
| |
| if (action == PM_SUSPEND_PREPARE) { |
| pt_debug(cd->dev, DL_INFO, "%s: Suspend prepare\n", |
| __func__); |
| |
| /* |
| * If PM runtime is not suspended, either call runtime |
| * PM suspend callback or wait until it finishes |
| */ |
| if (!pm_runtime_suspended(cd->dev)) |
| pm_runtime_suspend(cd->dev); |
| |
| (void) pt_core_suspend(cd->dev); |
| } |
| |
| return NOTIFY_DONE; |
| } |
| #endif |
| |
| const struct dev_pm_ops pt_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(pt_core_suspend, pt_core_resume) |
| SET_RUNTIME_PM_OPS(pt_core_rt_suspend, pt_core_rt_resume, |
| NULL) |
| }; |
| EXPORT_SYMBOL_GPL(pt_pm_ops); |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip2_enter_bl |
| * |
| * SUMMARY: Force the DUT to enter the BL by resetting the DUT by use of the |
| * XRES pin or a soft reset. |
| * |
| * NOTE: The WD MUST be stopped/restarted by the calling Function. Having |
| * the WD active could cause this function to fail! |
| * NOTE: If start_mode is passed in as PT_MODE_IGNORE, this function |
| * will not try to determine the current mode but will proceed with |
| * resetting the DUT and entering the BL. |
| * |
| * NOTE: The definition of result code: |
| * PT_ENTER_BL_PASS (0) |
| * PT_ENTER_BL_ERROR (1) |
| * PT_ENTER_BL_RESET_FAIL (2) |
| * PT_ENTER_BL_HID_START_BL_FAIL (3) |
| * PT_ENTER_BL_CONFIRM_FAIL (4) |
| * PT_ENTER_BL_GET_FLASH_INFO_FAIL (5) |
| * |
| * RETURNS: |
| * 0 = success |
| * !0 = failure |
| * |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *start_mode - pointer to the mode the DUT was in when this function |
| * starts |
| * *result - pointer to store the result when to enter BL |
| ******************************************************************************/ |
| int _pt_request_pip2_enter_bl(struct device *dev, u8 *start_mode, int *result) |
| { |
| int rc = 0; |
| int t; |
| int tmp_result = PT_ENTER_BL_ERROR; |
| int flash_info_retry = 2; |
| u8 mode = PT_MODE_UNKNOWN; |
| u8 sys_mode = FW_SYS_MODE_UNDEFINED; |
| u8 read_buf[32]; |
| u16 actual_read_len; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| u8 saved_flashless_auto_bl_mode = cd->flashless_auto_bl; |
| |
| if (cd->watchdog_enabled) { |
| pt_debug(dev, DL_WARN, |
| "%s: Watchdog must be stopped before entering BL\n", |
| __func__); |
| goto exit; |
| } |
| |
| cancel_work_sync(&cd->enum_work); |
| cancel_work_sync(&cd->watchdog_work); |
| |
| /* Check and correct "mode" if need when protocol mode is HID */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) { |
| rc = _pt_detect_dut_mode(dev, &mode); |
| if (!rc && cd->mode != mode) { |
| pt_debug(dev, DL_WARN, |
| "%s: cd->mode(%d) doesn't match detected(%d)\n", |
| __func__, cd->mode, mode); |
| cd->mode = mode; |
| } |
| } |
| |
| /* if undefined assume operational/test to bypass all checks */ |
| if (*start_mode == PT_MODE_IGNORE) { |
| mode = PT_MODE_OPERATIONAL; |
| sys_mode = FW_SYS_MODE_TEST; |
| pt_debug(dev, DL_INFO, "%s: Assume Mode = %d\n", |
| __func__, mode); |
| } else if (*start_mode == PT_MODE_UNKNOWN) { |
| rc = pt_pip2_get_mode_sysmode_(cd, &mode, &sys_mode); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Get mode failed, mode unknown\n", |
| __func__); |
| } |
| *start_mode = mode; |
| pt_debug(dev, DL_INFO, "%s: Get Mode = %d\n", __func__, mode); |
| } else if (*start_mode == PT_MODE_OPERATIONAL) { |
| /* Assume SCANNIING mode to avoid doing an extra get_mode */ |
| sys_mode = FW_SYS_MODE_SCANNING; |
| } |
| |
| _retry: |
| /* For Flashless DUTs - Supress auto BL on next BL sentinel */ |
| pt_debug(dev, DL_INFO, "%s: Flashless Auto_BL - SUPPRESS\n", __func__); |
| cd->flashless_auto_bl = PT_SUPPRESS_AUTO_BL; |
| |
| switch (mode) { |
| case PT_MODE_SECONDARY_IMAGE: |
| case PT_MODE_UNKNOWN: |
| /* |
| * When either the secondary image is running or the mode |
| * could not be determined due to the DUT potentially |
| * running corrupted FW or FW that is not responding to |
| * the mode request, a hard reset is done. |
| */ |
| mutex_lock(&cd->system_lock); |
| cd->enum_status = ENUM_STATUS_START; |
| pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); |
| mutex_unlock(&cd->system_lock); |
| |
| rc = pt_dut_reset(cd, PT_CORE_CMD_UNPROTECTED); |
| if (rc) { |
| tmp_result = PT_ENTER_BL_RESET_FAIL; |
| goto exit; |
| } |
| break; |
| |
| case PT_MODE_OPERATIONAL: |
| if (sys_mode == FW_SYS_MODE_SCANNING) { |
| pt_debug(dev, DL_INFO, "%s: Suspend Scanning\n", |
| __func__); |
| rc = pt_pip_suspend_scanning_(cd); |
| if (rc) { |
| /* |
| * Print to log but don't exit, the FW could be |
| * running but be hung or fail to respond to |
| * this request |
| */ |
| pt_debug(dev, DL_ERROR, |
| "%s Suspend Scan Failed\n", __func__); |
| } |
| /* sleep to allow the suspend scan to be processed */ |
| usleep_range(1000, 2000); |
| } |
| mutex_lock(&cd->system_lock); |
| cd->enum_status = ENUM_STATUS_START; |
| pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); |
| mutex_unlock(&cd->system_lock); |
| |
| /* Reset device to enter the BL */ |
| if (cd->enter_bl_method == PT_ENTER_BL_BY_RESET_PIN) { |
| rc = pt_dut_reset(cd, PT_CORE_CMD_UNPROTECTED); |
| if (rc) { |
| tmp_result = PT_ENTER_BL_RESET_FAIL; |
| goto exit; |
| } |
| } else { |
| rc = pt_pip_start_bootloader_(cd); |
| if (rc) { |
| tmp_result = PT_ENTER_BL_START_BL_CMD_FAIL; |
| goto exit; |
| } |
| } |
| break; |
| |
| case PT_MODE_BOOTLOADER: |
| /* Do nothing as we are already in the BL */ |
| tmp_result = PT_ENTER_BL_PASS; |
| goto exit; |
| |
| default: |
| /* Should NEVER get here */ |
| tmp_result = PT_ENTER_BL_ERROR; |
| pt_debug(dev, DL_ERROR, "%s: Unknown mode code\n", __func__); |
| goto exit; |
| } |
| |
| if (!cd->flashless_dut && |
| (mode == PT_MODE_UNKNOWN || |
| mode == PT_MODE_OPERATIONAL || |
| mode == PT_MODE_SECONDARY_IMAGE) && |
| cd->enter_bl_method == PT_ENTER_BL_BY_RESET_PIN) { |
| pt_send_host_mode_cmd_(cd); |
| } |
| |
| /* |
| * To avoid the case that next PIP command can be confused by BL/FW |
| * sentinel's "wakeup" event, chekcing hid_reset_cmd_state which is |
| * followed by "wakeup event" function can lower the failure rate. |
| */ |
| t = wait_event_timeout(cd->wait_q, |
| ((cd->enum_status != ENUM_STATUS_START) |
| && (cd->hid_reset_cmd_state == 0)), |
| msecs_to_jiffies(300)); |
| if (IS_TMO(t)) { |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: TMO waiting for BL sentinel\n", __func__); |
| } |
| |
| /* Check if device is now in BL mode */ |
| rc = pt_pip2_get_mode_sysmode_(cd, &mode, NULL); |
| pt_debug(dev, DL_INFO, "%s: Mode=%d, Status=0x%04X\n", __func__, mode, |
| cd->enum_status); |
| if (!rc && mode == PT_MODE_BOOTLOADER) { |
| pt_debug(dev, DL_INFO, "%s In bootloader mode now\n", __func__); |
| mutex_lock(&cd->system_lock); |
| cd->pip2_prot_active = true; |
| cd->mode = PT_MODE_BOOTLOADER; |
| mutex_unlock(&cd->system_lock); |
| tmp_result = PT_ENTER_BL_PASS; |
| } else { |
| /* |
| * If the device doesn't enter BL mode as expected and rc is |
| * tested pass by above function pt_pip2_get_mode_sysmode_(), |
| * the function should return an error code to indicate this |
| * failure PT_ENTER_BL_CONFIRM_FAIL. |
| */ |
| if (!rc) |
| rc = -EINVAL; |
| tmp_result = PT_ENTER_BL_CONFIRM_FAIL; |
| mutex_lock(&cd->system_lock); |
| cd->mode = mode; |
| mutex_unlock(&cd->system_lock); |
| pt_debug(dev, DL_ERROR, |
| "%s ERROR: Not in BL as expected\n", __func__); |
| } |
| |
| exit: |
| if (!cd->flashless_dut && (tmp_result == PT_ENTER_BL_PASS)) { |
| /* Check to get (buffered) flash information */ |
| rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_UNPROTECTED, |
| PIP2_CMD_ID_FLASH_INFO, NULL, 0, |
| read_buf, &actual_read_len); |
| if (!rc) { |
| if (read_buf[PIP2_RESP_BODY_OFFSET] == 0) { |
| pt_debug( |
| dev, DL_WARN, |
| "%s Unavailable Manufacturer ID: 0x%02x\n", |
| __func__, |
| read_buf[PIP2_RESP_BODY_OFFSET]); |
| /* |
| * If the BL was unable to cache the correct |
| * values when entering the first time due to |
| * the Flash part not having been powered up |
| * long enough, re-enter the BL to trigger the |
| * BL to re-attempt to cache the values. |
| */ |
| if (flash_info_retry-- > 0) { |
| mode = PT_MODE_UNKNOWN; |
| pt_debug(dev, DL_WARN, |
| "%s Assume mode to UNKNOWN to enter BL again, retry=%d\n", |
| __func__, flash_info_retry); |
| goto _retry; |
| } else { |
| pt_debug(dev, DL_WARN, |
| "%s Manufacturer ID Unknown\n", |
| __func__); |
| tmp_result = PT_ENTER_BL_PASS; |
| } |
| } |
| } else { |
| tmp_result = PT_ENTER_BL_GET_FLASH_INFO_FAIL; |
| pt_debug( |
| dev, DL_ERROR, |
| "%s: Failed to send PIP2 READ_FLASH_INFO cmd\n", |
| __func__); |
| } |
| } |
| |
| pt_debug(dev, DL_INFO, "%s: Flashless Auto_BL - %s\n", __func__, |
| saved_flashless_auto_bl_mode == PT_ALLOW_AUTO_BL ? "ALLOW" : |
| "SUPPRESS"); |
| cd->flashless_auto_bl = saved_flashless_auto_bl_mode; |
| if (result) |
| *result = tmp_result; |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_open |
| * |
| * SUMMARY: Using the BL PIP2 commands open a file and return the file handle |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * |
| * RETURNS: |
| * <0 = Error |
| * >0 = file handle opened |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_no - PIP2 file number to open |
| ******************************************************************************/ |
| int _pt_pip2_file_open(struct device *dev, u8 file_no) |
| { |
| int rc = 0; |
| u16 status; |
| u16 actual_read_len; |
| u8 file_handle; |
| u8 data[2]; |
| u8 read_buf[10]; |
| u8 expected_len = pt_pip2_get_cmd_response_len(PIP2_CMD_ID_FILE_OPEN); |
| |
| pt_debug(dev, DL_DEBUG, "%s: OPEN file %d\n", __func__, file_no); |
| data[0] = file_no; |
| rc = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_OPEN, |
| data, 1, read_buf, &actual_read_len); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: FILE_OPEN timeout for file=%d\n", |
| __func__, file_no); |
| return -PIP2_RSP_ERR_NOT_OPEN; |
| } |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (rc || ((status != PIP2_RSP_ERR_NONE) && |
| (status != PIP2_RSP_ERR_ALREADY_OPEN))) { |
| pt_debug(dev, DL_ERROR, |
| "%s: %s 0x%02X for file=%d\n", |
| __func__, "FILE_OPEN failure:", status, file_no); |
| return -status; |
| } else if (actual_read_len == expected_len) { |
| /* File_open returned a file handle */ |
| file_handle = read_buf[PIP2_RESP_BODY_OFFSET]; |
| if (file_handle != file_no) { |
| pt_debug(dev, DL_ERROR, |
| "%s: %s 0x%02X file=%d returned handle=%d\n", |
| __func__, "FILE_OPEN failure:", |
| status, file_no, file_handle); |
| return -status; |
| } |
| } else { |
| return -status; |
| } |
| |
| return file_handle; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_close |
| * |
| * SUMMARY: Using the BL PIP2 commands close a file |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * |
| * RETURNS: |
| * <0 = Error |
| * >0 = file handle closed |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - handle to the file to be closed |
| ******************************************************************************/ |
| int _pt_pip2_file_close(struct device *dev, u8 file_handle) |
| { |
| int ret = 0; |
| u16 status; |
| u16 actual_read_len; |
| u8 data[2]; |
| u8 read_buf[32]; |
| |
| pt_debug(dev, DL_DEBUG, "%s: CLOSE file %d\n", __func__, file_handle); |
| data[0] = file_handle; |
| ret = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_CLOSE, |
| data, 1, read_buf, &actual_read_len); |
| if (ret) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM BL FILE_CLOSE timeout for file = %d\n", |
| __func__, file_handle); |
| return -ETIME; |
| } |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (status != 0x00) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM BL FILE_CLOSE failure: 0x%02X for file = %d\n", |
| __func__, status, file_handle); |
| return -status; |
| } |
| return file_handle; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_erase_by_file_no |
| * |
| * SUMMARY: Using the BL PIP2 commands erase a file by file number only. |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * NOTE: Some FLASH parts can time out while erasing one or more sectors, |
| * one retry is attempted for each sector in a file. |
| * |
| * RETURNS: |
| * <0 = Error |
| * >0 = file handle closed |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - handle to the file to be erased |
| * *status - PIP2 erase status code |
| ******************************************************************************/ |
| int _pt_pip2_file_erase_by_file_no(struct device *dev, u8 file_handle, |
| int *status) |
| { |
| int ret = 0; |
| int max_retry = PT_PIP2_MAX_FILE_SIZE/PT_PIP2_FILE_SECTOR_SIZE; |
| int retry = 1; |
| u16 actual_read_len; |
| u8 data[2]; |
| u8 read_buf[10]; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_debug(dev, DL_DEBUG, "%s: ERASE file %d\n", __func__, file_handle); |
| data[0] = file_handle; |
| data[1] = PIP2_FILE_IOCTL_CODE_ERASE_FILE; |
| *status = PIP2_RSP_ERR_TIMEOUT; |
| |
| /* Increase waiting time for large file erase */ |
| mutex_lock(&cd->system_lock); |
| cd->pip_cmd_timeout = PT_PIP2_CMD_FILE_ERASE_TIMEOUT; |
| mutex_unlock(&cd->system_lock); |
| while (*status == PIP2_RSP_ERR_TIMEOUT && retry < max_retry) { |
| ret = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, |
| data, 2, read_buf, &actual_read_len); |
| if (ret) |
| break; |
| *status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (*status == PIP2_RSP_ERR_TIMEOUT) { |
| #ifdef TTDL_DIAGNOSTICS |
| cd->file_erase_timeout_count++; |
| #endif |
| pt_debug(dev, DL_WARN, |
| "%s: ERASE timeout %d for file = %d\n", |
| __func__, retry, file_handle); |
| } |
| retry++; |
| } |
| mutex_lock(&cd->system_lock); |
| cd->pip_cmd_timeout = cd->pip_cmd_timeout_default; |
| mutex_unlock(&cd->system_lock); |
| if (ret) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM FILE_ERASE cmd failure: %d for file = %d\n", |
| __func__, ret, file_handle); |
| return -EIO; |
| } |
| |
| if (*status != 0x00) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM FILE_ERASE failure: 0x%02X for file = %d\n", |
| __func__, *status, file_handle); |
| return -EIO; |
| } |
| return file_handle; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_erase_by_file_sector |
| * |
| * SUMMARY: Using the BL PIP2 commands erase a file sector by sector. |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * NOTE: Some FLASH parts can time out while erasing one or more sectors, |
| * one retry is attempted for each sector in a file. |
| * |
| * RETURNS: |
| * <0 = Error |
| * >0 = file handle closed |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - handle to the file to be erased |
| * file_sector - Number of sectors to erase |
| * *status - PIP2 erase status code |
| ******************************************************************************/ |
| int _pt_pip2_file_erase_by_file_sector(struct device *dev, u8 file_handle, |
| u16 file_sector, int *status) |
| { |
| int ret = 0, index = 0; |
| int max_retry = 3; |
| int retry = 0; |
| int tmp_status = PIP2_RSP_ERR_NONE; |
| u16 sector_to_erase = 1; |
| u16 actual_read_len; |
| u8 data[6]; |
| u8 read_buf[10]; |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| |
| pt_debug(dev, DL_DEBUG, "%s: ERASE file=%d, sector=%d\n", __func__, |
| file_handle, file_sector); |
| data[0] = file_handle; |
| data[1] = PIP2_FILE_IOCTL_CODE_ERASE_FILE; |
| /* Set how many sectors to erase*/ |
| data[4] = LOW_BYTE(sector_to_erase); |
| data[5] = HI_BYTE(sector_to_erase); |
| |
| /* Increase waiting time for large file erase */ |
| mutex_lock(&cd->system_lock); |
| cd->pip_cmd_timeout = PT_PIP2_CMD_FILE_SECTOR_ERASE_TIMEOUT; |
| mutex_unlock(&cd->system_lock); |
| for (retry = 0; retry < max_retry; retry++) { |
| for (index = 0; index < file_sector; index++) { |
| /* Initialize status code */ |
| tmp_status = PIP2_RSP_ERR_NONE; |
| /* Set which sector starts to erase */ |
| data[2] = LOW_BYTE(index); |
| data[3] = HI_BYTE(index); |
| ret = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, |
| data, 6, read_buf, &actual_read_len); |
| |
| tmp_status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (ret || tmp_status != PIP2_RSP_ERR_NONE) |
| break; |
| } |
| /* |
| * Only retry if response doesn't arrive or the status code is |
| * PIP2_RSP_ERR_TIMEOUT. |
| */ |
| if ((ret != -ETIME) || (tmp_status != PIP2_RSP_ERR_TIMEOUT)) |
| break; |
| else { |
| #ifdef TTDL_DIAGNOSTICS |
| cd->file_erase_timeout_count++; |
| #endif |
| pt_debug(dev, DL_WARN, |
| "%s: ERASE timeout %d for file=%d, sector=%d\n", |
| __func__, retry, file_handle, index); |
| } |
| } |
| |
| /* |
| * When communication error happens, tmp_status just comes from the |
| * previous correct response and should not pass to caller. |
| */ |
| mutex_lock(&cd->system_lock); |
| cd->pip_cmd_timeout = cd->pip_cmd_timeout_default; |
| if (!ret) |
| *status = tmp_status; |
| mutex_unlock(&cd->system_lock); |
| if (ret) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM FILE_ERASE cmd failure: %d for file=%d, sector=%d\n", |
| __func__, ret, file_handle, index); |
| return -EIO; |
| } |
| |
| if (*status != 0x00) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM FILE_ERASE failure: 0x%02X for file=%d, sector=%d\n", |
| __func__, *status, file_handle, index); |
| return -EIO; |
| } |
| return file_handle; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_erase |
| * |
| * SUMMARY: Wrapper function to call _pt_pip2_file_erase_by_file_sector() and |
| * _pt_pip2_file_erase_by_file_no() by checking file_sector. |
| * |
| * NOTE: If the file_sector is 0, it will erase the entire file. |
| * |
| * RETURNS: |
| * <0 = Error |
| * >0 = file handle closed |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - handle to the file to be erased |
| * file_sector - Number of sectors to erase |
| * *status - PIP2 erase status code |
| ******************************************************************************/ |
| int _pt_pip2_file_erase(struct device *dev, u8 file_handle, u16 file_sector, |
| int *status) |
| { |
| if (file_sector) |
| return _pt_pip2_file_erase_by_file_sector(dev, file_handle, |
| file_sector, status); |
| else |
| return _pt_pip2_file_erase_by_file_no(dev, file_handle, status); |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_read |
| * |
| * SUMMARY: Using the BL PIP2 commands read n bytes from a already opened file |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * |
| * RETURNS: |
| * <0 = Error |
| * >0 = number of bytes read |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - File handle to read from |
| * num_bytes - number of bytes to read |
| ******************************************************************************/ |
| int _pt_pip2_file_read(struct device *dev, u8 file_handle, u16 num_bytes, |
| u8 *read_buf) |
| { |
| int ret = 0; |
| u16 status; |
| u16 actual_read_len; |
| u8 data[3]; |
| |
| data[0] = file_handle; |
| data[1] = (num_bytes & 0x00FF); |
| data[2] = (num_bytes & 0xFF00) >> 8; |
| ret = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_READ, |
| data, 3, read_buf, &actual_read_len); |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (ret || ((status != 0x00) && (status != 0x03))) { |
| pt_debug(dev, DL_ERROR, |
| "%s File open failure with error code = %d\n", |
| __func__, status); |
| return -1; |
| } |
| ret = num_bytes; |
| |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_read_us_file |
| * |
| * SUMMARY: Open a user space file and read 'size' bytes into buf. If size = 0 |
| * then read the entire file. |
| * NOTE: The file size must be less than PT_PIP2_MAX_FILE_SIZE |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *file_path - pointer to the file path |
| * *buf - pointer to the buffer to store the file contents |
| * *size - pointer to the size of the file |
| ******************************************************************************/ |
| #if defined(PT_CORE_READ_USER_SPACE_FILE) |
| int _pt_read_us_file(struct device *dev, u8 *file_path, u8 *buf, int *size) |
| { |
| struct file *filp = NULL; |
| struct inode *inode = NULL; |
| unsigned int file_len = 0; |
| unsigned int read_len = 0; |
| int rc = 0; |
| |
| if (file_path == NULL || buf == NULL) { |
| pt_debug(dev, DL_ERROR, "%s: path || buf is NULL.\n", __func__); |
| return -EINVAL; |
| } |
| |
| pt_debug(dev, DL_WARN, "%s: path = %s\n", __func__, file_path); |
| |
| filp = filp_open(file_path, O_RDONLY, 0400); |
| if (IS_ERR(filp)) { |
| pt_debug(dev, DL_WARN, "%s: Failed to open %s\n", __func__, |
| file_path); |
| rc = -ENOENT; |
| goto err; |
| } |
| |
| if (filp->f_op == NULL) { |
| pt_debug(dev, DL_ERROR, "%s: File Operation Method Error\n", |
| __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| inode = filp->f_path.dentry->d_inode; |
| if (inode == NULL) { |
| pt_debug(dev, DL_ERROR, "%s: Get inode from filp failed\n", |
| __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| file_len = i_size_read(inode->i_mapping->host); |
| if (file_len == 0) { |
| pt_debug(dev, DL_ERROR, "%s: file size error,file_len = %d\n", |
| __func__, file_len); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| if (*size == 0) |
| read_len = file_len; |
| else |
| read_len = *size; |
| |
| if (read_len > PT_PIP2_MAX_FILE_SIZE) { |
| pt_debug(dev, DL_ERROR, "%s: file size ( %d ) exception.\n", |
| __func__, read_len); |
| rc = -EINVAL; |
| goto exit; |
| } |
| filp->private_data = inode->i_private; |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) |
| if (kernel_read(filp, buf, read_len, &(filp->f_pos)) != read_len) { |
| #else |
| if (vfs_read(filp, buf, read_len, &(filp->f_pos)) != read_len) { |
| #endif |
| pt_debug(dev, DL_ERROR, "%s: file read error.\n", __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| *size = read_len; |
| |
| exit: |
| if (filp_close(filp, NULL) != 0) |
| pt_debug(dev, DL_ERROR, "%s: file close error.\n", __func__); |
| err: |
| return rc; |
| } |
| #else |
| int _pt_read_us_file(struct device *dev, u8 *file_path, u8 *buf, int *size) |
| { |
| return -EINVAL; |
| } |
| #endif /* PT_CORE_READ_USER_SPACE_FILE */ |
| /******************************************************************************* |
| * FUNCTION: _pt_request_pip2_bin_hdr |
| * |
| * SUMMARY: Read the stored bin file header from Flash or the User Space file |
| * in the case of a flashless DUT, and parse the contents |
| * |
| * RETURNS: |
| * 0 = Success |
| * !0 = Error condition |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| ******************************************************************************/ |
| int _pt_request_pip2_bin_hdr(struct device *dev, struct pt_bin_file_hdr *hdr) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| u8 file_handle; |
| u8 read_buf[255]; |
| u8 hdr_len = 0; |
| u8 i; |
| int bytes_read; |
| int read_size; |
| int ret = 0; |
| int rc = 0; |
| bool load_hdr_struct = false; |
| |
| if (cd->flashless_dut) { |
| read_size = sizeof(read_buf); |
| rc = _pt_read_us_file(dev, cd->pip2_us_file_path, |
| read_buf, &read_size); |
| if (rc) { |
| ret = rc; |
| pt_debug(dev, DL_WARN, |
| "%s Failed to read fw image from US, rc=%d\n", |
| __func__, rc); |
| goto exit; |
| } |
| load_hdr_struct = true; |
| hdr_len = read_buf[0]; |
| i = 0; |
| } else { |
| if (cd->mode != PT_MODE_BOOTLOADER) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| /* Open the bin file in Flash */ |
| pt_debug(dev, DL_INFO, "%s Open File 1\n", __func__); |
| file_handle = _pt_pip2_file_open(dev, PIP2_FW_FILE); |
| if (file_handle != PIP2_FW_FILE) { |
| ret = -ENOENT; |
| pt_debug(dev, DL_ERROR, |
| "%s Failed to open bin file\n", __func__); |
| goto exit; |
| } |
| |
| /* Read the header length from the file */ |
| pt_debug(dev, DL_INFO, "%s Read length of header\n", __func__); |
| read_size = 1; |
| bytes_read = _pt_pip2_file_read(dev, file_handle, read_size, |
| read_buf); |
| if (bytes_read != read_size) { |
| ret = -EX_ERR_FREAD; |
| pt_debug(dev, DL_ERROR, |
| "%s Failed to bin file header len\n", __func__); |
| goto exit_close_file; |
| } |
| hdr_len = read_buf[PIP2_RESP_BODY_OFFSET]; |
| if (hdr_len == 0xFF) { |
| ret = -EX_ERR_FLEN; |
| pt_debug(dev, DL_ERROR, |
| "%s Bin header len is invalid\n", __func__); |
| goto exit_close_file; |
| } |
| |
| /* Read the rest of the header from the bin file */ |
| pt_debug(dev, DL_INFO, "%s Read bin file header\n", __func__); |
| memset(read_buf, 0, sizeof(read_buf)); |
| bytes_read = _pt_pip2_file_read(dev, file_handle, hdr_len, |
| read_buf); |
| if (bytes_read != hdr_len) { |
| ret = -EX_ERR_FREAD; |
| pt_debug(dev, DL_ERROR, |
| "%s Failed to read bin file\n", __func__); |
| goto exit_close_file; |
| } |
| load_hdr_struct = true; |
| |
| exit_close_file: |
| /* Close the file */ |
| if (file_handle != _pt_pip2_file_close(dev, file_handle)) { |
| ret = -EX_ERR_FCLOSE; |
| pt_debug(dev, DL_ERROR, |
| "%s Failed to close bin file\n", __func__); |
| } |
| |
| /* |
| * The length was already read so subtract 1 to make the rest of |
| * the offsets match the spec |
| */ |
| i = PIP2_RESP_BODY_OFFSET - 1; |
| } |
| |
| if (load_hdr_struct) { |
| hdr->length = hdr_len; |
| hdr->ttpid = (read_buf[i+1] << 8) | read_buf[i+2]; |
| hdr->fw_major = (read_buf[i+3]); |
| hdr->fw_minor = (read_buf[i+4]); |
| hdr->fw_crc = (read_buf[i+5] << 24) | |
| (read_buf[i+6] << 16) | |
| (read_buf[i+7] << 8) | |
| (read_buf[i+8]); |
| hdr->fw_rev_ctrl = (read_buf[i+9] << 24) | |
| (read_buf[i+10] << 16) | |
| (read_buf[i+11] << 8) | |
| (read_buf[i+12]); |
| hdr->si_rev = (read_buf[i+14] << 8) | (read_buf[i+13]); |
| hdr->si_id = (read_buf[i+16] << 8) | (read_buf[i+15]); |
| hdr->config_ver = (read_buf[i+17] << 8) | (read_buf[i+18]); |
| if (hdr_len >= 22) { |
| hdr->hex_file_size = (read_buf[i+19] << 24) | |
| (read_buf[i+20] << 16) | |
| (read_buf[i+21] << 8) | |
| (read_buf[i+22]); |
| } else { |
| hdr->hex_file_size = 0; |
| } |
| } |
| |
| exit: |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_get_stats |
| * |
| * SUMMARY: Using the BL PIP2 commands get file information from an already |
| * opened file |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * |
| * RETURNS: |
| * !0 = Error |
| * 0 = Success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - File handle to read from |
| * *address - pointer to store address of file |
| * *file_size _ pointer to store size of file |
| ******************************************************************************/ |
| int _pt_pip2_file_get_stats(struct device *dev, u8 file_handle, u32 *address, |
| u32 *file_size) |
| { |
| int ret = 1; |
| u16 status; |
| u16 actual_read_len; |
| u8 data[2]; |
| u8 read_buf[16]; |
| |
| data[0] = file_handle; |
| data[1] = PIP2_FILE_IOCTL_CODE_FILE_STATS; |
| |
| ret = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, |
| data, 2, read_buf, &actual_read_len); |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (ret || (status != 0x00)) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM FILE_STATS failure: 0x%02X for file = %d, ret = %d\n", |
| __func__, status, file_handle, ret); |
| ret = -EIO; |
| goto exit; |
| } |
| |
| pt_debug(dev, DL_DEBUG, |
| "%s --- FILE %d Information ---\n", __func__, file_handle); |
| |
| if (address) { |
| *address = read_buf[PIP2_RESP_BODY_OFFSET] + |
| (read_buf[PIP2_RESP_BODY_OFFSET + 1] << 8) + |
| (read_buf[PIP2_RESP_BODY_OFFSET + 2] << 16) + |
| (read_buf[PIP2_RESP_BODY_OFFSET + 3] << 24); |
| pt_debug(dev, DL_DEBUG, "%s Address: 0x%08x\n", |
| __func__, *address); |
| } |
| |
| if (file_size) { |
| *file_size = read_buf[PIP2_RESP_BODY_OFFSET + 4] + |
| (read_buf[PIP2_RESP_BODY_OFFSET + 5] << 8) + |
| (read_buf[PIP2_RESP_BODY_OFFSET + 6] << 16) + |
| (read_buf[PIP2_RESP_BODY_OFFSET + 7] << 24); |
| pt_debug(dev, DL_DEBUG, "%s Size : 0x%08x\n", |
| __func__, *file_size); |
| } |
| |
| exit: |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_seek_offset |
| * |
| * SUMMARY: Using the BL PIP2 commands seek read/write offset for an already |
| * opened file |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * NOTE: File open/erase command can reset the offset |
| * |
| * RETURNS: |
| * !0 = Error |
| * 0 = Success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - File handle to read from |
| * read_offset - read offset of file |
| * write_offset - write offset of file |
| ******************************************************************************/ |
| int _pt_pip2_file_seek_offset(struct device *dev, u8 file_handle, |
| u32 read_offset, u32 write_offset) |
| { |
| int ret = 1; |
| u16 status; |
| u16 actual_read_len; |
| u8 data[10]; |
| u8 read_buf[16]; |
| |
| data[0] = file_handle; |
| data[1] = PIP2_FILE_IOCTL_CODE_SEEK_POINTER; |
| data[2] = 0xff & read_offset; |
| data[3] = 0xff & (read_offset >> 8); |
| data[4] = 0xff & (read_offset >> 16); |
| data[5] = 0xff & (read_offset >> 24); |
| data[6] = 0xff & write_offset; |
| data[7] = 0xff & (write_offset >> 8); |
| data[8] = 0xff & (write_offset >> 16); |
| data[9] = 0xff & (write_offset >> 24); |
| |
| ret = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, |
| data, 10, read_buf, &actual_read_len); |
| |
| status = read_buf[PIP2_RESP_STATUS_OFFSET]; |
| if (ret || (status != 0x00)) { |
| pt_debug(dev, DL_ERROR, |
| "%s ROM FILE_SEEK failure: 0x%02X for file = %d, ret = %d\n", |
| __func__, status, ret, file_handle); |
| ret = -EIO; |
| } |
| return ret; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_pip2_file_crc |
| * |
| * SUMMARY: Using the BL PIP2 commands to calculate CRC for a file or portion of |
| * the file. |
| * |
| * NOTE: The DUT must be in BL mode for this command to work |
| * NOTE: This command only can be used for BL version 1.8 or greater. |
| * BL version 1.8 added this change according to PGV-173. |
| * |
| * RETURNS: |
| * !0 = Error |
| * 0 = Success |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * file_handle - File handle to read from |
| * offset - start offset for CRC calculation |
| * length - number of bytes to calculate CRC over |
| * read_buf - pointer to the read buffer |
| ******************************************************************************/ |
| int _pt_pip2_file_crc(struct device *dev, u8 file_handle, |
| u32 offset, u32 length, u8 *read_buf) |
| { |
| int rc = 1; |
| u16 actual_read_len; |
| u8 data[10]; |
| |
| data[0] = file_handle; |
| data[1] = PIP2_FILE_IOCTL_CODE_FILE_CRC; |
| data[2] = 0xff & offset; |
| data[3] = 0xff & (offset >> 8); |
| data[4] = 0xff & (offset >> 16); |
| data[5] = 0xff & (offset >> 24); |
| data[6] = 0xff & length; |
| data[7] = 0xff & (length >> 8); |
| data[8] = 0xff & (length >> 16); |
| data[9] = 0xff & (length >> 24); |
| |
| rc = _pt_request_pip2_send_cmd(dev, |
| PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, |
| data, 10, read_buf, &actual_read_len); |
| |
| if (rc) |
| pt_debug(dev, DL_ERROR, |
| "%s Return FILE_CRC failure, rc = %d\n", |
| __func__, rc); |
| |
| return rc; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: pt_pip2_ping_test |
| * |
| * SUMMARY: BIST type test that uses the PIP2 PING command and ramps up the |
| * optional payload from 0 bytes to max_bytes and ensures the PIP2 |
| * response payload matches what was sent. |
| * The max payload size is 247: |
| * (255 - 2 byte reg address - 4 byte header - 2 byte CRC) |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *attr - pointer to device attributes |
| * *buf - pointer to output buffer |
| ******************************************************************************/ |
| int pt_pip2_ping_test(struct device *dev, int max_bytes, int *last_packet_size) |
| { |
| struct pt_core_data *cd = dev_get_drvdata(dev); |
| u16 actual_read_len; |
| u8 *read_buf = NULL; |
| u8 *data = NULL; |
| int data_offset = PIP2_RESP_STATUS_OFFSET; |
| int i = 1; |
| int j = 0; |
| int rc = 0; |
| |
| read_buf = kzalloc(PT_MAX_PIP2_MSG_SIZE, GFP_KERNEL); |
| if (!read_buf) |
| goto ping_test_exit; |
| |
| data = kzalloc(PT_MAX_PIP2_MSG_SIZE, GFP_KERNEL); |
| if (!data) |
| goto ping_test_exit; |
| |
| /* Load data payload with an array of walking 1's */ |
| for (i = 0; i < 255; i++) |
| data[i] = 0x01 << (i % 8); |
| |
| /* |
| * In HID/PIP3 mode, there is a new "Status" field for "Ping" |
| * command. So the offset of "Data" should be incremented by 1. |
| */ |
| if (cd->protocol_mode == PT_PROTOCOL_MODE_HID) |
| data_offset = PIP2_RESP_STATUS_OFFSET + 1; |
| |
| /* Send 'max_bytes' PING cmds using 'i' bytes as payload for each */ |
| for (i = 0; i <= max_bytes; i++) { |
| rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_UNPROTECTED, |
| PIP2_CMD_ID_PING, data, i, read_buf, |
| &actual_read_len); |
| if (rc) { |
| pt_debug(dev, DL_ERROR, |
| "%s: PING failed with %d byte payload\n", |
| __func__, i); |
| break; |
| } |
| /* Verify data returned matches data sent */ |
| for (j = 0; j < i; j++) { |
| if (read_buf[data_offset + j] != data[j]) { |
| pt_debug(dev, DL_DEBUG, |
| "%s: PING packet size %d: sent[%d]=0x%02X recv[%d]=0x%02X\n", |
| __func__, i, j, data[j], j, |
| read_buf[data_offset + j]); |
| goto ping_test_exit; |
| } |
| } |
| } |
| |
| ping_test_exit: |
| *last_packet_size = i - 1; |
| kfree(read_buf); |
| kfree(data); |
| return rc; |
| } |
| |
| #ifndef TTDL_KERNEL_SUBMISSION |
| /******************************************************************************* |
| * FUNCTION: _pt_ic_parse_input_hex |
| * |
| * SUMMARY: Parse a char data array as space delimited hex values into |
| * an int array. |
| * |
| * NOTE: _pt_ic_parse_input() function may have simliar work while the type of |
| * buffer to store data is "u32". This function is still needed by the |
| * "command" sysfs node because the buffer type to store data is "u8". |
| * |
| * RETURN: Length of parsed data |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *buf - pointer to buffer that holds the input array to parse |
| * buf_size - size of buf |
| * *ic_buf - pointer to array to store parsed data |
| * ic_buf_size - max size of ic_buf |
| ******************************************************************************/ |
| static int _pt_ic_parse_input_hex(struct device *dev, const char *buf, |
| size_t buf_size, u8 *ic_buf, size_t ic_buf_size) |
| { |
| const char *pbuf = buf; |
| unsigned long value; |
| char scan_buf[PT_MAX_PIP2_MSG_SIZE + 1]; |
| u32 i = 0; |
| u32 j; |
| int last = 0; |
| int ret; |
| |
| pt_debug(dev, DL_DEBUG, |
| "%s: pbuf=%p buf=%p size=%zu %s=%d buf=%s\n", |
| __func__, pbuf, buf, buf_size, "scan buf size", |
| PT_MAX_PIP2_MSG_SIZE, buf); |
| |
| while (pbuf <= (buf + buf_size)) { |
| if (i >= PT_MAX_PIP2_MSG_SIZE) { |
| pt_debug(dev, DL_ERROR, "%s: %s size=%d max=%d\n", |
| __func__, "Max cmd size exceeded", i, |
| PT_MAX_PIP2_MSG_SIZE); |
| return -EINVAL; |
| } |
| if (i >= ic_buf_size) { |
| pt_debug(dev, DL_ERROR, "%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, PT_MAX_PIP2_MSG_SIZE); |
| if ((last == ',') && (*pbuf == ',')) { |
| pt_debug(dev, DL_ERROR, "%s: %s \",,\" not allowed.\n", |
| __func__, "Invalid data format."); |
| return -EINVAL; |
| } |
| for (j = 0; j < (PT_MAX_PIP2_MSG_SIZE - 1) |
| && (pbuf < (buf + buf_size)) |
| && (*pbuf != ' ') |
| && (*pbuf != ','); j++) { |
| last = *pbuf; |
| scan_buf[j] = *pbuf++; |
| } |
| ret = kstrtoul(scan_buf, 16, &value); |
| if (ret < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: %s '%s' %s%s i=%d r=%d\n", __func__, |
| "Invalid data format. ", scan_buf, |
| "Use \"0xHH,...,0xHH\"", " instead.", |
| i, ret); |
| return ret; |
| } |
| |
| ic_buf[i] = value; |
| pt_debug(dev, DL_DEBUG, "%s: item = %d, value = 0x%02lX\n", |
| __func__, i, value); |
| i++; |
| } |
| |
| return i; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: _pt_ic_parse_input |
| * |
| * SUMMARY: Parse user sysfs input data as a space or comma delimited list of |
| * hex values or dec values into an int array with the following rules: |
| * 1) Hex values must have a "0x" prefix for each element or the first element |
| * only |
| * 2) Dec values do not have any prefix |
| * 3) It is not allowed to have a mix of dec and hex values in the user input |
| * string |
| * |
| * RETURN: Number of values parsed |
| * |
| * PARAMETERS: |
| * *dev - pointer to device structure |
| * *buf - pointer to buffer that holds the input array to parse |
| * buf_size - size of buf |
| * *out_buf - pointer to array to store parsed data |
| * out_buf_size - max size of buffer to be stored |
| ******************************************************************************/ |
| static int _pt_ic_parse_input(struct device *dev, |
| const char *buf, size_t buf_size, |
| u32 *out_buf, size_t out_buf_size) |
| { |
| const char *pbuf = buf; |
| unsigned long value; |
| char scan_buf[PT_MAX_PIP2_MSG_SIZE + 1]; |
| u32 i = 0; |
| u32 j; |
| int last = 0; |
| int ret = 0; |
| u8 str_base = 0; |
| |
| pt_debug(dev, DL_DEBUG, |
| "%s: in_buf_size=%zu out_buf_size=%zu %s=%d buf=%s\n", |
| __func__, buf_size, out_buf_size, "scan buf size", |
| PT_MAX_PIP2_MSG_SIZE, buf); |
| |
| while (pbuf <= (buf + buf_size)) { |
| if (i >= PT_MAX_PIP2_MSG_SIZE) { |
| pt_debug(dev, DL_ERROR, "%s: %s size=%d max=%d\n", |
| __func__, "Max cmd size exceeded", i, |
| PT_MAX_PIP2_MSG_SIZE); |
| ret = -EINVAL; |
| goto error; |
| } |
| if (i >= out_buf_size) { |
| pt_debug(dev, DL_ERROR, "%s: %s size=%d buf_size=%zu\n", |
| __func__, "Buffer size exceeded", i, |
| out_buf_size); |
| ret = -EINVAL; |
| goto error; |
| } |
| while (((*pbuf == ' ') || (*pbuf == ',')) |
| && (pbuf < (buf + buf_size))) { |
| last = *pbuf; |
| pbuf++; |
| } |
| |
| if (pbuf >= (buf + buf_size)) |
| break; |
| |
| memset(scan_buf, 0, PT_MAX_PIP2_MSG_SIZE); |
| if ((last == ',') && (*pbuf == ',')) { |
| pt_debug(dev, DL_ERROR, "%s: %s \",,\" not allowed.\n", |
| __func__, "Invalid data format."); |
| ret = -EINVAL; |
| goto error; |
| } |
| for (j = 0; j < (PT_MAX_PIP2_MSG_SIZE - 1) |
| && (pbuf < (buf + buf_size)) |
| && (*pbuf != ' ') |
| && (*pbuf != ','); j++) { |
| last = *pbuf; |
| scan_buf[j] = *pbuf++; |
| } |
| |
| if (i == 0) { |
| if ((strncmp(scan_buf, "0x", 2) == 0) || |
| (strncmp(scan_buf, "0X", 2) == 0)) |
| str_base = 16; |
| else |
| str_base = 10; |
| } else { |
| if (((strncmp(scan_buf, "0x", 2) == 0) || |
| (strncmp(scan_buf, "0X", 2) == 0)) && |
| (str_base == 10)) { |
| pt_debug(dev, DL_ERROR, |
| "%s: Decimal and Heximal data mixed\n", |
| __func__); |
| ret = -EINVAL; |
| goto error; |
| } |
| } |
| ret = kstrtoul(scan_buf, str_base, &value); |
| if (ret < 0) { |
| pt_debug(dev, DL_ERROR, |
| "%s: %s '%s' %s%s i=%d r=%d\n", __func__, |
| "Invalid data format. ", scan_buf, |
| "Use \"0xHH,...,0xHH\" or \"DD DD DD ... DD\"", |
| " instead.", i, ret); |
| goto error; |
| } |
| |
| out_buf[i] = value; |
| pt_debug(dev, DL_DEBUG, "%s: item = %d, value = 0x%02lx(%lu)\n", |
| __func__, i, value, value); |
| i++; |
| } |
| ret = i; |
| error: |
| return ret; |
| } |
| #endif /* !TTDL_KERNEL_SUBMISSION */ |
| |
| #ifdef TTHE_TUNER_SUPPORT |
| /******************************************************************************* |
| * FUNCTION: tthe_debugfs_open |
| * |
| * SUMMARY: Open method for tthe_tuner debugfs node. On some hosts the size of |
| * PT_MAX_PRBUF_SIZE (equal to PAGE_SIZE) is not large enough to handle |
| * printing a large number of fingers and sensor data without overflowing |
| * the buffer. tthe_tuner needs ~4K and so the buffer is sized to some |
| * even multiple of PAGE_SIZE |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *inode - file inode number |
| * *filp - file pointer to debugfs file |
| ******************************************************************************/ |
| static int tthe_debugfs_open(struct inode *inode, struct file *filp) |
| { |
| struct pt_core_data *cd = inode->i_private; |
| u32 buf_size = PT_MAX_PRBUF_SIZE; |
| |
| filp->private_data = inode->i_private; |
| |
| if (cd->tthe_buf) |
| return -EBUSY; |
| |
| while (buf_size < 4096) |
| buf_size = buf_size << 1; |
| |
| pt_debug(cd->dev, DL_INFO, "%s:PT_MAX_BRBUF_SIZE=%d buf_size=%d\n", |
| __func__, (int)PT_MAX_PRBUF_SIZE, (int)buf_size); |
| |
| cd->tthe_buf_size = buf_size; |
| cd->tthe_buf = kzalloc(cd->tthe_buf_size, GFP_KERNEL); |
| if (!cd->tthe_buf) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: tthe_debugfs_close |
| * |
| * SUMMARY: Close method for tthe_tuner debugfs node. |
| * |
| * RETURN: |
| * 0 = success |
| * !0 = failure |
| * |
| * PARAMETERS: |
| * *inode - file inode number |
| * *filp - file pointer to debugfs file |
| ******************************************************************************/ |
| static int tthe_debugfs_close(struct inode *inode, struct file *filp) |
| { |
| struct pt_core_data *cd = filp->private_data; |
| |
| filp->private_data = NULL; |
| |
| kfree(cd->tthe_buf); |
| cd->tthe_buf = NULL; |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: tthe_debugfs_store |
| * |
| * SUMMARY: Write method for tthe_tuner debugfs node. This function allows |
| * user configuration of some output values via a bitmask. |
| * 0x01 = HID USB vs I2C output |
| * 0xFE = Reserved |
| * |
| * RETURN: Size of debugfs data write |
| * |
| * PARAMETERS: |
| * *filp - file pointer to debugfs file |
| * *buf - the user space buffer to read to |
| * count - the maximum number of bytes to read |
| * *ppos - the current position in the buffer |
| ******************************************************************************/ |
| static ssize_t tthe_debugfs_store(struct file *filp, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct pt_core_data *cd = filp->private_data; |
| ssize_t length; |
| u32 input_data[2]; |
| u8 tmp_buf[4]; /* large enough for 1 32bit value */ |
| int rc = 0; |
| |
| mutex_lock(&cd->tthe_lock); |
| |
| /* copy data from user space */ |
| rc = simple_write_to_buffer(tmp_buf, sizeof(tmp_buf), |
| ppos, buf, count); |
| if (rc < 0) |
| goto exit; |
| |
| length = _pt_ic_parse_input(cd->dev, tmp_buf, count, |
| input_data, ARRAY_SIZE(input_data)); |
| |
| if (length == 1) { |
| mutex_lock(&(cd->system_lock)); |
| cd->tthe_hid_usb_format = input_data[0]; |
| if (input_data[0] == PT_TTHE_TUNER_FORMAT_HID_USB) |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Enable tthe_tuner HID-USB format\n", |
| __func__); |
| else if (input_data[0] == PT_TTHE_TUNER_FORMAT_HID_I2C) |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Enable tthe_tuner HID-I2C format\n", |
| __func__); |
| else if (input_data[0] == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_TO_PIP) |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Enable tthe_tuner HID-FINGER-TO-PIP format\n", |
| __func__); |
| else if (input_data[0] == |
| PT_TTHE_TUNER_FORMAT_HID_FINGER_AND_PEN_TO_PIP) |
| pt_debug(cd->dev, DL_INFO, |
| "%s: Enable tthe_tuner HID_FINGER_AND_PEN_TO_PIP format\n", |
| __func__); |
| else { |
| rc = -EINVAL; |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid parameter: %d\n", |
| __func__, input_data[0]); |
| } |
| mutex_unlock(&(cd->system_lock)); |
| } else { |
| rc = -EINVAL; |
| pt_debug(cd->dev, DL_ERROR, |
| "%s: Invalid number of parameters\n", __func__); |
| } |
| |
| exit: |
| mutex_unlock(&cd->tthe_lock); |
| if (rc) |
| return rc; |
| else |
| return count; |
| } |
| |
| /******************************************************************************* |
| * FUNCTION: tthe_debugfs_read |
| * |
| * SUMMARY: Read method for tthe_tuner debugfs node. This function prints |
| * tthe_buf to user buffer. |
| * |
| * RETURN: Size of debugfs data print |
| * |
| * PARAMETERS: |
| * *filp - file pointer to debugfs file |
| * *buf - the user space buffer to read to |
| * count - the maximum number of bytes to read |
| * *ppos - the current position in the buffer |
| ******************************************************************************/ |
| static ssize_t tthe_debugfs_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct pt_core_data *cd = filp->private_data; |
| int size; |
| int ret; |
| static int partial_read; |
| |
| 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; |
| } |
| |
| if (partial_read) { |
| ret = copy_to_user(buf, cd->tthe_buf + partial_read, size); |
| partial_read = 0; |
| } else { |
| ret = copy_to_user(buf, cd->tthe_buf, size); |
| } |
| |
| /* |
| * When size >= tthe_buf_len setting partial_read will cause NULL |
| * characters to be printed in the output. |
| */ |
| if (size == count && size < cd->tthe_buf_len) |
| partial_read = count; |
| |
| if (ret == size) { |
| mutex_unlock(&cd->tthe_lock); |
| 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, |
| |