diff --git a/synaptics_tcm/synaptics_tcm.h b/synaptics_tcm/synaptics_tcm.h new file mode 100644 index 0000000000..1d650b451f --- /dev/null +++ b/synaptics_tcm/synaptics_tcm.h @@ -0,0 +1,65 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_H_ +#define _SYNAPTICS_TCM_H_ + +#define I2C_MODULE_NAME "synaptics_tcm_i2c" +#define SPI_MODULE_NAME "synaptics_tcm_spi" + +struct syna_tcm_board_data { + bool x_flip; + bool y_flip; + bool swap_axes; + int irq_gpio; + int irq_on_state; + int power_gpio; + int power_on_state; + int reset_gpio; + int reset_on_state; + unsigned int spi_mode; + unsigned int power_delay_ms; + unsigned int reset_delay_ms; + unsigned int reset_active_ms; + unsigned int byte_delay_us; + unsigned int block_delay_us; + unsigned int ubl_i2c_addr; + unsigned int ubl_max_freq; + unsigned int ubl_byte_delay_us; + unsigned long irq_flags; + const char *pwr_reg_name; + const char *bus_reg_name; + const char *fw_name; + bool extend_report; +}; + +#endif diff --git a/synaptics_tcm/synaptics_tcm_core.c b/synaptics_tcm/synaptics_tcm_core.c new file mode 100644 index 0000000000..f095624ddf --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_core.c @@ -0,0 +1,3779 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include +#include +#include "synaptics_tcm_core.h" + +/* #define RESET_ON_RESUME */ + +/* #define RESUME_EARLY_UNBLANK */ + +#define RESET_ON_RESUME_DELAY_MS 50 + +#define PREDICTIVE_READING + +#define MIN_READ_LENGTH 9 + +#define KEEP_DRIVER_ON_ERROR + +/* #define FORCE_RUN_APPLICATION_FIRMWARE */ + +#define SYNA_VTG_MIN_UV 2800000 + +#define SYNA_VTG_MAX_UV 3300000 + +#define SYNA_LOAD_MAX_UA 30000 + +#define SYNA_VDD_VTG_MIN_UV 1800000 + +#define SYNA_VDD_VTG_MAX_UV 2000000 + +#define NOTIFIER_PRIORITY 2 + +#define RESPONSE_TIMEOUT_MS 3000 + +#define APP_STATUS_POLL_TIMEOUT_MS 1000 + +#define APP_STATUS_POLL_MS 100 + +#define ENABLE_IRQ_DELAY_MS 20 + +#define FALL_BACK_ON_POLLING + +#define POLLING_DELAY_MS 5 + +#define RUN_WATCHDOG true + +#define WATCHDOG_TRIGGER_COUNT 2 + +#define WATCHDOG_DELAY_MS 50000 + +#define MODE_SWITCH_DELAY_MS 100 + +#define READ_RETRY_US_MIN 5000 + +#define READ_RETRY_US_MAX 10000 + +#define WRITE_DELAY_US_MIN 500 + +#define WRITE_DELAY_US_MAX 1000 + +#define HOST_DOWNLOAD_WAIT_MS 100 + +#define HOST_DOWNLOAD_TIMEOUT_MS 1000 + +#define DYNAMIC_CONFIG_SYSFS_DIR_NAME "dynamic_config" + +#define dynamic_config_sysfs(c_name, id) \ +static ssize_t syna_tcm_sysfs_##c_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int retval; \ + unsigned short value; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->get_dynamic_config(tcm_hcd, id, &value); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to get dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, "%u\n", value); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} \ +\ +static ssize_t syna_tcm_sysfs_##c_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + int retval; \ + unsigned int input; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + if (kstrtouint(buf, 10, &input)) \ + return -EINVAL; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->set_dynamic_config(tcm_hcd, id, input); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to set dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = count; \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} + +DECLARE_COMPLETION(response_complete); + +static struct kobject *sysfs_dir; + +static struct syna_tcm_module_pool mod_pool; + +SHOW_PROTOTYPE(syna_tcm, info); +STORE_PROTOTYPE(syna_tcm, irq_en); +STORE_PROTOTYPE(syna_tcm, reset); +STORE_PROTOTYPE(syna_tcm, watchdog); +SHOW_STORE_PROTOTYPE(syna_tcm, no_doze); +SHOW_STORE_PROTOTYPE(syna_tcm, disable_noise_mitigation); +SHOW_STORE_PROTOTYPE(syna_tcm, inhibit_frequency_shift); +SHOW_STORE_PROTOTYPE(syna_tcm, requested_frequency); +SHOW_STORE_PROTOTYPE(syna_tcm, disable_hsync); +SHOW_STORE_PROTOTYPE(syna_tcm, rezero_on_exit_deep_sleep); +SHOW_STORE_PROTOTYPE(syna_tcm, charger_connected); +SHOW_STORE_PROTOTYPE(syna_tcm, no_baseline_relaxation); +SHOW_STORE_PROTOTYPE(syna_tcm, in_wakeup_gesture_mode); +SHOW_STORE_PROTOTYPE(syna_tcm, stimulus_fingers); +SHOW_STORE_PROTOTYPE(syna_tcm, grip_suppression_enabled); +SHOW_STORE_PROTOTYPE(syna_tcm, enable_thick_glove); +SHOW_STORE_PROTOTYPE(syna_tcm, enable_glove); + +static struct device_attribute *attrs[] = { + ATTRIFY(info), + ATTRIFY(irq_en), + ATTRIFY(reset), + ATTRIFY(watchdog), +}; + +static struct device_attribute *dynamic_config_attrs[] = { + ATTRIFY(no_doze), + ATTRIFY(disable_noise_mitigation), + ATTRIFY(inhibit_frequency_shift), + ATTRIFY(requested_frequency), + ATTRIFY(disable_hsync), + ATTRIFY(rezero_on_exit_deep_sleep), + ATTRIFY(charger_connected), + ATTRIFY(no_baseline_relaxation), + ATTRIFY(in_wakeup_gesture_mode), + ATTRIFY(stimulus_fingers), + ATTRIFY(grip_suppression_enabled), + ATTRIFY(enable_thick_glove), + ATTRIFY(enable_glove), +}; + +static ssize_t syna_tcm_sysfs_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int count; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + count = 0; + + retval = snprintf(buf, PAGE_SIZE - count, + "TouchComm version: %d\n", + tcm_hcd->id_info.version); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + if (SYNAPTICS_TCM_ID_SUBVERSION == 0) { + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d\n", + (unsigned char)(SYNAPTICS_TCM_ID_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_ID_VERSION); + } else { + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d.%d\n", + (unsigned char)(SYNAPTICS_TCM_ID_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_ID_VERSION, + SYNAPTICS_TCM_ID_SUBVERSION); + } + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + switch (tcm_hcd->id_info.mode) { + case MODE_APPLICATION: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Application\n"); + if (retval < 0) + goto exit; + break; + case MODE_HOST_DOWNLOAD: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Host Download\n"); + if (retval < 0) + goto exit; + break; + case MODE_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Bootloader\n"); + if (retval < 0) + goto exit; + break; + case MODE_TDDI_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: TDDI Bootloader\n"); + if (retval < 0) + goto exit; + break; + default: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Unknown (%d)\n", + tcm_hcd->id_info.mode); + if (retval < 0) + goto exit; + break; + } + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Part number: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = secure_memcpy(buf, + PAGE_SIZE - count, + tcm_hcd->id_info.part_number, + sizeof(tcm_hcd->id_info.part_number), + sizeof(tcm_hcd->id_info.part_number)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy part number string\n"); + goto exit; + } + buf += sizeof(tcm_hcd->id_info.part_number); + count += sizeof(tcm_hcd->id_info.part_number); + + retval = snprintf(buf, PAGE_SIZE - count, + "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Packrat number: %d\n", + tcm_hcd->packrat_number); + if (retval < 0) + goto exit; + + count += retval; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t syna_tcm_sysfs_irq_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (input == 0) { + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + goto exit; + } + } else if (input == 1) { + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto exit; + } + } else { + retval = -EINVAL; + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t syna_tcm_sysfs_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool hw_reset; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input == 1) + hw_reset = false; + else if (input == 2) + hw_reset = true; + else + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->reset(tcm_hcd, hw_reset, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t syna_tcm_sysfs_watchdog_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input != 0 && input != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + tcm_hcd->watchdog.run = input; + tcm_hcd->update_watchdog(tcm_hcd, input); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return count; +} + +dynamic_config_sysfs(no_doze, DC_NO_DOZE) + +dynamic_config_sysfs(disable_noise_mitigation, DC_DISABLE_NOISE_MITIGATION) + +dynamic_config_sysfs(inhibit_frequency_shift, DC_INHIBIT_FREQUENCY_SHIFT) + +dynamic_config_sysfs(requested_frequency, DC_REQUESTED_FREQUENCY) + +dynamic_config_sysfs(disable_hsync, DC_DISABLE_HSYNC) + +dynamic_config_sysfs(rezero_on_exit_deep_sleep, DC_REZERO_ON_EXIT_DEEP_SLEEP) + +dynamic_config_sysfs(charger_connected, DC_CHARGER_CONNECTED) + +dynamic_config_sysfs(no_baseline_relaxation, DC_NO_BASELINE_RELAXATION) + +dynamic_config_sysfs(in_wakeup_gesture_mode, DC_IN_WAKEUP_GESTURE_MODE) + +dynamic_config_sysfs(stimulus_fingers, DC_STIMULUS_FINGERS) + +dynamic_config_sysfs(grip_suppression_enabled, DC_GRIP_SUPPRESSION_ENABLED) + +dynamic_config_sysfs(enable_thick_glove, DC_ENABLE_THICK_GLOVE) + +dynamic_config_sysfs(enable_glove, DC_ENABLE_GLOVE) + +int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert) +{ + struct syna_tcm_module_handler *mod_handler; + + if (!mod_pool.initialized) { + mutex_init(&mod_pool.mutex); + INIT_LIST_HEAD(&mod_pool.list); + mod_pool.initialized = true; + } + + mutex_lock(&mod_pool.mutex); + + if (insert) { + mod_handler = kzalloc(sizeof(*mod_handler), GFP_KERNEL); + if (!mod_handler) { + mutex_unlock(&mod_pool.mutex); + return -ENOMEM; + } + mod_handler->mod_cb = mod_cb; + mod_handler->insert = true; + mod_handler->detach = false; + list_add_tail(&mod_handler->link, &mod_pool.list); + } else if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (mod_handler->mod_cb->type == mod_cb->type) { + mod_handler->insert = false; + mod_handler->detach = true; + goto exit; + } + } + } + +exit: + mutex_unlock(&mod_pool.mutex); + + if (mod_pool.queue_work) + queue_work(mod_pool.workqueue, &mod_pool.work); + + return 0; +} +EXPORT_SYMBOL(syna_tcm_add_module); + +static void syna_tcm_module_work(struct work_struct *work) +{ + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_module_handler *tmp_handler; + struct syna_tcm_hcd *tcm_hcd = mod_pool.tcm_hcd; + + mutex_lock(&mod_pool.mutex); + mod_pool.reconstructing = true; + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry_safe(mod_handler, + tmp_handler, + &mod_pool.list, + link) { + if (mod_handler->insert) { + if (mod_handler->mod_cb->init) + mod_handler->mod_cb->init(tcm_hcd); + mod_handler->insert = false; + } + if (mod_handler->detach) { + if (mod_handler->mod_cb->remove) + mod_handler->mod_cb->remove(tcm_hcd); + list_del(&mod_handler->link); + kfree(mod_handler); + } + } + } + + mod_pool.reconstructing = false; + mutex_unlock(&mod_pool.mutex); +} + +/** + * syna_tcm_report_notifier() - notify occurrence of report received from device + * + * @data: handle of core module + * + * The occurrence of the report generated by the device is forwarded to the + * asynchronous inbox of each registered application module. + */ +static int syna_tcm_report_notifier(void *data) +{ + struct sched_param param = { .sched_priority = NOTIFIER_PRIORITY }; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = data; + + sched_setscheduler_nocheck(current, SCHED_RR, ¶m); + + set_current_state(TASK_INTERRUPTIBLE); + + while (!kthread_should_stop()) { + schedule(); + + if (kthread_should_stop()) + break; + + set_current_state(TASK_RUNNING); + + mutex_lock(&mod_pool.mutex); + mod_pool.reconstructing = true; + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->asyncbox)) + mod_handler->mod_cb->asyncbox(tcm_hcd); + } + } + + mod_pool.reconstructing = false; + mutex_unlock(&mod_pool.mutex); + + set_current_state(TASK_INTERRUPTIBLE); + }; + + return 0; +} + +/** + * syna_tcm_dispatch_report() - dispatch report received from device + * + * @tcm_hcd: handle of core module + * + * The report generated by the device is forwarded to the synchronous inbox of + * each registered application module for further processing. In addition, the + * report notifier thread is woken up for asynchronous notification of the + * report occurrence. + */ +static void syna_tcm_dispatch_report(struct syna_tcm_hcd *tcm_hcd) +{ + struct syna_tcm_module_handler *mod_handler; + + LOCK_BUFFER(tcm_hcd->in); + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE]; + + tcm_hcd->report.buffer.buf_size = tcm_hcd->in.buf_size; + tcm_hcd->report.buffer.buf_size -= MESSAGE_HEADER_SIZE; + + tcm_hcd->report.buffer.data_length = tcm_hcd->payload_length; + + tcm_hcd->report.id = tcm_hcd->status_report_code; + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->syncbox)) + mod_handler->mod_cb->syncbox(tcm_hcd); + } + } + + tcm_hcd->async_report_id = tcm_hcd->status_report_code; + + mutex_unlock(&mod_pool.mutex); + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + UNLOCK_BUFFER(tcm_hcd->in); + + wake_up_process(tcm_hcd->notifier_thread); +} + +/** + * syna_tcm_dispatch_response() - dispatch response received from device + * + * @tcm_hcd: handle of core module + * + * The response to a command is forwarded to the sender of the command. + */ +static void syna_tcm_dispatch_response(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (atomic_read(&tcm_hcd->command_status) != CMD_BUSY) + return; + + tcm_hcd->response_code = tcm_hcd->status_report_code; + + if (tcm_hcd->payload_length == 0) { + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + goto exit; + } + + LOCK_BUFFER(tcm_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->resp, + tcm_hcd->payload_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->resp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->resp); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + goto exit; + } + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy(tcm_hcd->resp.buf, + tcm_hcd->resp.buf_size, + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_hcd->payload_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->in); + UNLOCK_BUFFER(tcm_hcd->resp); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + goto exit; + } + + tcm_hcd->resp.data_length = tcm_hcd->payload_length; + + UNLOCK_BUFFER(tcm_hcd->in); + UNLOCK_BUFFER(tcm_hcd->resp); + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + +exit: + complete(&response_complete); +} + +/** + * syna_tcm_dispatch_message() - dispatch message received from device + * + * @tcm_hcd: handle of core module + * + * The information received in the message read in from the device is dispatched + * to the appropriate destination based on whether the information represents a + * report or a response to a command. + */ +static void syna_tcm_dispatch_message(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *build_id; + unsigned int payload_length; + unsigned int max_write_size; + + if (tcm_hcd->status_report_code == REPORT_IDENTIFY) { + payload_length = tcm_hcd->payload_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + MIN(sizeof(tcm_hcd->id_info), payload_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + build_id = tcm_hcd->id_info.build_id; + tcm_hcd->packrat_number = le4_to_uint(build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + + LOGD(tcm_hcd->pdev->dev.parent, + "Received identify report (firmware mode = 0x%02x)\n", + tcm_hcd->id_info.mode); + + if (atomic_read(&tcm_hcd->command_status) == CMD_BUSY) { + switch (tcm_hcd->command) { + case CMD_RESET: + case CMD_RUN_BOOTLOADER_FIRMWARE: + case CMD_RUN_APPLICATION_FIRMWARE: + case CMD_ENTER_PRODUCTION_TEST_MODE: + tcm_hcd->response_code = STATUS_OK; + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + complete(&response_complete); + break; + default: + LOGN(tcm_hcd->pdev->dev.parent, + "Device has been reset\n"); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + complete(&response_complete); + break; + } + } + + if (tcm_hcd->id_info.mode == MODE_HOST_DOWNLOAD) { + tcm_hcd->host_download_mode = true; + return; + } + +#ifdef FORCE_RUN_APPLICATION_FIRMWARE + if (tcm_hcd->id_info.mode != MODE_APPLICATION && + !mutex_is_locked(&tcm_hcd->reset_mutex)) { + if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_RUN_APPLICATION_FIRMWARE); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + return; + } + } +#endif + } + + if (tcm_hcd->status_report_code >= REPORT_IDENTIFY) { + if ((mod_pool.reconstructing) + && (tcm_hcd->status_report_code == REPORT_TOUCH)) + return; + syna_tcm_dispatch_report(tcm_hcd); + + } else + syna_tcm_dispatch_response(tcm_hcd); + +} + +/** + * syna_tcm_continued_read() - retrieve entire payload from device + * + * @tcm_hcd: handle of core module + * + * Read transactions are carried out until the entire payload is retrieved from + * the device and stored in the handle of the core module. + */ +static int syna_tcm_continued_read(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char marker; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int total_length; + unsigned int remaining_length; + + total_length = MESSAGE_HEADER_SIZE + tcm_hcd->payload_length + 1; + + remaining_length = total_length - tcm_hcd->read_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_realloc_mem(tcm_hcd, + &tcm_hcd->in, + total_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reallocate memory for tcm_hcd->in.buf\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + /** + * available chunk space for payload = total chunk size minus header + * marker byte and header code byte + */ + if (tcm_hcd->rd_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->rd_chunk_size - 2; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = tcm_hcd->read_length; + + LOCK_BUFFER(tcm_hcd->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + tcm_hcd->in.buf[offset] = MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->temp, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for temp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->temp.buf, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + marker = tcm_hcd->temp.buf[0]; + code = tcm_hcd->temp.buf[1]; + + if (marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header marker (0x%02x)\n", + marker); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return -EIO; + } + + if (code != STATUS_CONTINUED_READ) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + code); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return -EIO; + } + + retval = secure_memcpy(&tcm_hcd->in.buf[offset], + tcm_hcd->in.buf_size - offset, + &tcm_hcd->temp.buf[2], + tcm_hcd->temp.buf_size - 2, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + offset += xfer_length; + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + + return 0; +} + +/** + * syna_tcm_raw_read() - retrieve specific number of data bytes from device + * + * @tcm_hcd: handle of core module + * @in_buf: buffer for storing data retrieved from device + * @length: number of bytes to retrieve from device + * + * Read transactions are carried out until the specific number of data bytes + * are retrieved from the device and stored in in_buf. + */ +static int syna_tcm_raw_read(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length) +{ + int retval; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + + if (length < 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid length information\n"); + return -EINVAL; + } + + /* minus header marker byte and header code byte */ + remaining_length = length - 2; + + /** + * available chunk space for data = total chunk size minus header + * marker byte and header code byte + */ + if (tcm_hcd->rd_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->rd_chunk_size - 2; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = 0; + + LOCK_BUFFER(tcm_hcd->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + in_buf[offset] = MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->temp, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for temp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->temp.buf, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + code = tcm_hcd->temp.buf[1]; + + if (idx == 0) { + retval = secure_memcpy(&in_buf[0], + length, + &tcm_hcd->temp.buf[0], + tcm_hcd->temp.buf_size, + xfer_length + 2); + } else { + if (code != STATUS_CONTINUED_READ) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + code); + UNLOCK_BUFFER(tcm_hcd->temp); + return -EIO; + } + + retval = secure_memcpy(&in_buf[offset], + length - offset, + &tcm_hcd->temp.buf[2], + tcm_hcd->temp.buf_size - 2, + xfer_length); + } + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + if (idx == 0) + offset += (xfer_length + 2); + else + offset += xfer_length; + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->temp); + + return 0; +} + +/** + * syna_tcm_raw_write() - write command/data to device without receiving + * response + * + * @tcm_hcd: handle of core module + * @command: command to send to device + * @data: data to send to device + * @length: length of data in bytes + * + * A command and its data, if any, are sent to the device. + */ +static int syna_tcm_raw_write(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + + remaining_length = length; + + /** + * available chunk space for data = total chunk size minus command + * byte + */ + if (tcm_hcd->wr_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->wr_chunk_size - 1; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + LOCK_BUFFER(tcm_hcd->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->out, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + + if (idx == 0) + tcm_hcd->out.buf[0] = command; + else + tcm_hcd->out.buf[0] = CMD_CONTINUE_WRITE; + + if (xfer_length) { + retval = secure_memcpy(&tcm_hcd->out.buf[1], + tcm_hcd->out.buf_size - 1, + &data[idx * chunk_space], + remaining_length, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + } + + retval = syna_tcm_write(tcm_hcd, + tcm_hcd->out.buf, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to device\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->out); + + return 0; +} + +/** + * syna_tcm_read_message() - read message from device + * + * @tcm_hcd: handle of core module + * @in_buf: buffer for storing data in raw read mode + * @length: length of data in bytes in raw read mode + * + * If in_buf is not NULL, raw read mode is used and syna_tcm_raw_read() is + * called. Otherwise, a message including its entire payload is retrieved from + * the device and dispatched to the appropriate destination. + */ +static int syna_tcm_read_message(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length) +{ + int retval; + bool retry; + unsigned int total_length; + struct syna_tcm_message_header *header; + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + if (in_buf != NULL) { + retval = syna_tcm_raw_read(tcm_hcd, in_buf, length); + goto exit; + } + + retry = true; + +retry: + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->in.buf, + tcm_hcd->read_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->in); + if (retry) { + usleep_range(READ_RETRY_US_MIN, READ_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + header = (struct syna_tcm_message_header *)tcm_hcd->in.buf; + + if (header->marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header marker (0x%02x)\n", + header->marker); + UNLOCK_BUFFER(tcm_hcd->in); + retval = -ENXIO; + if (retry) { + usleep_range(READ_RETRY_US_MIN, READ_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + tcm_hcd->status_report_code = header->code; + + tcm_hcd->payload_length = le2_to_uint(header->length); + + LOGD(tcm_hcd->pdev->dev.parent, + "Header code = 0x%02x\n", + tcm_hcd->status_report_code); + + LOGD(tcm_hcd->pdev->dev.parent, + "Payload length = %d\n", + tcm_hcd->payload_length); + + if (tcm_hcd->status_report_code <= STATUS_ERROR || + tcm_hcd->status_report_code == STATUS_INVALID) { + switch (tcm_hcd->status_report_code) { + case STATUS_OK: + break; + case STATUS_CONTINUED_READ: + LOGD(tcm_hcd->pdev->dev.parent, + "Out-of-sync continued read\n"); + case STATUS_IDLE: + case STATUS_BUSY: + tcm_hcd->payload_length = 0; + UNLOCK_BUFFER(tcm_hcd->in); + retval = 0; + goto exit; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + tcm_hcd->status_report_code); + if (tcm_hcd->status_report_code == STATUS_INVALID) { + if (retry) { + usleep_range(READ_RETRY_US_MIN, + READ_RETRY_US_MAX); + retry = false; + goto retry; + } else { + tcm_hcd->payload_length = 0; + } + } + } + } + + total_length = MESSAGE_HEADER_SIZE + tcm_hcd->payload_length + 1; + +#ifdef PREDICTIVE_READING + if (total_length <= tcm_hcd->read_length) { + goto check_padding; + } else if (total_length - 1 == tcm_hcd->read_length) { + tcm_hcd->in.buf[total_length - 1] = MESSAGE_PADDING; + goto check_padding; + } +#else + if (tcm_hcd->payload_length == 0) { + tcm_hcd->in.buf[total_length - 1] = MESSAGE_PADDING; + goto check_padding; + } +#endif + + UNLOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_continued_read(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do continued read\n"); + goto exit; + }; + + LOCK_BUFFER(tcm_hcd->in); + + tcm_hcd->in.buf[0] = MESSAGE_MARKER; + tcm_hcd->in.buf[1] = tcm_hcd->status_report_code; + tcm_hcd->in.buf[2] = (unsigned char)tcm_hcd->payload_length; + tcm_hcd->in.buf[3] = (unsigned char)(tcm_hcd->payload_length >> 8); + +check_padding: + if (tcm_hcd->in.buf[total_length - 1] != MESSAGE_PADDING) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect message padding byte (0x%02x)\n", + tcm_hcd->in.buf[total_length - 1]); + UNLOCK_BUFFER(tcm_hcd->in); + retval = -EIO; + goto exit; + } + + UNLOCK_BUFFER(tcm_hcd->in); + +#ifdef PREDICTIVE_READING + total_length = MAX(total_length, MIN_READ_LENGTH); + tcm_hcd->read_length = MIN(total_length, tcm_hcd->rd_chunk_size); + if (tcm_hcd->rd_chunk_size == 0) + tcm_hcd->read_length = total_length; +#endif + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + syna_tcm_dispatch_message(tcm_hcd); + + retval = 0; + + return retval; + +exit: + if (retval < 0) { + if (atomic_read(&tcm_hcd->command_status) == CMD_BUSY) { + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + complete(&response_complete); + } + } + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + return retval; +} + +/** + * syna_tcm_write_message() - write message to device and receive response + * + * @tcm_hcd: handle of core module + * @command: command to send to device + * @payload: payload of command + * @length: length of payload in bytes + * @resp_buf: buffer for storing command response + * @resp_buf_size: size of response buffer in bytes + * @resp_length: length of command response in bytes + * @response_code: status code returned in command response + * @polling_delay_ms: delay time after sending command before resuming polling + * + * If resp_buf is NULL, raw write mode is used and syna_tcm_raw_write() is + * called. Otherwise, a command and its payload, if any, are sent to the device + * and the response to the command generated by the device is read in. + */ +static int syna_tcm_write_message(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *payload, + unsigned int length, unsigned char **resp_buf, + unsigned int *resp_buf_size, unsigned int *resp_length, + unsigned char *response_code, unsigned int polling_delay_ms) +{ + int retval; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int command_status; + + if (response_code != NULL) + *response_code = STATUS_INVALID; + + if (!tcm_hcd->do_polling && current->pid == tcm_hcd->isr_pid) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid execution context\n"); + return -EINVAL; + } + + mutex_lock(&tcm_hcd->command_mutex); + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + if (resp_buf == NULL) { + retval = syna_tcm_raw_write(tcm_hcd, command, payload, length); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + if (tcm_hcd->do_polling && polling_delay_ms) { + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + } + + atomic_set(&tcm_hcd->command_status, CMD_BUSY); + + reinit_completion(&response_complete); + + tcm_hcd->command = command; + + LOCK_BUFFER(tcm_hcd->resp); + + tcm_hcd->resp.buf = *resp_buf; + tcm_hcd->resp.buf_size = *resp_buf_size; + tcm_hcd->resp.data_length = 0; + + UNLOCK_BUFFER(tcm_hcd->resp); + + /* adding two length bytes as part of payload */ + remaining_length = length + 2; + + /** + * available chunk space for payload = total chunk size minus command + * byte + */ + if (tcm_hcd->wr_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->wr_chunk_size - 1; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + LOGD(tcm_hcd->pdev->dev.parent, + "Command = 0x%02x\n", + command); + + LOCK_BUFFER(tcm_hcd->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->out, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + if (idx == 0) { + tcm_hcd->out.buf[0] = command; + tcm_hcd->out.buf[1] = (unsigned char)length; + tcm_hcd->out.buf[2] = (unsigned char)(length >> 8); + + if (xfer_length > 2) { + retval = secure_memcpy(&tcm_hcd->out.buf[3], + tcm_hcd->out.buf_size - 3, + payload, + remaining_length - 2, + xfer_length - 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + } + } else { + tcm_hcd->out.buf[0] = CMD_CONTINUE_WRITE; + + retval = secure_memcpy(&tcm_hcd->out.buf[1], + tcm_hcd->out.buf_size - 1, + &payload[idx * chunk_space - 2], + remaining_length, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + } + + retval = syna_tcm_write(tcm_hcd, + tcm_hcd->out.buf, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to device\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + remaining_length -= xfer_length; + + if (chunks > 1) + usleep_range(WRITE_DELAY_US_MIN, WRITE_DELAY_US_MAX); + } + + UNLOCK_BUFFER(tcm_hcd->out); + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + if (tcm_hcd->do_polling && polling_delay_ms) { + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(polling_delay_ms)); + } + + retval = wait_for_completion_timeout(&response_complete, + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for response (command 0x%02x)\n", + tcm_hcd->command); + retval = -EIO; + goto exit; + } + + command_status = atomic_read(&tcm_hcd->command_status); + if (command_status != CMD_IDLE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get valid response (command 0x%02x)\n", + tcm_hcd->command); + retval = -EIO; + goto exit; + } + + LOCK_BUFFER(tcm_hcd->resp); + + if (tcm_hcd->response_code != STATUS_OK) { + if (tcm_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Error code = 0x%02x (command 0x%02x)\n", + tcm_hcd->resp.buf[0], tcm_hcd->command); + } + retval = -EIO; + } else { + retval = 0; + } + + *resp_buf = tcm_hcd->resp.buf; + *resp_buf_size = tcm_hcd->resp.buf_size; + *resp_length = tcm_hcd->resp.data_length; + + if (response_code != NULL) + *response_code = tcm_hcd->response_code; + + UNLOCK_BUFFER(tcm_hcd->resp); + +exit: + tcm_hcd->command = CMD_NONE; + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + + mutex_unlock(&tcm_hcd->command_mutex); + + return retval; +} + +static int syna_tcm_wait_hdl(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + msleep(HOST_DOWNLOAD_WAIT_MS); + + if (!atomic_read(&tcm_hcd->host_downloading)) + return 0; + + retval = wait_event_interruptible_timeout(tcm_hcd->hdl_wq, + !atomic_read(&tcm_hcd->host_downloading), + msecs_to_jiffies(HOST_DOWNLOAD_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for completion of host download\n"); + atomic_set(&tcm_hcd->host_downloading, 0); + retval = -EIO; + } else { + retval = 0; + } + + return retval; +} + +static void syna_tcm_check_hdl(struct syna_tcm_hcd *tcm_hcd) +{ + struct syna_tcm_module_handler *mod_handler; + + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = NULL; + tcm_hcd->report.buffer.buf_size = 0; + tcm_hcd->report.buffer.data_length = 0; + tcm_hcd->report.id = REPORT_HDL; + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->syncbox)) + mod_handler->mod_cb->syncbox(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); +} + +static void syna_tcm_update_watchdog(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + + if (!tcm_hcd->watchdog.run) { + tcm_hcd->watchdog.count = 0; + return; + } + + if (en) { + queue_delayed_work(tcm_hcd->watchdog.workqueue, + &tcm_hcd->watchdog.work, + msecs_to_jiffies(WATCHDOG_DELAY_MS)); + } else { + tcm_hcd->watchdog.count = 0; + } +} + +static void syna_tcm_watchdog_work(struct work_struct *work) +{ + int retval; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct syna_tcm_watchdog *watchdog = + container_of(delayed_work, struct syna_tcm_watchdog, + work); + struct syna_tcm_hcd *tcm_hcd = + container_of(watchdog, struct syna_tcm_hcd, watchdog); + + if (mutex_is_locked(&tcm_hcd->rw_ctrl_mutex)) + goto exit; + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + retval = syna_tcm_read(tcm_hcd, + &tcm_hcd->marker, + 1); + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + if (retval < 0 || tcm_hcd->marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + + tcm_hcd->watchdog.count++; + + if (tcm_hcd->watchdog.count >= WATCHDOG_TRIGGER_COUNT) { + retval = tcm_hcd->reset(tcm_hcd, true, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + tcm_hcd->watchdog.count = 0; + } + } + +exit: + queue_delayed_work(tcm_hcd->watchdog.workqueue, + &tcm_hcd->watchdog.work, + msecs_to_jiffies(WATCHDOG_DELAY_MS)); +} + +static void syna_tcm_polling_work(struct work_struct *work) +{ + int retval; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct syna_tcm_hcd *tcm_hcd = + container_of(delayed_work, struct syna_tcm_hcd, + polling_work); + + if (!tcm_hcd->do_polling) + return; + + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + if (retval == -ENXIO && tcm_hcd->hw_if->bus_io->type == BUS_SPI) + syna_tcm_check_hdl(tcm_hcd); + } + + if (!(tcm_hcd->in_suspend && retval < 0)) { + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(POLLING_DELAY_MS)); + } +} + +static irqreturn_t syna_tcm_isr(int irq, void *data) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = data; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (unlikely(gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state)) + goto exit; + + tcm_hcd->isr_pid = current->pid; + + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to read message\n"); + if (retval == -ENXIO && + tcm_hcd->hw_if->bus_io->type == BUS_SPI) + syna_tcm_check_hdl(tcm_hcd); + } + +exit: + return IRQ_HANDLED; +} + +static int syna_tcm_enable_irq(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + static bool irq_freed = true; + + mutex_lock(&tcm_hcd->irq_en_mutex); + + if (en) { + if (tcm_hcd->irq_enabled) { + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt already enabled\n"); + retval = 0; + goto exit; + } + + if (bdata->irq_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid IRQ GPIO\n"); + retval = -EINVAL; + goto queue_polling_work; + } + + if (irq_freed) { + retval = request_threaded_irq(tcm_hcd->irq, NULL, + syna_tcm_isr, bdata->irq_flags, + PLATFORM_DRIVER_NAME, tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create interrupt thread\n"); + } + } else { + enable_irq(tcm_hcd->irq); + retval = 0; + } + +queue_polling_work: + if (retval < 0) { +#ifdef FALL_BACK_ON_POLLING + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(POLLING_DELAY_MS)); + tcm_hcd->do_polling = true; + retval = 0; +#endif + } + + if (retval < 0) + goto exit; + else + msleep(ENABLE_IRQ_DELAY_MS); + } else { + if (!tcm_hcd->irq_enabled) { + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt already disabled\n"); + retval = 0; + goto exit; + } + + if (bdata->irq_gpio >= 0) { + if (ns) { + disable_irq_nosync(tcm_hcd->irq); + } else { + disable_irq(tcm_hcd->irq); + free_irq(tcm_hcd->irq, tcm_hcd); + } + irq_freed = !ns; + } + + if (ns) { + cancel_delayed_work(&tcm_hcd->polling_work); + } else { + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + } + + tcm_hcd->do_polling = false; + } + + retval = 0; + +exit: + if (retval == 0) + tcm_hcd->irq_enabled = en; + + mutex_unlock(&tcm_hcd->irq_en_mutex); + + return retval; +} + +static int syna_tcm_set_gpio(struct syna_tcm_hcd *tcm_hcd, int gpio, + bool config, int dir, int state) +{ + int retval; + char label[16]; + + if (config) { + retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set GPIO label\n"); + return retval; + } + + retval = gpio_request(gpio, label); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to request GPIO %d\n", + gpio); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set GPIO %d direction\n", + gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return 0; +} + +static int syna_tcm_config_gpio(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->irq_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, + true, 0, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure interrupt GPIO\n"); + goto err_set_gpio_irq; + } + } + + if (bdata->power_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, + true, 1, !bdata->power_on_state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure power GPIO\n"); + goto err_set_gpio_power; + } + } + + if (bdata->reset_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, + true, 1, !bdata->reset_on_state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure reset GPIO\n"); + goto err_set_gpio_reset; + } + } + + if (bdata->power_gpio >= 0) { + gpio_set_value(bdata->power_gpio, bdata->power_on_state); + msleep(bdata->power_delay_ms); + } + + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + } + + return 0; + +err_set_gpio_reset: + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + +err_set_gpio_power: + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + +err_set_gpio_irq: + return retval; +} + +static int syna_tcm_enable_regulator(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!en) { + retval = 0; + goto disable_pwr_reg; + } + + if (tcm_hcd->bus_reg) { + retval = regulator_set_voltage(tcm_hcd->bus_reg, + SYNA_VDD_VTG_MIN_UV, SYNA_VDD_VTG_MAX_UV); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set bus regulator voltage failed\n"); + goto exit; + } + + retval = regulator_set_load(tcm_hcd->bus_reg, + SYNA_LOAD_MAX_UA); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set bus regulator load failed\n"); + goto exit; + } + + retval = regulator_enable(tcm_hcd->bus_reg); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable bus regulator\n"); + goto exit; + } + } + + if (tcm_hcd->pwr_reg) { + if (regulator_count_voltages(tcm_hcd->pwr_reg) > 0) { + retval = regulator_set_voltage(tcm_hcd->pwr_reg, + SYNA_VTG_MIN_UV, SYNA_VTG_MAX_UV); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set power regulator voltage failed\n"); + goto disable_bus_reg; + } + retval = regulator_set_load(tcm_hcd->pwr_reg, + SYNA_LOAD_MAX_UA); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set power regulator load failed\n"); + goto disable_bus_reg; + } + } + + retval = regulator_enable(tcm_hcd->pwr_reg); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable power regulator\n"); + goto disable_bus_reg; + } + msleep(bdata->power_delay_ms); + } + + return 0; + +disable_pwr_reg: + if (tcm_hcd->pwr_reg) { + if (regulator_count_voltages(tcm_hcd->pwr_reg) > 0) { + regulator_set_load(tcm_hcd->pwr_reg, 0); + regulator_set_voltage(tcm_hcd->pwr_reg, 0, + SYNA_VTG_MAX_UV); + } + regulator_disable(tcm_hcd->pwr_reg); + } + +disable_bus_reg: + if (tcm_hcd->bus_reg) { + regulator_set_load(tcm_hcd->bus_reg, 0); + regulator_set_voltage(tcm_hcd->bus_reg, 0, + SYNA_VDD_VTG_MAX_UV); + regulator_disable(tcm_hcd->bus_reg); + } + +exit: + return retval; +} + +static int syna_tcm_get_regulator(struct syna_tcm_hcd *tcm_hcd, bool get) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if (bdata->bus_reg_name != NULL && *bdata->bus_reg_name != 0) { + tcm_hcd->bus_reg = regulator_get(tcm_hcd->pdev->dev.parent, + bdata->bus_reg_name); + if (IS_ERR(tcm_hcd->bus_reg)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get bus regulator\n"); + retval = PTR_ERR(tcm_hcd->bus_reg); + goto regulator_put; + } + } + + if (bdata->pwr_reg_name != NULL && *bdata->pwr_reg_name != 0) { + tcm_hcd->pwr_reg = regulator_get(tcm_hcd->pdev->dev.parent, + bdata->pwr_reg_name); + if (IS_ERR(tcm_hcd->pwr_reg)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get power regulator\n"); + retval = PTR_ERR(tcm_hcd->pwr_reg); + goto regulator_put; + } + } + + return 0; + +regulator_put: + if (tcm_hcd->bus_reg) { + regulator_put(tcm_hcd->bus_reg); + tcm_hcd->bus_reg = NULL; + } + + if (tcm_hcd->pwr_reg) { + regulator_put(tcm_hcd->pwr_reg); + tcm_hcd->pwr_reg = NULL; + } + + return retval; +} + +static int syna_tcm_get_app_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned int timeout; + + timeout = APP_STATUS_POLL_TIMEOUT_MS; + + resp_buf = NULL; + resp_buf_size = 0; + +get_app_info: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_APPLICATION_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_APPLICATION_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->app_info, + sizeof(tcm_hcd->app_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->app_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application info\n"); + goto exit; + } + + tcm_hcd->app_status = le2_to_uint(tcm_hcd->app_info.status); + + if (tcm_hcd->app_status == APP_STATUS_BOOTING || + tcm_hcd->app_status == APP_STATUS_UPDATING) { + if (timeout > 0) { + msleep(APP_STATUS_POLL_MS); + timeout -= APP_STATUS_POLL_MS; + goto get_app_info; + } + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_get_boot_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_BOOT_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_BOOT_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->boot_info, + sizeof(tcm_hcd->boot_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->boot_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy boot info\n"); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_identify(struct syna_tcm_hcd *tcm_hcd, bool id) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned int max_write_size; + + resp_buf = NULL; + resp_buf_size = 0; + + mutex_lock(&tcm_hcd->identify_mutex); + + if (!id) + goto get_info; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_IDENTIFY, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_IDENTIFY)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->id_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + goto exit; + } + + tcm_hcd->packrat_number = le4_to_uint(tcm_hcd->id_info.build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + +get_info: + switch (tcm_hcd->id_info.mode) { + case MODE_APPLICATION: + retval = syna_tcm_get_app_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get application info\n"); + goto exit; + } + break; + case MODE_BOOTLOADER: + case MODE_TDDI_BOOTLOADER: + retval = syna_tcm_get_boot_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get boot info\n"); + goto exit; + } + break; + default: + break; + } + + retval = 0; + +exit: + mutex_unlock(&tcm_hcd->identify_mutex); + + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_production_test_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + bool retry; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + retry = true; + + resp_buf = NULL; + resp_buf_size = 0; + +retry: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ENTER_PRODUCTION_TEST_MODE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ENTER_PRODUCTION_TEST_MODE)); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run production test firmware\n"); + if (retry) { + retry = false; + goto retry; + } + retval = -EINVAL; + goto exit; + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_application_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + bool retry; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + retry = true; + + resp_buf = NULL; + resp_buf_size = 0; + +retry: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_APPLICATION_FIRMWARE)); + goto exit; + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application (status = 0x%02x)\n", + tcm_hcd->boot_info.status); + if (retry) { + retry = false; + goto retry; + } + retval = -EINVAL; + goto exit; + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_bootloader_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_BOOTLOADER_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_BOOTLOADER_FIRMWARE)); + goto exit; + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + retval = -EINVAL; + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_switch_mode(struct syna_tcm_hcd *tcm_hcd, + enum firmware_mode mode) +{ + int retval; + + mutex_lock(&tcm_hcd->reset_mutex); + + tcm_hcd->update_watchdog(tcm_hcd, false); + + switch (mode) { + case FW_MODE_BOOTLOADER: + retval = syna_tcm_run_bootloader_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to bootloader mode\n"); + goto exit; + } + break; + case FW_MODE_APPLICATION: + retval = syna_tcm_run_application_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to application mode\n"); + goto exit; + } + break; + case FW_MODE_PRODUCTION_TEST: + retval = syna_tcm_run_production_test_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to production test mode\n"); + goto exit; + } + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, "Invalid firmware mode\n"); + retval = -EINVAL; + goto exit; + } + + retval = 0; + +exit: + tcm_hcd->update_watchdog(tcm_hcd, true); + + mutex_unlock(&tcm_hcd->reset_mutex); + + return retval; +} + +static int syna_tcm_get_dynamic_config(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short *value) +{ + int retval; + unsigned char out_buf; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + out_buf = (unsigned char)id; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_DYNAMIC_CONFIG, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_DYNAMIC_CONFIG)); + goto exit; + } + + if (resp_length < 2) { + LOGE(tcm_hcd->pdev->dev.parent, "Invalid data length\n"); + retval = -EINVAL; + goto exit; + } + + *value = (unsigned short)le2_to_uint(resp_buf); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_set_dynamic_config(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short value) +{ + int retval; + unsigned char out_buf[3]; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + out_buf[0] = (unsigned char)id; + out_buf[1] = (unsigned char)value; + out_buf[2] = (unsigned char)(value >> 8); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SET_DYNAMIC_CONFIG, + out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_SET_DYNAMIC_CONFIG)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_get_data_location(struct syna_tcm_hcd *tcm_hcd, + enum flash_area area, unsigned int *addr, unsigned int *length) +{ + int retval; + unsigned char out_buf; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + switch (area) { + case CUSTOM_LCM: + out_buf = LCM_DATA; + break; + case CUSTOM_OEM: + out_buf = OEM_DATA; + break; + case PPDT: + out_buf = PPDT_DATA; + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid flash area\n"); + return -EINVAL; + } + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_DATA_LOCATION, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_DATA_LOCATION)); + goto exit; + } + + if (resp_length != 4) { + LOGE(tcm_hcd->pdev->dev.parent, "Invalid data length\n"); + retval = -EINVAL; + goto exit; + } + + *addr = le2_to_uint(&resp_buf[0]); + *length = le2_to_uint(&resp_buf[2]); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_sleep(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + int retval; + unsigned char command; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + command = en ? CMD_ENTER_DEEP_SLEEP : CMD_EXIT_DEEP_SLEEP; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + command, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + en ? + STR(CMD_ENTER_DEEP_SLEEP) : + STR(CMD_EXIT_DEEP_SLEEP)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_reset(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + struct syna_tcm_module_handler *mod_handler; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + resp_buf = NULL; + resp_buf_size = 0; + + mutex_lock(&tcm_hcd->reset_mutex); + + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, false); + + if (hw) { + if (bdata->reset_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Hardware reset unavailable\n"); + retval = -EINVAL; + goto exit; + } + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + } else { + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RESET, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + bdata->reset_delay_ms); + if (retval < 0 && !tcm_hcd->host_download_mode) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RESET)); + goto exit; + } + } + + if (tcm_hcd->host_download_mode) { + mutex_unlock(&tcm_hcd->reset_mutex); + kfree(resp_buf); + retval = syna_tcm_wait_hdl(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for completion of download\n"); + return retval; + } + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, true); + return 0; + } + + msleep(bdata->reset_delay_ms); + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) + goto get_features; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGN(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_APPLICATION_FIRMWARE)); + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + +get_features: + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware mode = 0x%02x\n", + tcm_hcd->id_info.mode); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + LOGN(tcm_hcd->pdev->dev.parent, + "Boot status = 0x%02x\n", + tcm_hcd->boot_info.status); + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) + goto dispatch_reset; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_FEATURES, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) + LOGN(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_FEATURES)); + else { + retval = secure_memcpy((unsigned char *)&tcm_hcd->features, + sizeof(tcm_hcd->features), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->features), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy feature description\n"); + } + } + +dispatch_reset: + mutex_lock(&mod_pool.mutex); + mod_pool.reconstructing = true; + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->reset)) + mod_handler->mod_cb->reset(tcm_hcd); + } + } + + mod_pool.reconstructing = false; + mutex_unlock(&mod_pool.mutex); + + retval = 0; + +exit: + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, true); + + mutex_unlock(&tcm_hcd->reset_mutex); + + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_rezero(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_REZERO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_REZERO)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static void syna_tcm_helper_work(struct work_struct *work) +{ + int retval; + unsigned char task; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_helper *helper = + container_of(work, struct syna_tcm_helper, work); + struct syna_tcm_hcd *tcm_hcd = + container_of(helper, struct syna_tcm_hcd, helper); + + task = atomic_read(&helper->task); + + switch (task) { + case HELP_RUN_APPLICATION_FIRMWARE: + mutex_lock(&tcm_hcd->reset_mutex); + tcm_hcd->update_watchdog(tcm_hcd, false); + retval = syna_tcm_run_application_firmware(tcm_hcd); + if (retval < 0) + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to application mode\n"); + tcm_hcd->update_watchdog(tcm_hcd, true); + mutex_unlock(&tcm_hcd->reset_mutex); + break; + case HELP_SEND_RESET_NOTIFICATION: + mutex_lock(&tcm_hcd->reset_mutex); + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + mutex_unlock(&tcm_hcd->reset_mutex); + break; + } + mutex_lock(&mod_pool.mutex); + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->reset)) + mod_handler->mod_cb->reset(tcm_hcd); + } + } + mutex_unlock(&mod_pool.mutex); + mutex_unlock(&tcm_hcd->reset_mutex); + wake_up_interruptible(&tcm_hcd->hdl_wq); + break; + default: + break; + } + + atomic_set(&helper->task, HELP_NONE); +} + +#if defined(CONFIG_PM) || defined(CONFIG_DRM) || defined(CONFIG_FB) + +static int syna_tcm_deferred_probe(struct device *dev); + +static int syna_tcm_resume(struct device *dev) +{ + int retval; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + if (!tcm_hcd->init_okay) + syna_tcm_deferred_probe(dev); + + if (!tcm_hcd->in_suspend) + return 0; + else { + if (tcm_hcd->irq_enabled) { + tcm_hcd->watchdog.run = false; + tcm_hcd->update_watchdog(tcm_hcd, false); + tcm_hcd->enable_irq(tcm_hcd, false, false); + } + } + + retval = syna_tcm_enable_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable regulators\n"); + } + + retval = pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_active); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + + if (tcm_hcd->host_download_mode) { +#ifndef WAKEUP_GESTURE + syna_tcm_check_hdl(tcm_hcd); + retval = syna_tcm_wait_hdl(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for completion of download\n"); + goto exit; + } +#endif + } else { + tcm_hcd->enable_irq(tcm_hcd, true, NULL); +#ifdef RESET_ON_RESUME + msleep(RESET_ON_RESUME_DELAY_MS); + goto do_reset; +#endif + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + goto do_reset; + } + + retval = tcm_hcd->sleep(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to exit deep sleep\n"); + goto exit; + } + + retval = syna_tcm_rezero(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to rezero\n"); + goto exit; + } + + goto mod_resume; + +do_reset: + retval = tcm_hcd->reset(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + retval = 0; + goto exit; + } + +mod_resume: + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->resume)) + mod_handler->mod_cb->resume(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + retval = 0; + +exit: + tcm_hcd->in_suspend = false; + + return retval; +} + +static int syna_tcm_suspend(struct device *dev) +{ + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + int retval; + + if (tcm_hcd->in_suspend || !tcm_hcd->init_okay) + return 0; + + if (pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_suspend)) + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate\n", + __func__, PINCTRL_STATE_RELEASE); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->suspend)) + mod_handler->mod_cb->suspend(tcm_hcd); + } + } + + retval = syna_tcm_enable_regulator(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable regulators\n"); + } + + mutex_unlock(&mod_pool.mutex); + + tcm_hcd->in_suspend = true; + + return retval; +} +#endif + + + +#ifdef CONFIG_DRM + +static int syna_tcm_early_suspend(struct device *dev) +{ +#ifndef WAKEUP_GESTURE + int retval; +#endif + + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + if (tcm_hcd->in_suspend || !tcm_hcd->init_okay) + return 0; + + if (pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_suspend)) + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate\n", + __func__, PINCTRL_STATE_RELEASE); + + tcm_hcd->update_watchdog(tcm_hcd, false); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return 0; + } + +#ifndef WAKEUP_GESTURE + retval = tcm_hcd->sleep(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter deep sleep\n"); + return retval; + } +#endif + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->early_suspend)) + mod_handler->mod_cb->early_suspend(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + +#ifndef WAKEUP_GESTURE + tcm_hcd->enable_irq(tcm_hcd, false, true); +#endif + + return 0; +} + +static int syna_tcm_fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + int retval = 0; + int transition; + struct drm_panel_notifier *evdata = data; + struct syna_tcm_hcd *tcm_hcd = + container_of(nb, struct syna_tcm_hcd, fb_notifier); + + if (!evdata) + return 0; + + transition = *(int *)evdata->data; + + if (atomic_read(&tcm_hcd->firmware_flashing) + && transition == DRM_PANEL_BLANK_POWERDOWN) { + retval = wait_event_interruptible_timeout(tcm_hcd->reflash_wq, + !atomic_read(&tcm_hcd->firmware_flashing), + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for flashing firmware\n"); + atomic_set(&tcm_hcd->firmware_flashing, 0); + return -EIO; + } + } + + if (action == DRM_PANEL_EARLY_EVENT_BLANK && + transition == DRM_PANEL_BLANK_POWERDOWN) + retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev); + else if (action == DRM_PANEL_EVENT_BLANK) { + if (transition == DRM_PANEL_BLANK_POWERDOWN) { + retval = syna_tcm_suspend(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready = 0; + } else if (transition == DRM_PANEL_BLANK_UNBLANK) { +#ifndef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + } else if (action == DRM_PANEL_EARLY_EVENT_BLANK && + transition == DRM_PANEL_BLANK_UNBLANK) { +#ifdef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + + + return 0; +} + +#elif CONFIG_FB + +static int syna_tcm_early_suspend(struct device *dev) +{ +#ifndef WAKEUP_GESTURE + int retval; +#endif + + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + if (tcm_hcd->in_suspend) + return 0; + + tcm_hcd->update_watchdog(tcm_hcd, false); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return 0; + } + +#ifndef WAKEUP_GESTURE + retval = tcm_hcd->sleep(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter deep sleep\n"); + return retval; + } +#endif + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->early_suspend)) + mod_handler->mod_cb->early_suspend(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + return 0; +} + +static int syna_tcm_fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + int retval = 0; + int *transition; + struct fb_event *evdata = data; + struct syna_tcm_hcd *tcm_hcd = + container_of(nb, struct syna_tcm_hcd, fb_notifier); + + if (!evdata || !evdata->data || !tcm_hcd) + return 0; + + transition = (int *)evdata->data; + + if (atomic_read(&tcm_hcd->firmware_flashing) + && *transition == FB_BLANK_POWERDOWN) { + retval = wait_event_interruptible_timeout(tcm_hcd->reflash_wq, + !atomic_read(&tcm_hcd->firmware_flashing), + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for flashing firmware\n"); + atomic_set(&tcm_hcd->firmware_flashing, 0); + return -EIO; + } + } + + if (action == FB_EARLY_EVENT_BLANK && + *transition == FB_BLANK_POWERDOWN) + retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev); + else if (action == FB_EVENT_BLANK) { + if (*transition == FB_BLANK_POWERDOWN) { + retval = syna_tcm_suspend(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready = 0; + } else if (*transition == FB_BLANK_UNBLANK) { +#ifndef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + } else if (action == FB_EARLY_EVENT_BLANK && + *transition == FB_BLANK_UNBLANK) { +#ifdef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + + return 0; +} +#endif + +static int synaptics_tcm_pinctrl_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + + /* Get pinctrl if target uses pinctrl */ + tcm_hcd->ts_pinctrl = devm_pinctrl_get((tcm_hcd->pdev->dev.parent)); + if (IS_ERR_OR_NULL(tcm_hcd->ts_pinctrl)) { + retval = PTR_ERR(tcm_hcd->ts_pinctrl); + LOGE(tcm_hcd->pdev->dev.parent, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + tcm_hcd->pinctrl_state_active + = pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_active)) { + retval = PTR_ERR(tcm_hcd->pinctrl_state_active); + LOGE(tcm_hcd->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + tcm_hcd->pinctrl_state_suspend + = pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_suspend)) { + retval = PTR_ERR(tcm_hcd->pinctrl_state_suspend); + LOGE(tcm_hcd->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + tcm_hcd->pinctrl_state_release + = pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_release"); + if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_release)) { + retval = PTR_ERR(tcm_hcd->pinctrl_state_release); + LOGE(tcm_hcd->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return retval; + +err_pinctrl_lookup: + devm_pinctrl_put(tcm_hcd->ts_pinctrl); +err_pinctrl_get: + tcm_hcd->ts_pinctrl = NULL; + return retval; +} + +static int syna_tcm_probe(struct platform_device *pdev) +{ + int retval; + int idx; + struct syna_tcm_hcd *tcm_hcd; + const struct syna_tcm_board_data *bdata; + const struct syna_tcm_hw_interface *hw_if; + struct drm_panel *active_panel = tcm_get_panel(); + + hw_if = pdev->dev.platform_data; + if (!hw_if) { + LOGE(&pdev->dev, + "Hardware interface not found\n"); + return -ENODEV; + } + + bdata = hw_if->bdata; + if (!bdata) { + LOGE(&pdev->dev, + "Board data not found\n"); + return -ENODEV; + } + + tcm_hcd = kzalloc(sizeof(*tcm_hcd), GFP_KERNEL); + if (!tcm_hcd) { + LOGE(&pdev->dev, + "Failed to allocate memory for tcm_hcd\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tcm_hcd); + + tcm_hcd->pdev = pdev; + tcm_hcd->hw_if = hw_if; + tcm_hcd->reset = syna_tcm_reset; + tcm_hcd->sleep = syna_tcm_sleep; + tcm_hcd->identify = syna_tcm_identify; + tcm_hcd->enable_irq = syna_tcm_enable_irq; + tcm_hcd->switch_mode = syna_tcm_switch_mode; + tcm_hcd->read_message = syna_tcm_read_message; + tcm_hcd->write_message = syna_tcm_write_message; + tcm_hcd->get_dynamic_config = syna_tcm_get_dynamic_config; + tcm_hcd->set_dynamic_config = syna_tcm_set_dynamic_config; + tcm_hcd->get_data_location = syna_tcm_get_data_location; + + tcm_hcd->rd_chunk_size = RD_CHUNK_SIZE; + tcm_hcd->wr_chunk_size = WR_CHUNK_SIZE; + +#ifdef PREDICTIVE_READING + tcm_hcd->read_length = MIN_READ_LENGTH; +#else + tcm_hcd->read_length = MESSAGE_HEADER_SIZE; +#endif + + tcm_hcd->watchdog.run = RUN_WATCHDOG; + tcm_hcd->update_watchdog = syna_tcm_update_watchdog; + + if (bdata->irq_gpio >= 0) + tcm_hcd->irq = gpio_to_irq(bdata->irq_gpio); + else + tcm_hcd->irq = bdata->irq_gpio; + + mutex_init(&tcm_hcd->extif_mutex); + mutex_init(&tcm_hcd->reset_mutex); + mutex_init(&tcm_hcd->irq_en_mutex); + mutex_init(&tcm_hcd->io_ctrl_mutex); + mutex_init(&tcm_hcd->rw_ctrl_mutex); + mutex_init(&tcm_hcd->command_mutex); + mutex_init(&tcm_hcd->identify_mutex); + + INIT_BUFFER(tcm_hcd->in, false); + INIT_BUFFER(tcm_hcd->out, false); + INIT_BUFFER(tcm_hcd->resp, true); + INIT_BUFFER(tcm_hcd->temp, false); + INIT_BUFFER(tcm_hcd->config, false); + INIT_BUFFER(tcm_hcd->report.buffer, true); + + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->in, + tcm_hcd->read_length + 1); + if (retval < 0) { + LOGE(&pdev->dev, + "Failed to allocate memory for tcm_hcd->in.buf\n"); + UNLOCK_BUFFER(tcm_hcd->in); + goto err_alloc_mem; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + + atomic_set(&tcm_hcd->helper.task, HELP_NONE); + + device_init_wakeup(&pdev->dev, 1); + + init_waitqueue_head(&tcm_hcd->hdl_wq); + + init_waitqueue_head(&tcm_hcd->reflash_wq); + atomic_set(&tcm_hcd->firmware_flashing, 0); + + if (!mod_pool.initialized) { + mutex_init(&mod_pool.mutex); + INIT_LIST_HEAD(&mod_pool.list); + mod_pool.initialized = true; + } + + retval = syna_tcm_get_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get regulators\n"); + goto err_get_regulator; + } + + retval = syna_tcm_enable_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable regulators\n"); + goto err_enable_regulator; + } + + retval = syna_tcm_config_gpio(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure GPIO's\n"); + goto err_config_gpio; + } + + retval = synaptics_tcm_pinctrl_init(tcm_hcd); + if (!retval && tcm_hcd->ts_pinctrl) { + retval = pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_active); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + } + + sysfs_dir = kobject_create_and_add(PLATFORM_DRIVER_NAME, + &pdev->dev.kobj); + if (!sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + tcm_hcd->sysfs_dir = sysfs_dir; + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(tcm_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + tcm_hcd->dynamnic_config_sysfs_dir = + kobject_create_and_add(DYNAMIC_CONFIG_SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!tcm_hcd->dynamnic_config_sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create dynamic config sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dynamic_config_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) { + retval = sysfs_create_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create dynamic config sysfs file\n"); + goto err_sysfs_create_dynamic_config_file; + } + } + +#ifdef CONFIG_DRM + tcm_hcd->fb_notifier.notifier_call = syna_tcm_fb_notifier_cb; + if (active_panel) { + retval = drm_panel_notifier_register(active_panel, + &tcm_hcd->fb_notifier); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to register fb notifier client\n", + __func__); + goto err_drm_reg; + } + } + +#elif CONFIG_FB + tcm_hcd->fb_notifier.notifier_call = syna_tcm_fb_notifier_cb; + retval = fb_register_client(&tcm_hcd->fb_notifier); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register FB notifier client\n"); + } +#endif + + tcm_hcd->notifier_thread = kthread_run(syna_tcm_report_notifier, + tcm_hcd, "syna_tcm_report_notifier"); + if (IS_ERR(tcm_hcd->notifier_thread)) { + retval = PTR_ERR(tcm_hcd->notifier_thread); + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create and run tcm_hcd->notifier_thread\n"); + goto err_create_run_kthread; + } + + tcm_hcd->helper.workqueue = + create_singlethread_workqueue("syna_tcm_helper"); + INIT_WORK(&tcm_hcd->helper.work, syna_tcm_helper_work); + + tcm_hcd->watchdog.workqueue = + create_singlethread_workqueue("syna_tcm_watchdog"); + INIT_DELAYED_WORK(&tcm_hcd->watchdog.work, syna_tcm_watchdog_work); + + tcm_hcd->polling_workqueue = + create_singlethread_workqueue("syna_tcm_polling"); + INIT_DELAYED_WORK(&tcm_hcd->polling_work, syna_tcm_polling_work); + + mod_pool.workqueue = + create_singlethread_workqueue("syna_tcm_module"); + INIT_WORK(&mod_pool.work, syna_tcm_module_work); + mod_pool.tcm_hcd = tcm_hcd; + mod_pool.queue_work = true; + mod_pool.reconstructing = false; + + return 0; + + +err_create_run_kthread: +#ifdef CONFIG_DRM + if (active_panel) + drm_panel_notifier_unregister(active_panel, + &tcm_hcd->fb_notifier); +#elif CONFIG_FB + fb_unregister_client(&tcm_hcd->fb_notifier); +#endif + +err_sysfs_create_dynamic_config_file: + for (idx--; idx >= 0; idx--) { + sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + } + + kobject_put(tcm_hcd->dynamnic_config_sysfs_dir); + + idx = ARRAY_SIZE(attrs); + +err_sysfs_create_dynamic_config_dir: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(tcm_hcd->sysfs_dir); + +err_sysfs_create_dir: + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0); + +err_config_gpio: + syna_tcm_enable_regulator(tcm_hcd, false); + +err_enable_regulator: + syna_tcm_get_regulator(tcm_hcd, false); + +err_get_regulator: + device_init_wakeup(&pdev->dev, 0); + +err_alloc_mem: + RELEASE_BUFFER(tcm_hcd->report.buffer); + RELEASE_BUFFER(tcm_hcd->config); + RELEASE_BUFFER(tcm_hcd->temp); + RELEASE_BUFFER(tcm_hcd->resp); + RELEASE_BUFFER(tcm_hcd->out); + RELEASE_BUFFER(tcm_hcd->in); + +err_drm_reg: + kfree(tcm_hcd); + + return retval; +} + +static int syna_tcm_deferred_probe(struct device *dev) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto err_enable_irq; + } + retval = tcm_hcd->reset(tcm_hcd, false, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + tcm_hcd->init_okay = false; + tcm_hcd->watchdog.run = false; + tcm_hcd->update_watchdog(tcm_hcd, false); + tcm_hcd->enable_irq(tcm_hcd, false, false); +#ifndef KEEP_DRIVER_ON_ERROR + goto err_reset; +#endif + } else { + tcm_hcd->init_okay = true; + tcm_hcd->update_watchdog(tcm_hcd, true); + } + + queue_work(mod_pool.workqueue, &mod_pool.work); + + return 0; +#ifndef KEEP_DRIVER_ON_ERROR +err_reset: +#endif +err_enable_irq: + + return retval; +} + + +static int syna_tcm_remove(struct platform_device *pdev) +{ + int idx; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_module_handler *tmp_handler; + struct syna_tcm_hcd *tcm_hcd = platform_get_drvdata(pdev); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + struct drm_panel *active_panel = tcm_get_panel(); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry_safe(mod_handler, tmp_handler, + &mod_pool.list, link) { + if (mod_handler->mod_cb->remove) + mod_handler->mod_cb->remove(tcm_hcd); + list_del(&mod_handler->link); + kfree(mod_handler); + } + } + + mod_pool.queue_work = false; + cancel_work_sync(&mod_pool.work); + flush_workqueue(mod_pool.workqueue); + destroy_workqueue(mod_pool.workqueue); + + mutex_unlock(&mod_pool.mutex); + + if (tcm_hcd->irq_enabled && bdata->irq_gpio >= 0) { + disable_irq(tcm_hcd->irq); + free_irq(tcm_hcd->irq, tcm_hcd); + } + + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + destroy_workqueue(tcm_hcd->polling_workqueue); + + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + destroy_workqueue(tcm_hcd->watchdog.workqueue); + + cancel_work_sync(&tcm_hcd->helper.work); + flush_workqueue(tcm_hcd->helper.workqueue); + destroy_workqueue(tcm_hcd->helper.workqueue); + + kthread_stop(tcm_hcd->notifier_thread); + +#ifdef CONFIG_DRM + if (active_panel) + drm_panel_notifier_unregister(active_panel, + &tcm_hcd->fb_notifier); +#elif CONFIG_FB + fb_unregister_client(&tcm_hcd->fb_notifier); +#endif + + for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) { + sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + } + + kobject_put(tcm_hcd->dynamnic_config_sysfs_dir); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(tcm_hcd->sysfs_dir); + + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0); + + syna_tcm_enable_regulator(tcm_hcd, false); + + syna_tcm_get_regulator(tcm_hcd, false); + + device_init_wakeup(&pdev->dev, 0); + + RELEASE_BUFFER(tcm_hcd->report.buffer); + RELEASE_BUFFER(tcm_hcd->config); + RELEASE_BUFFER(tcm_hcd->temp); + RELEASE_BUFFER(tcm_hcd->resp); + RELEASE_BUFFER(tcm_hcd->out); + RELEASE_BUFFER(tcm_hcd->in); + + kfree(tcm_hcd); + + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops syna_tcm_dev_pm_ops = { +#if !defined(CONFIG_DRM) && !defined(CONFIG_FB) + .suspend = syna_tcm_suspend, + .resume = syna_tcm_resume, +#endif +}; +#endif + +static struct platform_driver syna_tcm_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &syna_tcm_dev_pm_ops, +#endif + }, + .probe = syna_tcm_probe, + .remove = syna_tcm_remove, +}; + +static int __init syna_tcm_module_init(void) +{ + int retval; + + retval = syna_tcm_bus_init(); + if (retval < 0) + return retval; + + return platform_driver_register(&syna_tcm_driver); +} + +static void __exit syna_tcm_module_exit(void) +{ + platform_driver_unregister(&syna_tcm_driver); + + syna_tcm_bus_exit(); +} + +late_initcall(syna_tcm_module_init); +module_exit(syna_tcm_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_core.h b/synaptics_tcm/synaptics_tcm_core.h new file mode 100644 index 0000000000..62f36bdd55 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_core.h @@ -0,0 +1,684 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_CORE_H_ +#define _SYNAPTICS_TCM_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DRM +#include +#elif CONFIG_FB +#include +#include +#endif +#include + +#define SYNAPTICS_TCM_ID_PRODUCT (1 << 0) +#define SYNAPTICS_TCM_ID_VERSION 0x0101 +#define SYNAPTICS_TCM_ID_SUBVERSION 0 + +#define PLATFORM_DRIVER_NAME "synaptics_tcm" + +#define TOUCH_INPUT_NAME "synaptics_tcm_touch" +#define TOUCH_INPUT_PHYS_PATH "synaptics_tcm/touch_input" + +/* #define WAKEUP_GESTURE */ + +#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */ +#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */ + +#define MESSAGE_HEADER_SIZE 4 +#define MESSAGE_MARKER 0xa5 +#define MESSAGE_PADDING 0x5a + +#define LOGx(func, dev, log, ...) \ + func(dev, "%s: " log, __func__, ##__VA_ARGS__) + +#define LOGy(func, dev, log, ...) \ + func(dev, "%s (line %d): " log, __func__, __LINE__, ##__VA_ARGS__) + +#define LOGD(dev, log, ...) LOGx(dev_dbg, dev, log, ##__VA_ARGS__) +#define LOGI(dev, log, ...) LOGx(dev_info, dev, log, ##__VA_ARGS__) +#define LOGN(dev, log, ...) LOGx(dev_notice, dev, log, ##__VA_ARGS__) +#define LOGW(dev, log, ...) LOGy(dev_warn, dev, log, ##__VA_ARGS__) +#define LOGE(dev, log, ...) LOGy(dev_err, dev, log, ##__VA_ARGS__) + +#define INIT_BUFFER(buffer, is_clone) \ + mutex_init(&buffer.buf_mutex); \ + buffer.clone = is_clone + +#define LOCK_BUFFER(buffer) \ + mutex_lock(&buffer.buf_mutex) + +#define UNLOCK_BUFFER(buffer) \ + mutex_unlock(&buffer.buf_mutex) + +#define RELEASE_BUFFER(buffer) \ + do { \ + if (buffer.clone == false) { \ + kfree(buffer.buf); \ + buffer.buf_size = 0; \ + buffer.data_length = 0; \ + } \ + } while (0) + +#define MAX(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define MIN(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define STR(x) #x + +#define CONCAT(a, b) a##b + +#define SHOW_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \ + struct device_attribute *attr, char *buf); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, 0444, \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + syna_tcm_store_error) + +#define STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, 0220, \ + syna_tcm_show_error, \ + CONCAT(m_name##_sysfs, _##a_name##_store)) + +#define SHOW_STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \ + struct device_attribute *attr, char *buf); \ +\ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, 0664, \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + CONCAT(m_name##_sysfs, _##a_name##_store)) + +#define ATTRIFY(a_name) (&dev_attr_##a_name) + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +enum module_type { + TCM_TOUCH = 0, + TCM_DEVICE = 1, + TCM_TESTING = 2, + TCM_REFLASH = 3, + TCM_RECOVERY = 4, + TCM_ZEROFLASH = 5, + TCM_DIAGNOSTICS = 6, + TCM_LAST, +}; + +enum boot_mode { + MODE_APPLICATION = 0x01, + MODE_HOST_DOWNLOAD = 0x02, + MODE_BOOTLOADER = 0x0b, + MODE_TDDI_BOOTLOADER = 0x0c, + MODE_PRODUCTION_TEST = 0x0e, +}; + +enum boot_status { + BOOT_STATUS_OK = 0x00, + BOOT_STATUS_BOOTING = 0x01, + BOOT_STATUS_APP_BAD_DISPLAY_CRC = 0xfc, + BOOT_STATUS_BAD_DISPLAY_CONFIG = 0xfd, + BOOT_STATUS_BAD_APP_FIRMWARE = 0xfe, + BOOT_STATUS_WARM_BOOT = 0xff, +}; + +enum app_status { + APP_STATUS_OK = 0x00, + APP_STATUS_BOOTING = 0x01, + APP_STATUS_UPDATING = 0x02, + APP_STATUS_BAD_APP_CONFIG = 0xff, +}; + +enum firmware_mode { + FW_MODE_BOOTLOADER = 0, + FW_MODE_APPLICATION = 1, + FW_MODE_PRODUCTION_TEST = 2, +}; + +enum dynamic_config_id { + DC_UNKNOWN = 0x00, + DC_NO_DOZE, + DC_DISABLE_NOISE_MITIGATION, + DC_INHIBIT_FREQUENCY_SHIFT, + DC_REQUESTED_FREQUENCY, + DC_DISABLE_HSYNC, + DC_REZERO_ON_EXIT_DEEP_SLEEP, + DC_CHARGER_CONNECTED, + DC_NO_BASELINE_RELAXATION, + DC_IN_WAKEUP_GESTURE_MODE, + DC_STIMULUS_FINGERS, + DC_GRIP_SUPPRESSION_ENABLED, + DC_ENABLE_THICK_GLOVE, + DC_ENABLE_GLOVE, +}; + +enum command { + CMD_NONE = 0x00, + CMD_CONTINUE_WRITE = 0x01, + CMD_IDENTIFY = 0x02, + CMD_RESET = 0x04, + CMD_ENABLE_REPORT = 0x05, + CMD_DISABLE_REPORT = 0x06, + CMD_GET_BOOT_INFO = 0x10, + CMD_ERASE_FLASH = 0x11, + CMD_WRITE_FLASH = 0x12, + CMD_READ_FLASH = 0x13, + CMD_RUN_APPLICATION_FIRMWARE = 0x14, + CMD_SPI_MASTER_WRITE_THEN_READ = 0x15, + CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16, + CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f, + CMD_GET_APPLICATION_INFO = 0x20, + CMD_GET_STATIC_CONFIG = 0x21, + CMD_SET_STATIC_CONFIG = 0x22, + CMD_GET_DYNAMIC_CONFIG = 0x23, + CMD_SET_DYNAMIC_CONFIG = 0x24, + CMD_GET_TOUCH_REPORT_CONFIG = 0x25, + CMD_SET_TOUCH_REPORT_CONFIG = 0x26, + CMD_REZERO = 0x27, + CMD_COMMIT_CONFIG = 0x28, + CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29, + CMD_PRODUCTION_TEST = 0x2a, + CMD_SET_CONFIG_ID = 0x2b, + CMD_ENTER_DEEP_SLEEP = 0x2c, + CMD_EXIT_DEEP_SLEEP = 0x2d, + CMD_GET_TOUCH_INFO = 0x2e, + CMD_GET_DATA_LOCATION = 0x2f, + CMD_DOWNLOAD_CONFIG = 0x30, + CMD_ENTER_PRODUCTION_TEST_MODE = 0x31, + CMD_GET_FEATURES = 0x32, +}; + +enum status_code { + STATUS_IDLE = 0x00, + STATUS_OK = 0x01, + STATUS_BUSY = 0x02, + STATUS_CONTINUED_READ = 0x03, + STATUS_NOT_EXECUTED_IN_DEEP_SLEEP = 0x0b, + STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c, + STATUS_PREVIOUS_COMMAND_PENDING = 0x0d, + STATUS_NOT_IMPLEMENTED = 0x0e, + STATUS_ERROR = 0x0f, + STATUS_INVALID = 0xff, +}; + +enum report_type { + REPORT_IDENTIFY = 0x10, + REPORT_TOUCH = 0x11, + REPORT_DELTA = 0x12, + REPORT_RAW = 0x13, + REPORT_STATUS = 0x1b, + REPORT_PRINTF = 0x82, + REPORT_HDL = 0xfe, +}; + +enum command_status { + CMD_IDLE = 0, + CMD_BUSY = 1, + CMD_ERROR = -1, +}; + +enum flash_area { + BOOTLOADER = 0, + BOOT_CONFIG, + APP_FIRMWARE, + APP_CONFIG, + DISP_CONFIG, + CUSTOM_OTP, + CUSTOM_LCM, + CUSTOM_OEM, + PPDT, +}; + +enum flash_data { + LCM_DATA = 1, + OEM_DATA, + PPDT_DATA, +}; + +enum helper_task { + HELP_NONE = 0, + HELP_RUN_APPLICATION_FIRMWARE, + HELP_SEND_RESET_NOTIFICATION, +}; + +struct syna_tcm_helper { + atomic_t task; + struct work_struct work; + struct workqueue_struct *workqueue; +}; + +struct syna_tcm_watchdog { + bool run; + unsigned char count; + struct delayed_work work; + struct workqueue_struct *workqueue; +}; + +struct syna_tcm_buffer { + bool clone; + unsigned char *buf; + unsigned int buf_size; + unsigned int data_length; + struct mutex buf_mutex; +}; + +struct syna_tcm_report { + unsigned char id; + struct syna_tcm_buffer buffer; +}; + +struct syna_tcm_identification { + unsigned char version; + unsigned char mode; + unsigned char part_number[16]; + unsigned char build_id[4]; + unsigned char max_write_size[2]; +}; + +struct syna_tcm_boot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char erase_page_size_words[2]; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; + unsigned char boot_config_start_block[2]; + unsigned char boot_config_size_blocks[2]; + unsigned char display_config_start_block[4]; + unsigned char display_config_length_blocks[2]; + unsigned char backup_display_config_start_block[4]; + unsigned char backup_display_config_length_blocks[2]; + unsigned char custom_otp_start_block[2]; + unsigned char custom_otp_length_blocks[2]; +}; + +struct syna_tcm_app_info { + unsigned char version[2]; + unsigned char status[2]; + unsigned char static_config_size[2]; + unsigned char dynamic_config_size[2]; + unsigned char app_config_start_write_block[2]; + unsigned char app_config_size[2]; + unsigned char max_touch_report_config_size[2]; + unsigned char max_touch_report_payload_size[2]; + unsigned char customer_config_id[16]; + unsigned char max_x[2]; + unsigned char max_y[2]; + unsigned char max_objects[2]; + unsigned char num_of_buttons[2]; + unsigned char num_of_image_rows[2]; + unsigned char num_of_image_cols[2]; + unsigned char has_hybrid_data[2]; +}; + +struct syna_tcm_touch_info { + unsigned char image_2d_scale_factor[4]; + unsigned char image_0d_scale_factor[4]; + unsigned char hybrid_x_scale_factor[4]; + unsigned char hybrid_y_scale_factor[4]; +}; + +struct syna_tcm_message_header { + unsigned char marker; + unsigned char code; + unsigned char length[2]; +}; + +struct syna_tcm_features { + unsigned char byte_0_reserved; + unsigned char byte_1_reserved; + unsigned char dual_firmware:1; + unsigned char byte_2_reserved:7; +} __packed; + +struct syna_tcm_hcd { + pid_t isr_pid; + atomic_t command_status; + atomic_t host_downloading; + atomic_t firmware_flashing; + wait_queue_head_t hdl_wq; + wait_queue_head_t reflash_wq; + int irq; + bool init_okay; + bool do_polling; + bool in_suspend; + bool irq_enabled; + bool host_download_mode; + unsigned char marker; + unsigned char fb_ready; + unsigned char command; + unsigned char async_report_id; + unsigned char status_report_code; + unsigned char response_code; + unsigned int read_length; + unsigned int payload_length; + unsigned int packrat_number; + unsigned int rd_chunk_size; + unsigned int wr_chunk_size; + unsigned int app_status; + struct platform_device *pdev; + struct regulator *pwr_reg; + struct regulator *bus_reg; + struct kobject *sysfs_dir; + struct kobject *dynamnic_config_sysfs_dir; + struct mutex extif_mutex; + struct mutex reset_mutex; + struct mutex irq_en_mutex; + struct mutex io_ctrl_mutex; + struct mutex rw_ctrl_mutex; + struct mutex command_mutex; + struct mutex identify_mutex; + struct delayed_work polling_work; + struct workqueue_struct *polling_workqueue; + struct task_struct *notifier_thread; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; +#if defined(CONFIG_DRM) || defined(CONFIG_FB) + struct notifier_block fb_notifier; +#endif + struct syna_tcm_buffer in; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer temp; + struct syna_tcm_buffer config; + struct syna_tcm_report report; + struct syna_tcm_app_info app_info; + struct syna_tcm_boot_info boot_info; + struct syna_tcm_touch_info touch_info; + struct syna_tcm_identification id_info; + struct syna_tcm_helper helper; + struct syna_tcm_watchdog watchdog; + struct syna_tcm_features features; + const struct syna_tcm_hw_interface *hw_if; + int (*reset)(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd); + int (*sleep)(struct syna_tcm_hcd *tcm_hcd, bool en); + int (*identify)(struct syna_tcm_hcd *tcm_hcd, bool id); + int (*enable_irq)(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns); + int (*switch_mode)(struct syna_tcm_hcd *tcm_hcd, + enum firmware_mode mode); + int (*read_message)(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length); + int (*write_message)(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *payload, + unsigned int length, unsigned char **resp_buf, + unsigned int *resp_buf_size, unsigned int *resp_length, + unsigned char *response_code, + unsigned int polling_delay_ms); + int (*get_dynamic_config)(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short *value); + int (*set_dynamic_config)(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short value); + int (*get_data_location)(struct syna_tcm_hcd *tcm_hcd, + enum flash_area area, unsigned int *addr, + unsigned int *length); + int (*read_flash_data)(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output); + void (*report_touch)(void); + void (*update_watchdog)(struct syna_tcm_hcd *tcm_hcd, bool en); +}; + +struct syna_tcm_module_cb { + enum module_type type; + int (*init)(struct syna_tcm_hcd *tcm_hcd); + int (*remove)(struct syna_tcm_hcd *tcm_hcd); + int (*syncbox)(struct syna_tcm_hcd *tcm_hcd); + int (*asyncbox)(struct syna_tcm_hcd *tcm_hcd); + int (*reset)(struct syna_tcm_hcd *tcm_hcd); + int (*suspend)(struct syna_tcm_hcd *tcm_hcd); + int (*resume)(struct syna_tcm_hcd *tcm_hcd); + int (*early_suspend)(struct syna_tcm_hcd *tcm_hcd); +}; + +struct syna_tcm_module_handler { + bool insert; + bool detach; + struct list_head link; + struct syna_tcm_module_cb *mod_cb; +}; + +struct syna_tcm_module_pool { + bool initialized; + bool queue_work; + bool reconstructing; + struct mutex mutex; + struct list_head list; + struct work_struct work; + struct workqueue_struct *workqueue; + struct syna_tcm_hcd *tcm_hcd; +}; + +struct syna_tcm_bus_io { + unsigned char type; + int (*rmi_read)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr, + unsigned char *data, unsigned int length); + int (*rmi_write)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr, + unsigned char *data, unsigned int length); + int (*read)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length); + int (*write)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length); +}; + +struct syna_tcm_hw_interface { + struct syna_tcm_board_data *bdata; + const struct syna_tcm_bus_io *bus_io; +}; + +struct drm_panel *tcm_get_panel(void); + +int syna_tcm_bus_init(void); + +void syna_tcm_bus_exit(void); + +int syna_tcm_bus_init_spi(void); + +void syna_tcm_bus_exit_spi(void); + +int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert); + +static inline int syna_tcm_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->rmi_read(tcm_hcd, addr, data, length); +} + +static inline int syna_tcm_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->rmi_write(tcm_hcd, addr, data, length); +} + +static inline int syna_tcm_read(struct syna_tcm_hcd *tcm_hcd, + unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->read(tcm_hcd, data, length); +} + +static inline int syna_tcm_write(struct syna_tcm_hcd *tcm_hcd, + unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->write(tcm_hcd, data, length); +} + +static inline ssize_t syna_tcm_show_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_err("%s: Attribute not readable\n", + __func__); + + return -EPERM; +} + +static inline ssize_t syna_tcm_store_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + pr_err("%s: Attribute not writable\n", + __func__); + + return -EPERM; +} + +static inline int secure_memcpy(unsigned char *dest, unsigned int dest_size, + const unsigned char *src, unsigned int src_size, + unsigned int count) +{ + if (dest == NULL || src == NULL) + return -EINVAL; + + if (count > dest_size || count > src_size) { + pr_err("%s: src_size = %d, dest_size = %d, count = %d\n", + __func__, src_size, dest_size, count); + return -EINVAL; + } + + memcpy((void *)dest, (const void *)src, count); + + return 0; +} + +static inline int syna_tcm_realloc_mem(struct syna_tcm_hcd *tcm_hcd, + struct syna_tcm_buffer *buffer, unsigned int size) +{ + int retval; + unsigned char *temp; + + if (size > buffer->buf_size) { + temp = buffer->buf; + + buffer->buf = kmalloc(size, GFP_KERNEL); + if (!(buffer->buf)) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to allocate memory\n", + __func__); + kfree(temp); + buffer->buf_size = 0; + return -ENOMEM; + } + + retval = secure_memcpy(buffer->buf, + size, + temp, + buffer->buf_size, + buffer->buf_size); + if (retval < 0) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + kfree(temp); + kfree(buffer->buf); + buffer->buf_size = 0; + return retval; + } + + kfree(temp); + buffer->buf_size = size; + } + + return 0; +} + +static inline int syna_tcm_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + struct syna_tcm_buffer *buffer, unsigned int size) +{ + if (size > buffer->buf_size) { + kfree(buffer->buf); + buffer->buf = kmalloc(size, GFP_KERNEL); + if (!(buffer->buf)) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to allocate memory\n", + __func__); + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Allocation size = %d\n", + __func__, size); + buffer->buf_size = 0; + buffer->data_length = 0; + return -ENOMEM; + } + buffer->buf_size = size; + } + + memset(buffer->buf, 0x00, buffer->buf_size); + buffer->data_length = 0; + + return 0; +} + +static inline unsigned int le2_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100; +} + +static inline unsigned int le4_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100 + + (unsigned int)src[2] * 0x10000 + + (unsigned int)src[3] * 0x1000000; +} + +static inline unsigned int ceil_div(unsigned int dividend, + unsigned int divisor) +{ + return (dividend + divisor - 1) / divisor; +} + +#endif diff --git a/synaptics_tcm/synaptics_tcm_device.c b/synaptics_tcm/synaptics_tcm_device.c new file mode 100644 index 0000000000..963e2994bd --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_device.c @@ -0,0 +1,707 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include +#include "synaptics_tcm_core.h" + +#define CHAR_DEVICE_NAME "tcm" + +#define CONCURRENT true + +#define DEVICE_IOC_MAGIC 's' +#define DEVICE_IOC_RESET _IO(DEVICE_IOC_MAGIC, 0) /* 0x00007300 */ +#define DEVICE_IOC_IRQ _IOW(DEVICE_IOC_MAGIC, 1, int) /* 0x40047301 */ +#define DEVICE_IOC_RAW _IOW(DEVICE_IOC_MAGIC, 2, int) /* 0x40047302 */ +#define DEVICE_IOC_CONCURRENT _IOW(DEVICE_IOC_MAGIC, 3, int) /* 0x40047303 */ + +struct device_hcd { + dev_t dev_num; + bool raw_mode; + bool concurrent; + unsigned int ref_count; + struct cdev char_dev; + struct class *class; + struct device *device; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer report; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(device_remove_complete); + +static struct device_hcd *device_hcd; + +static int rmidev_major_num; + +static void device_capture_touch_report(unsigned int count) +{ + int retval; + unsigned char id; + unsigned int idx; + unsigned int size; + unsigned char *data; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + static bool report; + static unsigned int offset; + static unsigned int remaining_size; + + if (count < 2) + return; + + data = &device_hcd->resp.buf[0]; + + if (data[0] != MESSAGE_MARKER) + return; + + id = data[1]; + + size = 0; + + LOCK_BUFFER(device_hcd->report); + + switch (id) { + case REPORT_TOUCH: + if (count >= 4) { + remaining_size = le2_to_uint(&data[2]); + } else { + report = false; + goto exit; + } + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->report, + remaining_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for report.buf\n"); + report = false; + goto exit; + } + idx = 4; + size = count - idx; + offset = 0; + report = true; + break; + case STATUS_CONTINUED_READ: + if (report == false) + goto exit; + if (count >= 2) { + idx = 2; + size = count - idx; + } + break; + default: + goto exit; + } + + if (size) { + size = MIN(size, remaining_size); + retval = secure_memcpy(&device_hcd->report.buf[offset], + device_hcd->report.buf_size - offset, + &data[idx], + count - idx, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy touch report data\n"); + report = false; + goto exit; + } else { + offset += size; + remaining_size -= size; + device_hcd->report.data_length += size; + } + } + + if (remaining_size) + goto exit; + + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = device_hcd->report.buf; + tcm_hcd->report.buffer.buf_size = device_hcd->report.buf_size; + tcm_hcd->report.buffer.data_length = device_hcd->report.data_length; + + tcm_hcd->report_touch(); + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + + report = false; + +exit: + UNLOCK_BUFFER(device_hcd->report); +} + +static int device_capture_touch_report_config(unsigned int count) +{ + int retval; + unsigned int size; + unsigned int buf_size; + unsigned char *data; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (device_hcd->raw_mode) { + if (count < 3) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid write data\n"); + return -EINVAL; + } + + size = le2_to_uint(&device_hcd->out.buf[1]); + + if (count - 3 < size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incomplete write data\n"); + return -EINVAL; + } + + if (!size) + return 0; + + data = &device_hcd->out.buf[3]; + buf_size = device_hcd->out.buf_size - 3; + } else { + size = count - 1; + + if (!size) + return 0; + + data = &device_hcd->out.buf[1]; + buf_size = device_hcd->out.buf_size - 1; + } + + LOCK_BUFFER(tcm_hcd->config); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->config, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->config.buf\n"); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + retval = secure_memcpy(tcm_hcd->config.buf, + tcm_hcd->config.buf_size, + data, + buf_size, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy touch report config data\n"); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + tcm_hcd->config.data_length = size; + + UNLOCK_BUFFER(tcm_hcd->config); + + return 0; +} + +#ifdef HAVE_UNLOCKED_IOCTL +static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +#else +static int device_ioctl(struct inode *inp, struct file *filp, unsigned int cmd, + unsigned long arg) +#endif +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = 0; + + switch (cmd) { + case DEVICE_IOC_RESET: + retval = tcm_hcd->reset(tcm_hcd, false, true); + break; + case DEVICE_IOC_IRQ: + if (arg == 0) + retval = tcm_hcd->enable_irq(tcm_hcd, false, false); + else if (arg == 1) + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + break; + case DEVICE_IOC_RAW: + if (arg == 0) { + device_hcd->raw_mode = false; + tcm_hcd->update_watchdog(tcm_hcd, true); + } else if (arg == 1) { + device_hcd->raw_mode = true; + tcm_hcd->update_watchdog(tcm_hcd, false); + } + break; + case DEVICE_IOC_CONCURRENT: + if (arg == 0) + device_hcd->concurrent = false; + else if (arg == 1) + device_hcd->concurrent = true; + break; + default: + retval = -ENOTTY; + break; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static loff_t device_llseek(struct file *filp, loff_t off, int whence) +{ + return -EINVAL; +} + +static ssize_t device_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (count == 0) + return 0; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(device_hcd->resp); + + if (device_hcd->raw_mode) { + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->resp, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for resp.buf\n"); + UNLOCK_BUFFER(device_hcd->resp); + goto exit; + } + + retval = tcm_hcd->read_message(tcm_hcd, + device_hcd->resp.buf, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + UNLOCK_BUFFER(device_hcd->resp); + goto exit; + } + } else { + if (count != device_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid length information\n"); + UNLOCK_BUFFER(device_hcd->resp); + retval = -EINVAL; + goto exit; + } + } + + if (copy_to_user(buf, device_hcd->resp.buf, count)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data to user space\n"); + UNLOCK_BUFFER(device_hcd->resp); + retval = -EINVAL; + goto exit; + } + + if (!device_hcd->concurrent) + goto skip_concurrent; + + if (tcm_hcd->report_touch == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Unable to report touch\n"); + device_hcd->concurrent = false; + } + + if (device_hcd->raw_mode) + device_capture_touch_report(count); + +skip_concurrent: + UNLOCK_BUFFER(device_hcd->resp); + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t device_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (count == 0) + return 0; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(device_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->out, + count == 1 ? count + 1 : count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd->out.buf\n"); + UNLOCK_BUFFER(device_hcd->out); + goto exit; + } + + if (copy_from_user(device_hcd->out.buf, buf, count)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data from user space\n"); + UNLOCK_BUFFER(device_hcd->out); + retval = -EINVAL; + goto exit; + } + + LOCK_BUFFER(device_hcd->resp); + + if (device_hcd->raw_mode) { + retval = tcm_hcd->write_message(tcm_hcd, + device_hcd->out.buf[0], + &device_hcd->out.buf[1], + count - 1, + NULL, + NULL, + NULL, + NULL, + 0); + } else { + mutex_lock(&tcm_hcd->reset_mutex); + retval = tcm_hcd->write_message(tcm_hcd, + device_hcd->out.buf[0], + &device_hcd->out.buf[1], + count - 1, + &device_hcd->resp.buf, + &device_hcd->resp.buf_size, + &device_hcd->resp.data_length, + NULL, + 0); + mutex_unlock(&tcm_hcd->reset_mutex); + } + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command 0x%02x\n", + device_hcd->out.buf[0]); + UNLOCK_BUFFER(device_hcd->resp); + UNLOCK_BUFFER(device_hcd->out); + goto exit; + } + + if (count && device_hcd->out.buf[0] == CMD_SET_TOUCH_REPORT_CONFIG) { + retval = device_capture_touch_report_config(count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to capture touch report config\n"); + } + } + + UNLOCK_BUFFER(device_hcd->out); + + if (device_hcd->raw_mode) + retval = count; + else + retval = device_hcd->resp.data_length; + + UNLOCK_BUFFER(device_hcd->resp); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int device_open(struct inode *inp, struct file *filp) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (device_hcd->ref_count < 1) { + device_hcd->ref_count++; + retval = 0; + } else { + retval = -EACCES; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int device_release(struct inode *inp, struct file *filp) +{ + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (device_hcd->ref_count) + device_hcd->ref_count--; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return 0; +} + +static char *device_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + /* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; */ + *mode = 0666; + + return kasprintf(GFP_KERNEL, "%s/%s", PLATFORM_DRIVER_NAME, + dev_name(dev)); +} + +static int device_create_class(void) +{ + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (device_hcd->class != NULL) + return 0; + + device_hcd->class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME); + + if (IS_ERR(device_hcd->class)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create class\n"); + return -ENODEV; + } + + device_hcd->class->devnode = device_devnode; + + return 0; +} + +static const struct file_operations device_fops = { + .owner = THIS_MODULE, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = device_ioctl, +#ifdef HAVE_COMPAT_IOCTL + .compat_ioctl = device_ioctl, +#endif +#else + .ioctl = device_ioctl, +#endif + .llseek = device_llseek, + .read = device_read, + .write = device_write, + .open = device_open, + .release = device_release, +}; + +static int device_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + dev_t dev_num; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + device_hcd = kzalloc(sizeof(*device_hcd), GFP_KERNEL); + if (!device_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd\n"); + return -ENOMEM; + } + + device_hcd->tcm_hcd = tcm_hcd; + + device_hcd->concurrent = CONCURRENT; + + INIT_BUFFER(device_hcd->out, false); + INIT_BUFFER(device_hcd->resp, false); + INIT_BUFFER(device_hcd->report, false); + + if (rmidev_major_num) { + dev_num = MKDEV(rmidev_major_num, 0); + retval = register_chrdev_region(dev_num, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register char device\n"); + goto err_register_chrdev_region; + } + } else { + retval = alloc_chrdev_region(&dev_num, 0, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate char device\n"); + goto err_alloc_chrdev_region; + } + + rmidev_major_num = MAJOR(dev_num); + } + + device_hcd->dev_num = dev_num; + + cdev_init(&device_hcd->char_dev, &device_fops); + + retval = cdev_add(&device_hcd->char_dev, dev_num, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add char device\n"); + goto err_add_chardev; + } + + retval = device_create_class(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create class\n"); + goto err_create_class; + } + + device_hcd->device = device_create(device_hcd->class, NULL, + device_hcd->dev_num, NULL, CHAR_DEVICE_NAME"%d", + MINOR(device_hcd->dev_num)); + if (IS_ERR(device_hcd->device)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create device\n"); + retval = -ENODEV; + goto err_create_device; + } + + if (bdata->irq_gpio >= 0) { + retval = gpio_export(bdata->irq_gpio, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to export GPIO\n"); + } else { + retval = gpio_export_link(&tcm_hcd->pdev->dev, + "attn", bdata->irq_gpio); + if (retval < 0) + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to export GPIO link\n"); + } + } + + return 0; + +err_create_device: + class_destroy(device_hcd->class); + +err_create_class: + cdev_del(&device_hcd->char_dev); + +err_add_chardev: + unregister_chrdev_region(dev_num, 1); + +err_alloc_chrdev_region: +err_register_chrdev_region: + RELEASE_BUFFER(device_hcd->report); + RELEASE_BUFFER(device_hcd->resp); + RELEASE_BUFFER(device_hcd->out); + + kfree(device_hcd); + device_hcd = NULL; + + return retval; +} + +static int device_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!device_hcd) + goto exit; + + device_destroy(device_hcd->class, device_hcd->dev_num); + + class_destroy(device_hcd->class); + + cdev_del(&device_hcd->char_dev); + + unregister_chrdev_region(device_hcd->dev_num, 1); + + RELEASE_BUFFER(device_hcd->report); + RELEASE_BUFFER(device_hcd->resp); + RELEASE_BUFFER(device_hcd->out); + + kfree(device_hcd); + device_hcd = NULL; + +exit: + complete(&device_remove_complete); + + return 0; +} + +static int device_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!device_hcd) { + retval = device_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb device_module = { + .type = TCM_DEVICE, + .init = device_init, + .remove = device_remove, + .syncbox = NULL, + .asyncbox = NULL, + .reset = device_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init device_module_init(void) +{ + return syna_tcm_add_module(&device_module, true); +} + +static void __exit device_module_exit(void) +{ + syna_tcm_add_module(&device_module, false); + + wait_for_completion(&device_remove_complete); +} + +module_init(device_module_init); +module_exit(device_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Device Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_diagnostics.c b/synaptics_tcm/synaptics_tcm_diagnostics.c new file mode 100644 index 0000000000..92472e1086 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_diagnostics.c @@ -0,0 +1,561 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include "synaptics_tcm_core.h" + +#define SYSFS_DIR_NAME "diagnostics" + +enum pingpong_state { + PING = 0, + PONG = 1, +}; + +struct diag_hcd { + pid_t pid; + unsigned char report_type; + enum pingpong_state state; + struct kobject *sysfs_dir; + struct siginfo sigio; + struct task_struct *task; + struct syna_tcm_buffer ping; + struct syna_tcm_buffer pong; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(diag_remove_complete); + +static struct diag_hcd *diag_hcd; + +STORE_PROTOTYPE(diag, pid); +SHOW_PROTOTYPE(diag, size); +STORE_PROTOTYPE(diag, type); +SHOW_PROTOTYPE(diag, rows); +SHOW_PROTOTYPE(diag, cols); +SHOW_PROTOTYPE(diag, hybrid); +SHOW_PROTOTYPE(diag, buttons); + +static struct device_attribute *attrs[] = { + ATTRIFY(pid), + ATTRIFY(size), + ATTRIFY(type), + ATTRIFY(rows), + ATTRIFY(cols), + ATTRIFY(hybrid), + ATTRIFY(buttons), +}; + +static ssize_t diag_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "data", + .mode = 0444, + }, + .size = 0, + .read = diag_sysfs_data_show, +}; + +static ssize_t diag_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + diag_hcd->pid = input; + + if (diag_hcd->pid) { + diag_hcd->task = pid_task(find_vpid(diag_hcd->pid), + PIDTYPE_PID); + if (!diag_hcd->task) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to locate task\n"); + retval = -EINVAL; + goto exit; + } + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (diag_hcd->state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + diag_hcd->ping.data_length); + + UNLOCK_BUFFER(diag_hcd->ping); + } else { + LOCK_BUFFER(diag_hcd->pong); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + diag_hcd->pong.data_length); + + UNLOCK_BUFFER(diag_hcd->pong); + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + diag_hcd->report_type = (unsigned char)input; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return count; +} + +static ssize_t diag_sysfs_rows_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int rows; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + rows = le2_to_uint(app_info->num_of_image_rows); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", rows); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_cols_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", cols); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_hybrid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int hybrid; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + hybrid = le2_to_uint(app_info->has_hybrid_data); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", hybrid); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_buttons_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int buttons; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + buttons = le2_to_uint(app_info->num_of_buttons); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", buttons); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = 0; + + if (diag_hcd->state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + if (diag_hcd->ping.data_length == 0) { + readlen = 0; + goto exit; + } + + readlen = MIN(count, diag_hcd->ping.data_length - pos); + + if (diag_hcd->ping.data_length) { + retval = secure_memcpy(buf, + count, + &diag_hcd->ping.buf[pos], + diag_hcd->ping.buf_size - pos, + readlen); + } + + UNLOCK_BUFFER(diag_hcd->ping); + } else { + LOCK_BUFFER(diag_hcd->pong); + + if (diag_hcd->pong.data_length == 0) { + readlen = 0; + goto exit; + } + + readlen = MIN(count, diag_hcd->pong.data_length - pos); + + if (diag_hcd->pong.data_length) { + retval = secure_memcpy(buf, + count, + &diag_hcd->pong.buf[pos], + diag_hcd->pong.buf_size - pos, + readlen); + } + + UNLOCK_BUFFER(diag_hcd->pong); + } + +exit: + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + } else { + retval = readlen; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static void diag_report(void) +{ + int retval; + static enum pingpong_state state = PING; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &diag_hcd->ping, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for ping.buf\n"); + UNLOCK_BUFFER(diag_hcd->ping); + return; + } + + retval = secure_memcpy(diag_hcd->ping.buf, + diag_hcd->ping.buf_size, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(diag_hcd->ping); + return; + } + + diag_hcd->ping.data_length = tcm_hcd->report.buffer.data_length; + + UNLOCK_BUFFER(diag_hcd->ping); + + diag_hcd->state = state; + state = PONG; + } else { + LOCK_BUFFER(diag_hcd->pong); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &diag_hcd->pong, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for pong.buf\n"); + UNLOCK_BUFFER(diag_hcd->pong); + return; + } + + retval = secure_memcpy(diag_hcd->pong.buf, + diag_hcd->pong.buf_size, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(diag_hcd->pong); + return; + } + + diag_hcd->pong.data_length = tcm_hcd->report.buffer.data_length; + + UNLOCK_BUFFER(diag_hcd->pong); + + diag_hcd->state = state; + state = PING; + } +} + +static int diag_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + + diag_hcd = kzalloc(sizeof(*diag_hcd), GFP_KERNEL); + if (!diag_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for diag_hcd\n"); + return -ENOMEM; + } + + diag_hcd->tcm_hcd = tcm_hcd; + diag_hcd->state = PING; + + INIT_BUFFER(diag_hcd->ping, false); + INIT_BUFFER(diag_hcd->pong, false); + + memset(&diag_hcd->sigio, 0x00, sizeof(diag_hcd->sigio)); + diag_hcd->sigio.si_signo = SIGIO; + diag_hcd->sigio.si_code = SI_USER; + + diag_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!diag_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(diag_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(diag_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(diag_hcd->sysfs_dir); + +err_sysfs_create_dir: + RELEASE_BUFFER(diag_hcd->pong); + RELEASE_BUFFER(diag_hcd->ping); + + kfree(diag_hcd); + diag_hcd = NULL; + + return retval; +} + +static int diag_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!diag_hcd) + goto exit; + + sysfs_remove_bin_file(diag_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(diag_hcd->sysfs_dir); + + RELEASE_BUFFER(diag_hcd->pong); + RELEASE_BUFFER(diag_hcd->ping); + + kfree(diag_hcd); + diag_hcd = NULL; + +exit: + complete(&diag_remove_complete); + + return 0; +} + +static int diag_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!diag_hcd) + return 0; + + if (tcm_hcd->report.id == diag_hcd->report_type) + diag_report(); + + return 0; +} + +static int diag_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!diag_hcd) { + retval = diag_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb diag_module = { + .type = TCM_DIAGNOSTICS, + .init = diag_init, + .remove = diag_remove, + .syncbox = diag_syncbox, + .asyncbox = NULL, + .reset = diag_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init diag_module_init(void) +{ + return syna_tcm_add_module(&diag_module, true); +} + +static void __exit diag_module_exit(void) +{ + syna_tcm_add_module(&diag_module, false); + + wait_for_completion(&diag_remove_complete); +} + +module_init(diag_module_init); +module_exit(diag_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Diagnostics Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_i2c.c b/synaptics_tcm/synaptics_tcm_i2c.c new file mode 100644 index 0000000000..483002ea12 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_i2c.c @@ -0,0 +1,523 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include "synaptics_tcm_core.h" +#include "linux/moduleparam.h" + +#define XFER_ATTEMPTS 10 + +static unsigned char *buf; + +static unsigned int buf_size; + +static struct syna_tcm_bus_io bus_io; + +static struct syna_tcm_hw_interface hw_if; + +static struct platform_device *syna_tcm_i2c_device; + +static struct drm_panel *active_tcm_panel; + +struct drm_panel *tcm_get_panel(void) +{ + return active_tcm_panel; +} +EXPORT_SYMBOL(tcm_get_panel); + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata) +{ + int retval; + struct device_node *np = dev->of_node; + + retval = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + if (!gpio_is_valid(retval)) { + if (retval != -EPROBE_DEFER) + dev_err(dev, "Error getting irq_gpio\n"); + return retval; + } + bdata->irq_gpio = retval; + + of_property_read_u32(np, "synaptics,irq-on-state", + &bdata->irq_on_state); + of_property_read_string(np, "synaptics,pwr-reg-name", + &bdata->pwr_reg_name); + of_property_read_string(np, "synaptics,bus-reg-name", + &bdata->bus_reg_name); + of_property_read_string(np, "synaptics,firmware-name", + &bdata->fw_name); + + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + + retval = of_property_read_u32(np, "synaptics,power-on-state", + &bdata->power_on_state); + if (retval < 0) { + LOGD(dev, "Failed to read synaptics,power-on-state\n"); + bdata->power_on_state = 0; + } + + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &bdata->power_delay_ms); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,power-delay-ms\n"); + return retval; + } + + retval = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + if (!gpio_is_valid(retval)) { + if (retval != -EPROBE_DEFER) + dev_err(dev, "Error getting reset gpio\n"); + return retval; + } + bdata->reset_gpio = retval; + + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &bdata->reset_on_state); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-on-state\n"); + return retval; + } + + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &bdata->reset_active_ms); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-active-ms\n"); + return retval; + } + + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &bdata->reset_delay_ms); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,reset-delay-ms\n"); + return retval; + } + + bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip"); + bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip"); + bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes"); + + retval = of_property_read_u32(np, "synaptics,ubl-i2c-addr", + &bdata->ubl_i2c_addr); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,ubl-i2c-addr\n"); + return retval; + } + + bdata->extend_report = of_property_read_bool(np, + "synaptics,extend_report"); + + return 0; +} +#endif + +static int syna_tcm_i2c_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + unsigned int size) +{ + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + + if (size > buf_size) { + if (buf_size) + kfree(buf); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + LOGE(&i2c->dev, + "Failed to allocate memory for buf\n"); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + return 0; +} + +static int syna_tcm_i2c_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval = 0; + unsigned char address; + unsigned int attempt; + struct i2c_msg msg[2]; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + address = (unsigned char)addr; + + msg[0].addr = bdata->ubl_i2c_addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &address; + + msg[1].addr = bdata->ubl_i2c_addr; + msg[1].flags = I2C_M_RD; + msg[1].len = length; + msg[1].buf = data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, msg, 2) == 2) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_i2c_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int attempt; + unsigned int byte_count; + struct i2c_msg msg; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + byte_count = length + 1; + + retval = syna_tcm_i2c_alloc_mem(tcm_hcd, byte_count); + if (retval < 0) { + LOGE(&i2c->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)addr; + retval = secure_memcpy(&buf[1], + buf_size - 1, + data, + length, + length); + if (retval < 0) { + LOGE(&i2c->dev, + "Failed to copy write data\n"); + goto exit; + } + + msg.addr = bdata->ubl_i2c_addr; + msg.flags = 0; + msg.len = byte_count; + msg.buf = buf; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_i2c_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval = 0; + unsigned int attempt; + struct i2c_msg msg; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + msg.addr = i2c->addr; + msg.flags = I2C_M_RD; + msg.len = length; + msg.buf = data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_i2c_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval = 0; + unsigned int attempt; + struct i2c_msg msg; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + msg.addr = i2c->addr; + msg.flags = 0; + msg.len = length; + msg.buf = data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_tcm_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} + +static int syna_tcm_check_default_tp(struct device_node *dt, const char *prop) +{ + const char *active_tp; + const char *compatible; + char *start; + int ret; + + ret = of_property_read_string(dt->parent, prop, &active_tp); + if (ret) { + pr_err(" %s:fail to read %s %d\n", __func__, prop, ret); + return -ENODEV; + } + + ret = of_property_read_string(dt, "compatible", &compatible); + if (ret < 0) { + pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret); + return -ENODEV; + } + + start = strnstr(active_tp, compatible, strlen(active_tp)); + if (start == NULL) { + pr_err(" %s:no match compatible, %s, %s\n", + __func__, compatible, active_tp); + ret = -ENODEV; + } + + return ret; +} + +static int syna_tcm_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + int retval; + struct device_node *dt = i2c->dev.of_node; + + retval = syna_tcm_check_dt(dt); + if (retval == -EPROBE_DEFER) + return retval; + + if (retval) { + if (!syna_tcm_check_default_tp(dt, "qcom,i2c-touch-active")) + retval = -EPROBE_DEFER; + else + retval = -ENODEV; + + return retval; + } + + syna_tcm_i2c_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_tcm_i2c_device) { + LOGE(&i2c->dev, + "Failed to allocate platform device\n"); + return -ENOMEM; + } + +#ifdef CONFIG_OF + hw_if.bdata = devm_kzalloc(&i2c->dev, sizeof(*hw_if.bdata), GFP_KERNEL); + if (!hw_if.bdata) { + LOGE(&i2c->dev, + "Failed to allocate memory for board data\n"); + return -ENOMEM; + } + retval = parse_dt(&i2c->dev, hw_if.bdata); + if (retval < 0) { + LOGE(&i2c->dev, "Failed to parse dt\n"); + return retval; + } +#else + hw_if.bdata = i2c->dev.platform_data; +#endif + + bus_io.type = BUS_I2C; + bus_io.read = syna_tcm_i2c_read; + bus_io.write = syna_tcm_i2c_write; + bus_io.rmi_read = syna_tcm_i2c_rmi_read; + bus_io.rmi_write = syna_tcm_i2c_rmi_write; + + hw_if.bus_io = &bus_io; + + syna_tcm_i2c_device->dev.parent = &i2c->dev; + syna_tcm_i2c_device->dev.platform_data = &hw_if; + + retval = platform_device_add(syna_tcm_i2c_device); + if (retval < 0) { + LOGE(&i2c->dev, + "Failed to add platform device\n"); + return retval; + } + + return 0; +} + +static int syna_tcm_i2c_remove(struct i2c_client *i2c) +{ + syna_tcm_i2c_device->dev.platform_data = NULL; + + platform_device_unregister(syna_tcm_i2c_device); + + return 0; +} + +static const struct i2c_device_id syna_tcm_id_table[] = { + {I2C_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, syna_tcm_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id syna_tcm_of_match_table[] = { + { + .compatible = "synaptics,tcm-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table); +#else +#define syna_tcm_of_match_table NULL +#endif + +static struct i2c_driver syna_tcm_i2c_driver = { + .driver = { + .name = I2C_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_tcm_of_match_table, + }, + .probe = syna_tcm_i2c_probe, + .remove = syna_tcm_i2c_remove, + .id_table = syna_tcm_id_table, +}; + +int syna_tcm_bus_init(void) +{ + return i2c_add_driver(&syna_tcm_i2c_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_init); + +void syna_tcm_bus_exit(void) +{ + kfree(buf); + + i2c_del_driver(&syna_tcm_i2c_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM I2C Bus Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_recovery.c b/synaptics_tcm/synaptics_tcm_recovery.c new file mode 100644 index 0000000000..3be072faf1 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_recovery.c @@ -0,0 +1,898 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include "synaptics_tcm_core.h" + +#define SET_UP_RECOVERY_MODE true + +#define ENABLE_SYSFS_INTERFACE true + +#define SYSFS_DIR_NAME "recovery" + +#define IHEX_BUF_SIZE (2048 * 1024) + +#define DATA_BUF_SIZE (512 * 1024) + +#define IHEX_RECORD_SIZE 14 + +#define PDT_START_ADDR 0x00e9 + +#define UBL_FN_NUMBER 0x35 + +#define F35_CHUNK_SIZE 16 + +#define F35_CHUNK_SIZE_WORDS 8 + +#define F35_ERASE_ALL_WAIT_MS 5000 + +#define F35_ERASE_ALL_POLL_MS 100 + +#define F35_DATA5_OFFSET 5 + +#define F35_CTRL3_OFFSET 18 + +#define F35_RESET_COMMAND 16 + +#define F35_ERASE_ALL_COMMAND 3 + +#define F35_WRITE_CHUNK_COMMAND 2 + +#define F35_READ_FLASH_STATUS_COMMAND 1 + +struct rmi_pdt_entry { + unsigned char query_base_addr; + unsigned char command_base_addr; + unsigned char control_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count:3; + unsigned char reserved_1:2; + unsigned char fn_version:2; + unsigned char reserved_2:1; + unsigned char fn_number; +} __packed; + +struct rmi_addr { + unsigned short query_base; + unsigned short command_base; + unsigned short control_base; + unsigned short data_base; +}; + +struct recovery_hcd { + bool set_up_recovery_mode; + unsigned char chunk_buf[F35_CHUNK_SIZE + 3]; + unsigned char out_buf[3]; + unsigned char *ihex_buf; + unsigned char *data_buf; + unsigned int ihex_size; + unsigned int ihex_records; + unsigned int data_entries; + struct kobject *sysfs_dir; + struct rmi_addr f35_addr; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(recovery_remove_complete); + +static struct recovery_hcd *recovery_hcd; + +static int recovery_do_recovery(void); + +STORE_PROTOTYPE(recovery, recovery); + +static struct device_attribute *attrs[] = { + ATTRIFY(recovery), +}; + +static ssize_t recovery_sysfs_ihex_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "ihex", + .mode = 0220, + }, + .size = 0, + .write = recovery_sysfs_ihex_store, +}; + +static ssize_t recovery_sysfs_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input == 1) + recovery_hcd->set_up_recovery_mode = true; + else if (input == 2) + recovery_hcd->set_up_recovery_mode = false; + else + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (recovery_hcd->ihex_size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get ihex data\n"); + retval = -EINVAL; + goto exit; + } + + if (recovery_hcd->ihex_size % IHEX_RECORD_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid ihex data\n"); + retval = -EINVAL; + goto exit; + } + + recovery_hcd->ihex_records = recovery_hcd->ihex_size / IHEX_RECORD_SIZE; + + retval = recovery_do_recovery(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do recovery\n"); + goto exit; + } + + retval = count; + +exit: + recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t recovery_sysfs_ihex_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = secure_memcpy(&recovery_hcd->ihex_buf[pos], + IHEX_BUF_SIZE - pos, + buf, + count, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy ihex data\n"); + recovery_hcd->ihex_size = 0; + goto exit; + } + + recovery_hcd->ihex_size = pos + count; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int recovery_device_reset(void) +{ + int retval; + unsigned char command; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + command = F35_RESET_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + msleep(bdata->reset_delay_ms); + + return 0; +} + +static int recovery_add_data_entry(unsigned char data) +{ + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (recovery_hcd->data_entries >= DATA_BUF_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Reached data buffer size limit\n"); + return -EINVAL; + } + + recovery_hcd->data_buf[recovery_hcd->data_entries++] = data; + + return 0; +} + +static int recovery_add_padding(unsigned int *words) +{ + int retval; + unsigned int padding; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + padding = (F35_CHUNK_SIZE_WORDS - *words % F35_CHUNK_SIZE_WORDS); + padding %= F35_CHUNK_SIZE_WORDS; + + while (padding) { + retval = recovery_add_data_entry(0xff); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(0xff); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + (*words)++; + padding--; + } + + return 0; +} + +static int recovery_parse_ihex(void) +{ + int retval; + unsigned char colon; + unsigned char *buf; + unsigned int addr; + unsigned int type; + unsigned int addrl; + unsigned int addrh; + unsigned int data0; + unsigned int data1; + unsigned int count; + unsigned int words; + unsigned int offset; + unsigned int record; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + words = 0; + + offset = 0; + + buf = recovery_hcd->ihex_buf; + + recovery_hcd->data_entries = 0; + + for (record = 0; record < recovery_hcd->ihex_records; record++) { + buf[(record + 1) * IHEX_RECORD_SIZE - 1] = 0x00; + retval = sscanf(&buf[record * IHEX_RECORD_SIZE], + "%c%02x%02x%02x%02x%02x%02x", + &colon, + &count, + &addrh, + &addrl, + &type, + &data0, + &data1); + if (retval != 7) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read ihex record\n"); + return -EINVAL; + } + + if (type == 0x00) { + if ((words % F35_CHUNK_SIZE_WORDS) == 0) { + addr = (addrh << 8) + addrl; + addr += offset; + addr >>= 4; + + retval = recovery_add_data_entry(addr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(addr >> 8); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + } + + retval = recovery_add_data_entry(data0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(data1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + words++; + } else if (type == 0x02) { + retval = recovery_add_padding(&words); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add padding\n"); + return retval; + } + + offset = (data0 << 8) + data1; + offset <<= 4; + } + } + + retval = recovery_add_padding(&words); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add padding\n"); + return retval; + } + + return 0; +} + +static int recovery_check_status(void) +{ + int retval; + unsigned char status; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + retval = syna_tcm_rmi_read(tcm_hcd, + recovery_hcd->f35_addr.data_base, + &status, + sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read status\n"); + return retval; + } + + status = status & 0x1f; + + if (status != 0x00) { + LOGE(tcm_hcd->pdev->dev.parent, + "Recovery mode status = 0x%02x\n", + status); + return -EINVAL; + } + + return 0; +} + +static int recovery_write_flash(void) +{ + int retval; + unsigned char *data_ptr; + unsigned int chunk_buf_size; + unsigned int chunk_data_size; + unsigned int entries_written; + unsigned int entries_to_write; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + entries_written = 0; + + data_ptr = recovery_hcd->data_buf; + + chunk_buf_size = sizeof(recovery_hcd->chunk_buf); + + chunk_data_size = chunk_buf_size - 1; + + recovery_hcd->chunk_buf[chunk_buf_size - 1] = F35_WRITE_CHUNK_COMMAND; + + while (entries_written < recovery_hcd->data_entries) { + entries_to_write = F35_CHUNK_SIZE + 2; + + retval = secure_memcpy(recovery_hcd->chunk_buf, + chunk_buf_size - 1, + data_ptr, + recovery_hcd->data_entries - entries_written, + entries_to_write); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy chunk data\n"); + return retval; + } + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base, + recovery_hcd->chunk_buf, + chunk_buf_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write chunk data\n"); + return retval; + } + + data_ptr += entries_to_write; + entries_written += entries_to_write; + } + + retval = recovery_check_status(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get no error recovery mode status\n"); + return retval; + } + + return 0; +} + +static int recovery_poll_erase_completion(void) +{ + int retval; + unsigned char status; + unsigned char command; + unsigned char data_base; + unsigned int timeout; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + timeout = F35_ERASE_ALL_WAIT_MS; + + data_base = recovery_hcd->f35_addr.data_base; + + do { + command = F35_READ_FLASH_STATUS_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.command_base, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + do { + retval = syna_tcm_rmi_read(tcm_hcd, + recovery_hcd->f35_addr.command_base, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read command status\n"); + return retval; + } + + if (command == 0x00) + break; + + if (timeout == 0) + break; + + msleep(F35_ERASE_ALL_POLL_MS); + timeout -= F35_ERASE_ALL_POLL_MS; + } while (true); + + if (command != 0 && timeout == 0) { + retval = -EINVAL; + goto exit; + } + + retval = syna_tcm_rmi_read(tcm_hcd, + data_base + F35_DATA5_OFFSET, + &status, + sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read flash status\n"); + return retval; + } + + if ((status & 0x01) == 0x00) + break; + + if (timeout == 0) { + retval = -EINVAL; + goto exit; + } + + msleep(F35_ERASE_ALL_POLL_MS); + timeout -= F35_ERASE_ALL_POLL_MS; + } while (true); + + retval = 0; + +exit: + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get erase completion\n"); + } + + return retval; +} + +static int recovery_erase_flash(void) +{ + int retval; + unsigned char command; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + command = F35_ERASE_ALL_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + if (recovery_hcd->f35_addr.command_base) { + retval = recovery_poll_erase_completion(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for erase completion\n"); + return retval; + } + } else { + msleep(F35_ERASE_ALL_WAIT_MS); + } + + retval = recovery_check_status(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get no error recovery mode status\n"); + return retval; + } + + return 0; +} + +static int recovery_set_up_recovery_mode(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } + + retval = tcm_hcd->write_message(tcm_hcd, + recovery_hcd->out_buf[0], + &recovery_hcd->out_buf[1], + 2, + NULL, + NULL, + NULL, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_REBOOT_TO_ROM_BOOTLOADER)); + return retval; + } + + msleep(bdata->reset_delay_ms); + + return 0; +} + +static int recovery_do_recovery(void) +{ + int retval; + struct rmi_pdt_entry p_entry; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + retval = recovery_parse_ihex(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse ihex data\n"); + return retval; + } + + if (recovery_hcd->set_up_recovery_mode) { + retval = recovery_set_up_recovery_mode(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up recovery mode\n"); + return retval; + } + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_START_ADDR, + (unsigned char *)&p_entry, + sizeof(p_entry)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read PDT entry\n"); + return retval; + } + + if (p_entry.fn_number != UBL_FN_NUMBER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to find F$35\n"); + return -ENODEV; + } + + recovery_hcd->f35_addr.query_base = p_entry.query_base_addr; + recovery_hcd->f35_addr.command_base = p_entry.command_base_addr; + recovery_hcd->f35_addr.control_base = p_entry.control_base_addr; + recovery_hcd->f35_addr.data_base = p_entry.data_base_addr; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of recovery\n"); + + retval = recovery_erase_flash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Flash erased\n"); + + retval = recovery_write_flash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Flash written\n"); + + retval = recovery_device_reset(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "End of recovery\n"); + + if (recovery_hcd->set_up_recovery_mode) + return 0; + + tcm_hcd->update_watchdog(tcm_hcd, true); + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + return retval; + } + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + return retval; + } + } + + return 0; +} + +static int recovery_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + int idx; + + recovery_hcd = kzalloc(sizeof(*recovery_hcd), GFP_KERNEL); + if (!recovery_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for recovery_hcd\n"); + return -ENOMEM; + } + + recovery_hcd->ihex_buf = kzalloc(IHEX_BUF_SIZE, GFP_KERNEL); + if (!recovery_hcd->ihex_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for ihex_buf\n"); + goto err_allocate_ihex_buf; + } + + recovery_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL); + if (!recovery_hcd->data_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for data_buf\n"); + goto err_allocate_data_buf; + } + + recovery_hcd->tcm_hcd = tcm_hcd; + + recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE; + + recovery_hcd->out_buf[0] = CMD_REBOOT_TO_ROM_BOOTLOADER; + recovery_hcd->out_buf[1] = 0; + recovery_hcd->out_buf[2] = 0; + + if (!ENABLE_SYSFS_INTERFACE) + return 0; + + recovery_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!recovery_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(recovery_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(recovery_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(recovery_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(recovery_hcd->sysfs_dir); + +err_sysfs_create_dir: + kfree(recovery_hcd->data_buf); +err_allocate_data_buf: + kfree(recovery_hcd->ihex_buf); +err_allocate_ihex_buf: + kfree(recovery_hcd); + recovery_hcd = NULL; + + return retval; +} + +static int recovery_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!recovery_hcd) + goto exit; + + if (ENABLE_SYSFS_INTERFACE) { + sysfs_remove_bin_file(recovery_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + sysfs_remove_file(recovery_hcd->sysfs_dir, + &(*attrs[idx]).attr); + } + + kobject_put(recovery_hcd->sysfs_dir); + } + + kfree(recovery_hcd->data_buf); + kfree(recovery_hcd->ihex_buf); + kfree(recovery_hcd); + recovery_hcd = NULL; + +exit: + complete(&recovery_remove_complete); + + return 0; +} + +static int recovery_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!recovery_hcd) { + retval = recovery_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb recovery_module = { + .type = TCM_RECOVERY, + .init = recovery_init, + .remove = recovery_remove, + .syncbox = NULL, + .asyncbox = NULL, + .reset = recovery_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init recovery_module_init(void) +{ + return syna_tcm_add_module(&recovery_module, true); +} + +static void __exit recovery_module_exit(void) +{ + syna_tcm_add_module(&recovery_module, false); + + wait_for_completion(&recovery_remove_complete); +} + +module_init(recovery_module_init); +module_exit(recovery_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Recovery Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_reflash.c b/synaptics_tcm/synaptics_tcm_reflash.c new file mode 100644 index 0000000000..bfb6e6db17 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_reflash.c @@ -0,0 +1,2193 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include "synaptics_tcm_core.h" + +#define STARTUP_REFLASH + +#define FORCE_REFLASH false + +#define ENABLE_SYSFS_INTERFACE true + +#define SYSFS_DIR_NAME "reflash" + +#define CUSTOM_DIR_NAME "custom" + +#define FW_IMAGE_NAME "synaptics_firmware.img" + +#define BOOT_CONFIG_ID "BOOT_CONFIG" + +#define APP_CODE_ID "APP_CODE" + +#define PROD_TEST_ID "APP_PROD_TEST" + +#define APP_CONFIG_ID "APP_CONFIG" + +#define DISP_CONFIG_ID "DISPLAY" + +#define FB_READY_COUNT 2 + +#define FB_READY_WAIT_MS 100 + +#define FB_READY_TIMEOUT_S 80 + +#define IMAGE_FILE_MAGIC_VALUE 0x4818472b + +#define FLASH_AREA_MAGIC_VALUE 0x7c05e516 + +#define BOOT_CONFIG_SIZE 8 + +#define BOOT_CONFIG_SLOTS 16 + +#define IMAGE_BUF_SIZE (512 * 1024) + +#define ERASE_FLASH_DELAY_MS 500 + +#define WRITE_FLASH_DELAY_MS 20 + +#define REFLASH (1 << 0) + +#define FORCE_UPDATE (1 << 1) + +#define APP_CFG_UPDATE (1 << 2) + +#define DISP_CFG_UPDATE (1 << 3) + +#define BOOT_CFG_UPDATE (1 << 4) + +#define BOOT_CFG_LOCKDOWN (1 << 5) + +#define reflash_write(p_name) \ +static int reflash_write_##p_name(void) \ +{ \ + int retval; \ + unsigned int size; \ + unsigned int flash_addr; \ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \ + const unsigned char *data; \ +\ + data = reflash_hcd->image_info.p_name.data; \ + size = reflash_hcd->image_info.p_name.size; \ + flash_addr = reflash_hcd->image_info.p_name.flash_addr; \ +\ + retval = reflash_write_flash(flash_addr, data, size); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to write to flash\n"); \ + return retval; \ + } \ +\ + return 0; \ +} + +#define reflash_erase(p_name) \ +static int reflash_erase_##p_name(void) \ +{ \ + int retval; \ + unsigned int size; \ + unsigned int flash_addr; \ + unsigned int page_start; \ + unsigned int page_count; \ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \ +\ + flash_addr = reflash_hcd->image_info.p_name.flash_addr; \ +\ + page_start = flash_addr / reflash_hcd->page_size; \ +\ + size = reflash_hcd->image_info.p_name.size; \ + page_count = ceil_div(size, reflash_hcd->page_size); \ +\ + LOGD(tcm_hcd->pdev->dev.parent, \ + "Page start = %d\n", \ + page_start); \ +\ + LOGD(tcm_hcd->pdev->dev.parent, \ + "Page count = %d\n", \ + page_count); \ +\ + retval = reflash_erase_flash(page_start, page_count); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to erase flash pages\n"); \ + return retval; \ + } \ +\ + return 0; \ +} + +#define reflash_update(p_name) \ +static int reflash_update_##p_name(bool reset) \ +{ \ + int retval; \ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \ +\ + retval = reflash_set_up_flash_access(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to set up flash access\n"); \ + return retval; \ + } \ +\ + tcm_hcd->update_watchdog(tcm_hcd, false); \ +\ + retval = reflash_check_##p_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed "#p_name" partition check\n"); \ + reset = true; \ + goto reset; \ + } \ +\ + retval = reflash_erase_##p_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to erase "#p_name" partition\n"); \ + reset = true; \ + goto reset; \ + } \ +\ + LOGN(tcm_hcd->pdev->dev.parent, \ + "Partition erased ("#p_name")\n"); \ +\ + retval = reflash_write_##p_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to write "#p_name" partition\n"); \ + reset = true; \ + goto reset; \ + } \ +\ + LOGN(tcm_hcd->pdev->dev.parent, \ + "Partition written ("#p_name")\n"); \ +\ + retval = 0; \ +\ +reset: \ + if (!reset) \ + goto exit; \ +\ + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to do reset\n"); \ + } \ +\ +exit: \ + tcm_hcd->update_watchdog(tcm_hcd, true); \ +\ + return retval; \ +} + +#define reflash_show_data() \ +{ \ + LOCK_BUFFER(reflash_hcd->read); \ +\ + readlen = MIN(count, reflash_hcd->read.data_length - pos); \ +\ + retval = secure_memcpy(buf, \ + count, \ + &reflash_hcd->read.buf[pos], \ + reflash_hcd->read.buf_size - pos, \ + readlen); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to copy read data\n"); \ + } else { \ + retval = readlen; \ + } \ +\ + UNLOCK_BUFFER(reflash_hcd->read); \ +} + +enum update_area { + NONE = 0, + FIRMWARE_CONFIG, + CONFIG_ONLY, +}; + +struct app_config_header { + unsigned short magic_value[4]; + unsigned char checksum[4]; + unsigned char length[2]; + unsigned char build_id[4]; + unsigned char customer_config_id[16]; +}; + +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; + +struct block_data { + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; +}; + +struct image_info { + struct block_data boot_config; + struct block_data app_firmware; + struct block_data prod_test_firmware; + struct block_data app_config; + struct block_data disp_config; +}; + +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; + +struct boot_config { + union { + unsigned char i2c_address; + struct { + unsigned char cpha:1; + unsigned char cpol:1; + unsigned char word0_b2__7:6; + } __packed; + }; + unsigned char attn_polarity:1; + unsigned char attn_drive:2; + unsigned char attn_pullup:1; + unsigned char word0_b12__14:3; + unsigned char used:1; + unsigned short customer_part_id; + unsigned short boot_timeout; + unsigned short continue_on_reset:1; + unsigned short word3_b1__15:15; +} __packed; + +struct reflash_hcd { + bool force_update; + bool disp_cfg_update; + const unsigned char *image; + unsigned char *image_buf; + unsigned int image_size; + unsigned int page_size; + unsigned int write_block_size; + unsigned int max_write_payload_size; + const struct firmware *fw_entry; + struct mutex reflash_mutex; + struct kobject *sysfs_dir; + struct kobject *custom_dir; + struct work_struct work; + struct workqueue_struct *workqueue; + struct image_info image_info; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer read; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(reflash_remove_complete); + +static struct reflash_hcd *reflash_hcd; + +static int reflash_get_fw_image(void); + +static int reflash_read_data(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output); + +static int reflash_update_custom_otp(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_custom_lcm(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_custom_oem(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_boot_config(bool lock); + +static int reflash_update_app_config(bool reset); + +static int reflash_update_disp_config(bool reset); + +static int reflash_do_reflash(void); + +STORE_PROTOTYPE(reflash, reflash); + +static struct device_attribute *attrs[] = { + ATTRIFY(reflash), +}; + +static ssize_t reflash_sysfs_image_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lockdown_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lockdown_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lcm_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lcm_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_oem_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_oem_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attrs[] = { + { + .attr = { + .name = "image", + .mode = 0220, + }, + .size = 0, + .write = reflash_sysfs_image_store, + }, + { + .attr = { + .name = "lockdown", + .mode = 0664, + }, + .size = 0, + .read = reflash_sysfs_lockdown_show, + .write = reflash_sysfs_lockdown_store, + }, + { + .attr = { + .name = "lcm", + .mode = 0664, + }, + .size = 0, + .read = reflash_sysfs_lcm_show, + .write = reflash_sysfs_lcm_store, + }, + { + .attr = { + .name = "oem", + .mode = 0664, + }, + .size = 0, + .read = reflash_sysfs_oem_show, + .write = reflash_sysfs_oem_store, + }, +}; + +static ssize_t reflash_sysfs_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + if (reflash_hcd->image_size != 0) + reflash_hcd->image = reflash_hcd->image_buf; + + reflash_hcd->force_update = input & FORCE_UPDATE ? true : false; + + if (input & REFLASH || input & FORCE_UPDATE) { + retval = reflash_do_reflash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reflash\n"); + goto exit; + } + } + + if ((input & ~(REFLASH | FORCE_UPDATE)) == 0) { + retval = count; + goto exit; + } + + retval = reflash_get_fw_image(); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + if (input & BOOT_CFG_LOCKDOWN) { + retval = reflash_update_boot_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to lockdown boot config\n"); + goto exit; + } + } else if (input & BOOT_CFG_UPDATE) { + retval = reflash_update_boot_config(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update boot config\n"); + goto exit; + } + } + + if (input & REFLASH || input & FORCE_UPDATE) { + retval = count; + goto exit; + } + + if (input & DISP_CFG_UPDATE) { + if (input & APP_CFG_UPDATE) + retval = reflash_update_disp_config(false); + else + retval = reflash_update_disp_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash display config\n"); + goto exit; + } + } + + if (input & APP_CFG_UPDATE) { + retval = reflash_update_app_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application config\n"); + goto exit; + } + } + + retval = count; + +exit: + if (reflash_hcd->fw_entry) { + release_firmware(reflash_hcd->fw_entry); + reflash_hcd->fw_entry = NULL; + } + + reflash_hcd->image = NULL; + reflash_hcd->image_size = 0; + reflash_hcd->force_update = FORCE_REFLASH; + + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_image_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = secure_memcpy(&reflash_hcd->image_buf[pos], + IMAGE_BUF_SIZE - pos, + buf, + count, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware image data\n"); + reflash_hcd->image_size = 0; + goto exit; + } + + reflash_hcd->image_size = pos + count; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lockdown_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_OTP, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read lockdown data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lockdown_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_otp(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom OTP data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lcm_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_LCM, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read LCM data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lcm_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_lcm(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom LCM data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_oem_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_OEM, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read OEM data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_oem_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_oem(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom OEM data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int reflash_set_up_flash_access(void) +{ + int retval; + unsigned int temp; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } + + temp = tcm_hcd->boot_info.write_block_size_words; + reflash_hcd->write_block_size = temp * 2; + + temp = le2_to_uint(tcm_hcd->boot_info.erase_page_size_words); + reflash_hcd->page_size = temp * 2; + + temp = le2_to_uint(tcm_hcd->boot_info.max_write_payload_size); + reflash_hcd->max_write_payload_size = temp; + + LOGD(tcm_hcd->pdev->dev.parent, + "Write block size = %d\n", + reflash_hcd->write_block_size); + + LOGD(tcm_hcd->pdev->dev.parent, + "Page size = %d\n", + reflash_hcd->page_size); + + LOGD(tcm_hcd->pdev->dev.parent, + "Max write payload size = %d\n", + reflash_hcd->max_write_payload_size); + + if (reflash_hcd->write_block_size > (tcm_hcd->wr_chunk_size - 5)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Write size greater than available chunk space\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_parse_fw_image(void) +{ + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct image_info *image_info; + struct area_descriptor *descriptor; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *image; + const unsigned char *content; + + image = reflash_hcd->image; + image_info = &reflash_hcd->image_info; + header = (struct image_header *)image; + + reflash_hcd->disp_cfg_update = false; + + magic_value = le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid image file magic value\n"); + return -EINVAL; + } + + memset(image_info, 0x00, sizeof(*image_info)); + + offset = sizeof(*header); + num_of_areas = le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2; + checksum = le4_to_uint(descriptor->checksum); + + if (!memcmp((char *)descriptor->id_string, + BOOT_CONFIG_ID, + strlen(BOOT_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config checksum error\n"); + return -EINVAL; + } + image_info->boot_config.size = length; + image_info->boot_config.data = content; + image_info->boot_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + APP_CODE_ID, + strlen(APP_CODE_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "APP firmware checksum error\n"); + return -EINVAL; + } + image_info->app_firmware.size = length; + image_info->app_firmware.data = content; + image_info->app_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + PROD_TEST_ID, + strlen(PROD_TEST_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Production test checksum error\n"); + return -EINVAL; + } + image_info->prod_test_firmware.size = length; + image_info->prod_test_firmware.data = content; + image_info->prod_test_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Production test firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Production test flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + APP_CONFIG_ID, + strlen(APP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application config checksum error\n"); + return -EINVAL; + } + image_info->app_config.size = length; + image_info->app_config.data = content; + image_info->app_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + DISP_CONFIG_ID, + strlen(DISP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Display config checksum error\n"); + return -EINVAL; + } + reflash_hcd->disp_cfg_update = true; + image_info->disp_config.size = length; + image_info->disp_config.data = content; + image_info->disp_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Display config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Display config flash address = 0x%08x\n", + flash_addr); + } + } + + return 0; +} + +static int reflash_get_fw_image(void) +{ + int retval; + const char *fw_name; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->fw_name) + fw_name = bdata->fw_name; + else + fw_name = FW_IMAGE_NAME; + + if (reflash_hcd->image == NULL) { + retval = request_firmware(&reflash_hcd->fw_entry, fw_name, + tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to request %s\n", + fw_name); + return retval; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware image size = %d\n", + (unsigned int)reflash_hcd->fw_entry->size); + + reflash_hcd->image = reflash_hcd->fw_entry->data; + reflash_hcd->image_size = reflash_hcd->fw_entry->size; + } + + retval = reflash_parse_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse firmware image\n"); + return retval; + } + + return 0; +} + +static enum update_area reflash_compare_id_info(void) +{ + enum update_area update_area; + unsigned int idx; + unsigned int image_fw_id; + unsigned int device_fw_id; + unsigned char *image_config_id; + unsigned char *device_config_id; + struct app_config_header *header; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *app_config_data; + + update_area = NONE; + + if (reflash_hcd->image_info.app_config.size < sizeof(*header)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid application config in image file\n"); + goto exit; + } + + app_config_data = reflash_hcd->image_info.app_config.data; + header = (struct app_config_header *)app_config_data; + + if (reflash_hcd->force_update) { + update_area = FIRMWARE_CONFIG; + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + update_area = FIRMWARE_CONFIG; + goto exit; + } + + image_fw_id = le4_to_uint(header->build_id); + device_fw_id = tcm_hcd->packrat_number; + + if (image_fw_id > device_fw_id) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image firmware ID newer than device firmware ID\n"); + update_area = FIRMWARE_CONFIG; + goto exit; + } else if (image_fw_id < device_fw_id) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image firmware ID older than device firmware ID\n"); + update_area = NONE; + goto exit; + } + + image_config_id = header->customer_config_id; + device_config_id = tcm_hcd->app_info.customer_config_id; + + for (idx = 0; idx < 16; idx++) { + if (image_config_id[idx] > device_config_id[idx]) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image config ID newer than device's ID\n"); + update_area = CONFIG_ONLY; + goto exit; + } else if (image_config_id[idx] < device_config_id[idx]) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image config ID older than device's ID\n"); + update_area = NONE; + goto exit; + } + } + + update_area = NONE; + +exit: + if (update_area == NONE) + LOGD(tcm_hcd->pdev->dev.parent, "No need to do reflash\n"); + else + LOGD(tcm_hcd->pdev->dev.parent, + "Updating %s\n", + update_area == FIRMWARE_CONFIG ? + "firmware and config" : + "config only"); + + return update_area; +} + +static int reflash_read_flash(unsigned int address, unsigned char *data, + unsigned int datalen) +{ + int retval; + unsigned int length_words; + unsigned int flash_addr_words; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOCK_BUFFER(reflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->out, + 6); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd->out.buf\n"); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + length_words = datalen / 2; + flash_addr_words = address / 2; + + reflash_hcd->out.buf[0] = (unsigned char)flash_addr_words; + reflash_hcd->out.buf[1] = (unsigned char)(flash_addr_words >> 8); + reflash_hcd->out.buf[2] = (unsigned char)(flash_addr_words >> 16); + reflash_hcd->out.buf[3] = (unsigned char)(flash_addr_words >> 24); + reflash_hcd->out.buf[4] = (unsigned char)length_words; + reflash_hcd->out.buf[5] = (unsigned char)(length_words >> 8); + + LOCK_BUFFER(reflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_READ_FLASH, + reflash_hcd->out.buf, + 6, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_READ_FLASH)); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->out); + + if (reflash_hcd->resp.data_length != datalen) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read requested length\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + return -EIO; + } + + retval = secure_memcpy(data, + datalen, + reflash_hcd->resp.buf, + reflash_hcd->resp.buf_size, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy read data\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + + return 0; +} + +static int reflash_read_data(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output) +{ + int retval; + unsigned int temp; + unsigned int addr; + unsigned int length; + struct syna_tcm_app_info *app_info; + struct syna_tcm_boot_info *boot_info; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + switch (area) { + case CUSTOM_LCM: + case CUSTOM_OEM: + case PPDT: + retval = tcm_hcd->get_data_location(tcm_hcd, + area, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + break; + default: + break; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + app_info = &tcm_hcd->app_info; + boot_info = &tcm_hcd->boot_info; + + switch (area) { + case BOOT_CONFIG: + temp = le2_to_uint(boot_info->boot_config_start_block); + addr = temp * reflash_hcd->write_block_size; + length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS; + break; + case APP_CONFIG: + temp = le2_to_uint(app_info->app_config_start_write_block); + addr = temp * reflash_hcd->write_block_size; + length = le2_to_uint(app_info->app_config_size); + break; + case DISP_CONFIG: + temp = le4_to_uint(boot_info->display_config_start_block); + addr = temp * reflash_hcd->write_block_size; + temp = le2_to_uint(boot_info->display_config_length_blocks); + length = temp * reflash_hcd->write_block_size; + break; + case CUSTOM_OTP: + temp = le2_to_uint(boot_info->custom_otp_start_block); + addr = temp * reflash_hcd->write_block_size; + temp = le2_to_uint(boot_info->custom_otp_length_blocks); + length = temp * reflash_hcd->write_block_size; + break; + case CUSTOM_LCM: + case CUSTOM_OEM: + case PPDT: + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data area\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + LOCK_BUFFER(reflash_hcd->read); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->read, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for read.buf\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + retval = reflash_read_flash(addr, reflash_hcd->read.buf, length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from flash\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + reflash_hcd->read.data_length = length; + + if (output != NULL) { + retval = syna_tcm_alloc_mem(tcm_hcd, + output, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output->buf\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + retval = secure_memcpy(output->buf, + output->buf_size, + reflash_hcd->read.buf, + reflash_hcd->read.buf_size, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy read data\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + output->data_length = length; + } + + UNLOCK_BUFFER(reflash_hcd->read); + + retval = 0; + +run_app_firmware: + if (!run_app_firmware) + goto exit; + + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + +exit: + return retval; +} + +static int reflash_check_boot_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int device_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.boot_config.size < BOOT_CONFIG_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "No valid boot config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.boot_config.flash_addr; + + temp = le2_to_uint(tcm_hcd->boot_info.boot_config_start_block); + device_addr = temp * reflash_hcd->write_block_size; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_app_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.app_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.app_config.flash_addr; + image_size = reflash_hcd->image_info.app_config.size; + + temp = le2_to_uint(tcm_hcd->app_info.app_config_start_write_block); + device_addr = temp * reflash_hcd->write_block_size; + device_size = le2_to_uint(tcm_hcd->app_info.app_config_size); + + if (device_addr == 0 && device_size == 0) + return 0; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + if (image_size != device_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config size mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_disp_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.disp_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No display config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.disp_config.flash_addr; + image_size = reflash_hcd->image_info.disp_config.size; + + temp = le4_to_uint(tcm_hcd->boot_info.display_config_start_block); + device_addr = temp * reflash_hcd->write_block_size; + + temp = le2_to_uint(tcm_hcd->boot_info.display_config_length_blocks); + device_size = temp * reflash_hcd->write_block_size; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + if (image_size != device_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config size mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_prod_test_firmware(void) +{ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.prod_test_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No production test firmware in image file\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_app_firmware(void) +{ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.app_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application firmware in image file\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_write_flash(unsigned int address, const unsigned char *data, + unsigned int datalen) +{ + int retval; + unsigned int offset; + unsigned int w_length; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int flash_address; + unsigned int block_address; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + w_length = tcm_hcd->wr_chunk_size - 5; + + w_length = w_length - (w_length % reflash_hcd->write_block_size); + + w_length = MIN(w_length, reflash_hcd->max_write_payload_size); + + offset = 0; + + remaining_length = datalen; + + LOCK_BUFFER(reflash_hcd->out); + LOCK_BUFFER(reflash_hcd->resp); + + while (remaining_length) { + if (remaining_length > w_length) + xfer_length = w_length; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->out, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + flash_address = address + offset; + block_address = flash_address / reflash_hcd->write_block_size; + reflash_hcd->out.buf[0] = (unsigned char)block_address; + reflash_hcd->out.buf[1] = (unsigned char)(block_address >> 8); + + retval = secure_memcpy(&reflash_hcd->out.buf[2], + reflash_hcd->out.buf_size - 2, + &data[offset], + datalen - offset, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy write data\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_WRITE_FLASH, + reflash_hcd->out.buf, + xfer_length + 2, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + WRITE_FLASH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_WRITE_FLASH)); + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address = 0x%08x\n", + flash_address); + LOGE(tcm_hcd->pdev->dev.parent, + "Data length = %d\n", + xfer_length); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + + return 0; +} + +reflash_write(app_config) + +reflash_write(disp_config) + +reflash_write(prod_test_firmware) + +reflash_write(app_firmware) + +static int reflash_erase_flash(unsigned int page_start, unsigned int page_count) +{ + int retval; + unsigned char out_buf[2]; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + out_buf[0] = (unsigned char)page_start; + out_buf[1] = (unsigned char)page_count; + + LOCK_BUFFER(reflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ERASE_FLASH, + out_buf, + sizeof(out_buf), + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + ERASE_FLASH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ERASE_FLASH)); + UNLOCK_BUFFER(reflash_hcd->resp); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + + return 0; +} + +reflash_erase(app_config) + +reflash_erase(disp_config) + +reflash_erase(prod_test_firmware) + +reflash_erase(app_firmware) + +static int reflash_update_custom_otp(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int temp; + unsigned int addr; + unsigned int length; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_start_block); + addr = temp * reflash_hcd->write_block_size; + + temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_length_blocks); + length = temp * reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +static int reflash_update_custom_lcm(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int addr; + unsigned int length; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->get_data_location(tcm_hcd, + CUSTOM_LCM, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (offset == 0) { + page_start = addr / reflash_hcd->page_size; + + page_count = ceil_div(length, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash pages\n"); + goto run_app_firmware; + } + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +static int reflash_update_custom_oem(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int addr; + unsigned int length; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->get_data_location(tcm_hcd, + CUSTOM_OEM, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (offset == 0) { + page_start = addr / reflash_hcd->page_size; + + page_count = ceil_div(length, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash pages\n"); + goto run_app_firmware; + } + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +static int reflash_update_boot_config(bool lock) +{ + int retval; + unsigned char slot_used; + unsigned int idx; + unsigned int addr = 0; + struct boot_config *data; + struct boot_config *last_slot; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + retval = reflash_check_boot_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed boot_config partition check\n"); + goto reset; + } + + retval = reflash_read_data(BOOT_CONFIG, false, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read boot config\n"); + goto reset; + } + + LOCK_BUFFER(reflash_hcd->read); + + data = (struct boot_config *)reflash_hcd->read.buf; + last_slot = data + (BOOT_CONFIG_SLOTS - 1); + slot_used = tcm_hcd->id_info.mode == MODE_TDDI_BOOTLOADER ? 0 : 1; + + if (last_slot->used == slot_used) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config already locked down\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto reset; + } + + if (lock) { + idx = BOOT_CONFIG_SLOTS - 1; + } else { + for (idx = 0; idx < BOOT_CONFIG_SLOTS; idx++) { + if (data->used == slot_used) { + data++; + continue; + } else { + break; + } + } + } + + UNLOCK_BUFFER(reflash_hcd->read); + + if (idx == BOOT_CONFIG_SLOTS) { + LOGE(tcm_hcd->pdev->dev.parent, + "No free boot config slot available\n"); + goto reset; + } + + addr += idx * BOOT_CONFIG_SIZE; + + retval = reflash_write_flash(addr, + reflash_hcd->image_info.boot_config.data, + BOOT_CONFIG_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Slot %d updated with new boot config\n", + idx); + + retval = 0; + +reset: + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +reflash_update(app_config) + +reflash_update(disp_config) + +reflash_update(prod_test_firmware) + +reflash_update(app_firmware) + +static int reflash_do_reflash(void) +{ + int retval; + enum update_area update_area; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_get_fw_image(); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Start of reflash\n"); + + atomic_set(&tcm_hcd->firmware_flashing, 1); + + update_area = reflash_compare_id_info(); + + switch (update_area) { + case FIRMWARE_CONFIG: + retval = reflash_update_app_firmware(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application firmware\n"); + goto exit; + } + memset(&tcm_hcd->app_info, 0x00, sizeof(tcm_hcd->app_info)); + if (tcm_hcd->features.dual_firmware) { + retval = reflash_update_prod_test_firmware(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash production test\n"); + goto exit; + } + } + case CONFIG_ONLY: + if (reflash_hcd->disp_cfg_update) { + retval = reflash_update_disp_config(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash display config\n"); + goto exit; + } + } + retval = reflash_update_app_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application config\n"); + goto exit; + } + break; + case NONE: + default: + break; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "End of reflash\n"); + + retval = 0; + +exit: + if (reflash_hcd->fw_entry) { + release_firmware(reflash_hcd->fw_entry); + reflash_hcd->fw_entry = NULL; + reflash_hcd->image = NULL; + reflash_hcd->image_size = 0; + } + + atomic_set(&tcm_hcd->firmware_flashing, 0); + wake_up_interruptible(&tcm_hcd->reflash_wq); + return retval; +} + +#ifdef STARTUP_REFLASH +static void reflash_startup_work(struct work_struct *work) +{ + int retval; +#if defined(CONFIG_DRM) || defined(CONFIG_FB) + unsigned int timeout; +#endif + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + +#if defined(CONFIG_DRM) || defined(CONFIG_FB) + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS; + + while (tcm_hcd->fb_ready != FB_READY_COUNT - 1) { + if (timeout == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for FB ready\n"); + return; + } + msleep(FB_READY_WAIT_MS); + timeout--; + } +#endif + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_do_reflash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reflash\n"); + } + + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); +} +#endif + +static int reflash_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + int idx; + + reflash_hcd = kzalloc(sizeof(*reflash_hcd), GFP_KERNEL); + if (!reflash_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd\n"); + return -ENOMEM; + } + + reflash_hcd->image_buf = kzalloc(IMAGE_BUF_SIZE, GFP_KERNEL); + if (!reflash_hcd->image_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for image_buf\n"); + goto err_allocate_memory; + } + + reflash_hcd->tcm_hcd = tcm_hcd; + + reflash_hcd->force_update = FORCE_REFLASH; + + mutex_init(&reflash_hcd->reflash_mutex); + + INIT_BUFFER(reflash_hcd->out, false); + INIT_BUFFER(reflash_hcd->resp, false); + INIT_BUFFER(reflash_hcd->read, false); + +#ifdef STARTUP_REFLASH + reflash_hcd->workqueue = + create_singlethread_workqueue("syna_tcm_reflash"); + INIT_WORK(&reflash_hcd->work, reflash_startup_work); + queue_work(reflash_hcd->workqueue, &reflash_hcd->work); +#endif + + if (!ENABLE_SYSFS_INTERFACE) + return 0; + + reflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!reflash_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(reflash_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + reflash_hcd->custom_dir = kobject_create_and_add(CUSTOM_DIR_NAME, + reflash_hcd->sysfs_dir); + if (!reflash_hcd->custom_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create custom sysfs directory\n"); + retval = -EINVAL; + goto err_custom_sysfs_create_dir; + } + + for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) { + retval = sysfs_create_bin_file(reflash_hcd->custom_dir, + &bin_attrs[idx]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_custom_sysfs_create_bin_file; + } + } + + tcm_hcd->read_flash_data = reflash_read_data; + + return 0; + +err_custom_sysfs_create_bin_file: + for (idx--; idx > 0; idx--) + sysfs_remove_bin_file(reflash_hcd->custom_dir, &bin_attrs[idx]); + + kobject_put(reflash_hcd->custom_dir); + + idx = ARRAY_SIZE(attrs); + +err_custom_sysfs_create_dir: + sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(reflash_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(reflash_hcd->sysfs_dir); + +err_sysfs_create_dir: +err_allocate_memory: + kfree(reflash_hcd->image_buf); + + RELEASE_BUFFER(reflash_hcd->read); + RELEASE_BUFFER(reflash_hcd->resp); + RELEASE_BUFFER(reflash_hcd->out); + + kfree(reflash_hcd); + reflash_hcd = NULL; + + return retval; +} + +static int reflash_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!reflash_hcd) + goto exit; + + tcm_hcd->read_flash_data = NULL; + + if (ENABLE_SYSFS_INTERFACE) { + for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) { + sysfs_remove_bin_file(reflash_hcd->custom_dir, + &bin_attrs[idx]); + } + + kobject_put(reflash_hcd->custom_dir); + + sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + sysfs_remove_file(reflash_hcd->sysfs_dir, + &(*attrs[idx]).attr); + } + + kobject_put(reflash_hcd->sysfs_dir); + } + +#ifdef STARTUP_REFLASH + cancel_work_sync(&reflash_hcd->work); + flush_workqueue(reflash_hcd->workqueue); + destroy_workqueue(reflash_hcd->workqueue); +#endif + + kfree(reflash_hcd->image_buf); + + RELEASE_BUFFER(reflash_hcd->read); + RELEASE_BUFFER(reflash_hcd->resp); + RELEASE_BUFFER(reflash_hcd->out); + + kfree(reflash_hcd); + reflash_hcd = NULL; + +exit: + complete(&reflash_remove_complete); + + return 0; +} + +static int reflash_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!reflash_hcd) { + retval = reflash_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb reflash_module = { + .type = TCM_REFLASH, + .init = reflash_init, + .remove = reflash_remove, + .syncbox = NULL, + .asyncbox = NULL, + .reset = reflash_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init reflash_module_init(void) +{ + return syna_tcm_add_module(&reflash_module, true); +} + +static void __exit reflash_module_exit(void) +{ + syna_tcm_add_module(&reflash_module, false); + + wait_for_completion(&reflash_remove_complete); +} + +module_init(reflash_module_init); +module_exit(reflash_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Reflash Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_spi.c b/synaptics_tcm/synaptics_tcm_spi.c new file mode 100644 index 0000000000..ee8751ac52 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_spi.c @@ -0,0 +1,670 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include "synaptics_tcm_core.h" + +static unsigned char *buf; + +static unsigned int buf_size; + +static struct spi_transfer *xfer; + +static struct syna_tcm_bus_io bus_io; + +static struct syna_tcm_hw_interface hw_if; + +static struct platform_device *syna_tcm_spi_device; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata) +{ + int retval; + u32 value; + struct property *prop; + struct device_node *np = dev->of_node; + const char *name; + + prop = of_find_property(np, "synaptics,irq-gpio", NULL); + if (prop && prop->length) { + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + } else { + bdata->irq_gpio = -1; + } + + retval = of_property_read_u32(np, "synaptics,irq-on-state", &value); + if (retval < 0) + bdata->irq_on_state = 0; + else + bdata->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name); + if (retval < 0) + bdata->pwr_reg_name = NULL; + else + bdata->pwr_reg_name = name; + + retval = of_property_read_string(np, "synaptics,bus-reg-name", &name); + if (retval < 0) + bdata->bus_reg_name = NULL; + else + bdata->bus_reg_name = name; + + prop = of_find_property(np, "synaptics,power-gpio", NULL); + if (prop && prop->length) { + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + } else { + bdata->power_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + LOGE(dev, + "Failed to read synaptics,power-on-state\n"); + return retval; + } + bdata->power_on_state = value; + } else { + bdata->power_on_state = 0; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,power-delay-ms\n"); + return retval; + } + bdata->power_delay_ms = value; + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + } else { + bdata->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-on-state\n"); + return retval; + } + bdata->reset_on_state = value; + } else { + bdata->reset_on_state = 0; + } + + prop = of_find_property(np, "synaptics,reset-active-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-active-ms\n"); + return retval; + } + bdata->reset_active_ms = value; + } else { + bdata->reset_active_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,reset-delay-ms\n"); + return retval; + } + bdata->reset_delay_ms = value; + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,x-flip", NULL); + bdata->x_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,y-flip", NULL); + bdata->y_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,byte-delay-us", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,byte-delay-us\n"); + return retval; + } + bdata->byte_delay_us = value; + } else { + bdata->byte_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,block-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,block-delay-us", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,block-delay-us\n"); + return retval; + } + bdata->block_delay_us = value; + } else { + bdata->block_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,spi-mode", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,spi-mode", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,spi-mode\n"); + return retval; + } + bdata->spi_mode = value; + + } else { + bdata->spi_mode = 0; + } + + prop = of_find_property(np, "synaptics,ubl-max-freq", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ubl-max-freq", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,ubl-max-freq\n"); + return retval; + } + bdata->ubl_max_freq = value; + } else { + bdata->ubl_max_freq = 0; + } + + prop = of_find_property(np, "synaptics,ubl-byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ubl-byte-delay-us", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,ubl-byte-delay-us\n"); + return retval; + } + bdata->ubl_byte_delay_us = value; + } else { + bdata->ubl_byte_delay_us = 0; + } + + return 0; +} +#endif + +static int syna_tcm_spi_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + unsigned int count, unsigned int size) +{ + static unsigned int xfer_count; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + + if (count > xfer_count) { + kfree(xfer); + xfer = kcalloc(count, sizeof(*xfer), GFP_KERNEL); + if (!xfer) { + LOGE(&spi->dev, + "Failed to allocate memory for xfer\n"); + xfer_count = 0; + return -ENOMEM; + } + xfer_count = count; + } else { + memset(xfer, 0, count * sizeof(*xfer)); + } + + if (size > buf_size) { + if (buf_size) + kfree(buf); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + LOGE(&spi->dev, + "Failed to allocate memory for buf\n"); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + return 0; +} + +static int syna_tcm_spi_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int idx; + unsigned int mode; + unsigned int byte_count; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + byte_count = length + 2; + + if (bdata->ubl_byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 2, byte_count); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, byte_count, 3); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)(addr >> 8) | 0x80; + buf[1] = (unsigned char)addr; + + if (bdata->ubl_byte_delay_us == 0) { + xfer[0].len = 2; + xfer[0].tx_buf = buf; + xfer[0].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[0], &msg); + memset(&buf[2], 0xff, length); + xfer[1].len = length; + xfer[1].tx_buf = &buf[2]; + xfer[1].rx_buf = data; + if (bdata->block_delay_us) + xfer[1].delay_usecs = bdata->block_delay_us; + xfer[1].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[1], &msg); + } else { + buf[2] = 0xff; + for (idx = 0; idx < byte_count; idx++) { + xfer[idx].len = 1; + if (idx < 2) { + xfer[idx].tx_buf = &buf[idx]; + } else { + xfer[idx].tx_buf = &buf[2]; + xfer[idx].rx_buf = &data[idx - 2]; + } + xfer[idx].delay_usecs = bdata->ubl_byte_delay_us; + if (bdata->block_delay_us && (idx == byte_count - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + xfer[idx].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + mode = spi->mode; + spi->mode = SPI_MODE_3; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + + spi->mode = mode; + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int mode; + unsigned int byte_count; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + byte_count = length + 2; + + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, byte_count); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)(addr >> 8) & ~0x80; + buf[1] = (unsigned char)addr; + retval = secure_memcpy(&buf[2], + buf_size - 2, + data, + length, + length); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to copy write data\n"); + goto exit; + } + + xfer[0].len = byte_count; + xfer[0].tx_buf = buf; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + + mode = spi->mode; + spi->mode = SPI_MODE_3; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + + spi->mode = mode; + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + if (bdata->byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, length); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory\n"); + goto exit; + } + + if (bdata->byte_delay_us == 0) { + memset(buf, 0xff, length); + xfer[0].len = length; + xfer[0].tx_buf = buf; + xfer[0].rx_buf = data; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + buf[0] = 0xff; + for (idx = 0; idx < length; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = buf; + xfer[idx].rx_buf = &data[idx]; + xfer[idx].delay_usecs = bdata->byte_delay_us; + if (bdata->block_delay_us && (idx == length - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + if (bdata->byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, 0); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 0); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + if (bdata->byte_delay_us == 0) { + xfer[0].len = length; + xfer[0].tx_buf = data; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + for (idx = 0; idx < length; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = &data[idx]; + xfer[idx].delay_usecs = bdata->byte_delay_us; + if (bdata->block_delay_us && (idx == length - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_probe(struct spi_device *spi) +{ + int retval; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + LOGE(&spi->dev, + "Full duplex not supported by host\n"); + return -EIO; + } + + syna_tcm_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_tcm_spi_device) { + LOGE(&spi->dev, + "Failed to allocate platform device\n"); + return -ENOMEM; + } + +#ifdef CONFIG_OF + hw_if.bdata = devm_kzalloc(&spi->dev, sizeof(*hw_if.bdata), GFP_KERNEL); + if (!hw_if.bdata) { + LOGE(&spi->dev, + "Failed to allocate memory for board data\n"); + return -ENOMEM; + } + parse_dt(&spi->dev, hw_if.bdata); +#else + hw_if.bdata = spi->dev.platform_data; +#endif + + switch (hw_if.bdata->spi_mode) { + case 0: + spi->mode = SPI_MODE_0; + break; + case 1: + spi->mode = SPI_MODE_1; + break; + case 2: + spi->mode = SPI_MODE_2; + break; + case 3: + spi->mode = SPI_MODE_3; + break; + } + + bus_io.type = BUS_SPI; + bus_io.read = syna_tcm_spi_read; + bus_io.write = syna_tcm_spi_write; + bus_io.rmi_read = syna_tcm_spi_rmi_read; + bus_io.rmi_write = syna_tcm_spi_rmi_write; + + hw_if.bus_io = &bus_io; + + spi->bits_per_word = 8; + + retval = spi_setup(spi); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to set up SPI protocol driver\n"); + return retval; + } + + syna_tcm_spi_device->dev.parent = &spi->dev; + syna_tcm_spi_device->dev.platform_data = &hw_if; + + retval = platform_device_add(syna_tcm_spi_device); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to add platform device\n"); + return retval; + } + + return 0; +} + +static int syna_tcm_spi_remove(struct spi_device *spi) +{ + syna_tcm_spi_device->dev.platform_data = NULL; + + platform_device_unregister(syna_tcm_spi_device); + + return 0; +} + +static const struct spi_device_id syna_tcm_id_table[] = { + {SPI_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, syna_tcm_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id syna_tcm_of_match_table[] = { + { + .compatible = "synaptics,tcm-spi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table); +#else +#define syna_tcm_of_match_table NULL +#endif + +static struct spi_driver syna_tcm_spi_driver = { + .driver = { + .name = SPI_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_tcm_of_match_table, + }, + .probe = syna_tcm_spi_probe, + .remove = syna_tcm_spi_remove, + .id_table = syna_tcm_id_table, +}; + +int syna_tcm_bus_init_spi(void) +{ + return spi_register_driver(&syna_tcm_spi_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_init_spi); + +void syna_tcm_bus_exit_spi(void) +{ + kfree(buf); + + kfree(xfer); + + spi_unregister_driver(&syna_tcm_spi_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_exit_spi); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM SPI Bus Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_testing.c b/synaptics_tcm/synaptics_tcm_testing.c new file mode 100644 index 0000000000..b1921f779c --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_testing.c @@ -0,0 +1,1938 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include "synaptics_tcm_core.h" +#include "synaptics_tcm_testing.h" + +#define SYSFS_DIR_NAME "testing" + +#define REPORT_TIMEOUT_MS 500 + +#define testing_sysfs_show(t_name) \ +static ssize_t testing_sysfs_##t_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int retval; \ + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = testing_##t_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to do "#t_name" test\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, \ + "%s\n", \ + testing_hcd->result ? "Passed" : "Failed"); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} + +enum test_code { + TEST_TRX_TRX_SHORTS = 0, + TEST_TRX_SENSOR_OPENS = 1, + TEST_TRX_GROUND_SHORTS = 2, + TEST_DYNAMIC_RANGE = 7, + TEST_OPEN_SHORT_DETECTOR = 8, + TEST_NOISE = 10, + TEST_PT11 = 11, + TEST_PT12 = 12, + TEST_PT13 = 13, + TEST_DYNAMIC_RANGE_DOZE = 14, + TEST_NOISE_DOZE = 15, +}; + +struct testing_hcd { + bool result; + unsigned char report_type; + unsigned int report_index; + unsigned int num_of_reports; + struct kobject *sysfs_dir; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer report; + struct syna_tcm_buffer process; + struct syna_tcm_buffer output; + struct syna_tcm_hcd *tcm_hcd; + int (*collect_reports)(enum report_type report_type, + unsigned int num_of_reports); +}; + +DECLARE_COMPLETION(report_complete); + +DECLARE_COMPLETION(testing_remove_complete); + +static struct testing_hcd *testing_hcd; + +static int testing_dynamic_range(void); + +static int testing_dynamic_range_lpwg(void); + +static int testing_dynamic_range_doze(void); + +static int testing_noise(void); + +static int testing_noise_lpwg(void); + +static int testing_noise_doze(void); + +static int testing_open_short_detector(void); + +static int testing_pt11(void); + +static int testing_pt12(void); + +static int testing_pt13(void); + +static int testing_reset_open(void); + +static int testing_lockdown(void); + +static int testing_trx(enum test_code test_code); + +SHOW_PROTOTYPE(testing, dynamic_range); +SHOW_PROTOTYPE(testing, dynamic_range_lpwg); +SHOW_PROTOTYPE(testing, dynamic_range_doze); +SHOW_PROTOTYPE(testing, noise); +SHOW_PROTOTYPE(testing, noise_lpwg); +SHOW_PROTOTYPE(testing, noise_doze); +SHOW_PROTOTYPE(testing, open_short_detector); +SHOW_PROTOTYPE(testing, pt11); +SHOW_PROTOTYPE(testing, pt12); +SHOW_PROTOTYPE(testing, pt13); +SHOW_PROTOTYPE(testing, reset_open); +SHOW_PROTOTYPE(testing, lockdown); +SHOW_PROTOTYPE(testing, trx_trx_shorts); +SHOW_PROTOTYPE(testing, trx_sensor_opens); +SHOW_PROTOTYPE(testing, trx_ground_shorts); +SHOW_PROTOTYPE(testing, size); + +static struct device_attribute *attrs[] = { + ATTRIFY(dynamic_range), + ATTRIFY(dynamic_range_lpwg), + ATTRIFY(dynamic_range_doze), + ATTRIFY(noise), + ATTRIFY(noise_lpwg), + ATTRIFY(noise_doze), + ATTRIFY(open_short_detector), + ATTRIFY(pt11), + ATTRIFY(pt12), + ATTRIFY(pt13), + ATTRIFY(reset_open), + ATTRIFY(lockdown), + ATTRIFY(trx_trx_shorts), + ATTRIFY(trx_sensor_opens), + ATTRIFY(trx_ground_shorts), + ATTRIFY(size), +}; + +static ssize_t testing_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "data", + .mode = 0444, + }, + .size = 0, + .read = testing_sysfs_data_show, +}; + +testing_sysfs_show(dynamic_range) + +testing_sysfs_show(dynamic_range_lpwg) + +testing_sysfs_show(dynamic_range_doze) + +testing_sysfs_show(noise) + +testing_sysfs_show(noise_lpwg) + +testing_sysfs_show(noise_doze) + +testing_sysfs_show(open_short_detector) + +testing_sysfs_show(pt11) + +testing_sysfs_show(pt12) + +testing_sysfs_show(pt13) + +testing_sysfs_show(reset_open) + +testing_sysfs_show(lockdown) + +static ssize_t testing_sysfs_trx_trx_shorts_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_trx(TEST_TRX_TRX_SHORTS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do TRX-TRX shorts test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, + "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_trx_sensor_opens_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_trx(TEST_TRX_SENSOR_OPENS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do TRX-sensor opens test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, + "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_trx_ground_shorts_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_trx(TEST_TRX_GROUND_SHORTS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do TRX-ground shorts test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, + "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(testing_hcd->output); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + testing_hcd->output.data_length); + + UNLOCK_BUFFER(testing_hcd->output); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(testing_hcd->output); + + readlen = MIN(count, testing_hcd->output.data_length - pos); + + retval = secure_memcpy(buf, + count, + &testing_hcd->output.buf[pos], + testing_hcd->output.buf_size - pos, + readlen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + } else { + retval = readlen; + } + + UNLOCK_BUFFER(testing_hcd->output); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int testing_run_prod_test_item(enum test_code test_code) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (tcm_hcd->features.dual_firmware && + tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_PRODUCTION_TEST); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run production test firmware\n"); + return retval; + } + } else if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return -ENODEV; + } + + LOCK_BUFFER(testing_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->out, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->out.buf\n"); + UNLOCK_BUFFER(testing_hcd->out); + return retval; + } + + testing_hcd->out.buf[0] = test_code; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_PRODUCTION_TEST, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_PRODUCTION_TEST)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + return retval; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + return 0; +} + +static int testing_collect_reports(enum report_type report_type, + unsigned int num_of_reports) +{ + int retval; + bool completed; + unsigned int timeout; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + testing_hcd->report_index = 0; + testing_hcd->report_type = report_type; + testing_hcd->num_of_reports = num_of_reports; + + reinit_completion(&report_complete); + + LOCK_BUFFER(testing_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->out, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->out.buf\n"); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + testing_hcd->out.buf[0] = testing_hcd->report_type; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ENABLE_REPORT, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ENABLE_REPORT)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + completed = false; + timeout = REPORT_TIMEOUT_MS * num_of_reports; + + retval = wait_for_completion_timeout(&report_complete, + msecs_to_jiffies(timeout)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for report collection\n"); + } else { + completed = true; + } + + LOCK_BUFFER(testing_hcd->out); + + testing_hcd->out.buf[0] = testing_hcd->report_type; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DISABLE_REPORT, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DISABLE_REPORT)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + if (completed) + retval = 0; + else + retval = -EIO; + +exit: + testing_hcd->report_type = 0; + + return retval; +} + +static void testing_get_frame_size_words(unsigned int *size, bool image_only) +{ + unsigned int rows; + unsigned int cols; + unsigned int hybrid; + unsigned int buttons; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + hybrid = le2_to_uint(app_info->has_hybrid_data); + buttons = le2_to_uint(app_info->num_of_buttons); + + *size = rows * cols; + + if (!image_only) { + if (hybrid) + *size += rows + cols; + *size += buttons; + } +} + +static void testing_doze_frame_output(unsigned int rows, unsigned int cols) +{ + int retval; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + header_size = 2; + + data_size = rows * cols; + + if (le2_to_uint(app_info->num_of_buttons)) + data_size++; + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.buf[0] = rows; + testing_hcd->output.buf[1] = cols; + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); +} + +static void testing_standard_frame_output(bool image_only) +{ + int retval; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + testing_get_frame_size_words(&data_size, image_only); + + header_size = sizeof(app_info->num_of_buttons) + + sizeof(app_info->num_of_image_rows) + + sizeof(app_info->num_of_image_cols) + + sizeof(app_info->has_hybrid_data); + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + &app_info->num_of_buttons[0], + header_size, + header_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy header data\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_dynamic_range_doze(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int data; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned int limits_rows; + unsigned int limits_cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE_DOZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + data_size = testing_hcd->resp.data_length / 2; + + if (le2_to_uint(app_info->num_of_buttons)) + data_size--; + + if (data_size % cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid max number of rows per burst\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + rows = data_size / cols; + + limits_rows = ARRAY_SIZE(drt_hi_limits); + limits_cols = ARRAY_SIZE(drt_hi_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(drt_lo_limits); + limits_cols = ARRAY_SIZE(drt_lo_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = le2_to_uint(&buf[idx * 2]); + if (data > drt_hi_limits[row][col] || + data < drt_lo_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_doze_frame_output(rows, cols); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_dynamic_range_lpwg(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + return retval; + } + + retval = testing_dynamic_range(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do dynamic range test\n"); + return retval; + } + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } + + return 0; +} + +static int testing_dynamic_range(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int data; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&frame_size_words, false); + + retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(drt_hi_limits); + limits_cols = ARRAY_SIZE(drt_hi_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(drt_lo_limits); + limits_cols = ARRAY_SIZE(drt_lo_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = le2_to_uint(&buf[idx * 2]); + if (data > drt_hi_limits[row][col] || + data < drt_lo_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(false); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_noise_doze(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned int limits_rows; + unsigned int limits_cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = testing_run_prod_test_item(TEST_NOISE_DOZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + data_size = testing_hcd->resp.data_length / 2; + + if (le2_to_uint(app_info->num_of_buttons)) + data_size--; + + if (data_size % cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid max number of rows per burst\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + rows = data_size / cols; + + limits_rows = ARRAY_SIZE(noise_limits); + limits_cols = ARRAY_SIZE(noise_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > noise_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_doze_frame_output(rows, cols); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_noise_lpwg(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + return retval; + } + + retval = testing_noise(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do noise test\n"); + return retval; + } + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } + + return 0; +} + +static int testing_noise(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&frame_size_words, false); + + retval = testing_run_prod_test_item(TEST_NOISE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(noise_limits); + limits_cols = ARRAY_SIZE(noise_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > noise_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(false); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static void testing_open_short_detector_output(void) +{ + int retval; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + data_size = (rows * cols + 7) / 8; + + header_size = sizeof(app_info->num_of_buttons) + + sizeof(app_info->num_of_image_rows) + + sizeof(app_info->num_of_image_cols) + + sizeof(app_info->has_hybrid_data); + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + &app_info->num_of_buttons[0], + header_size, + header_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy header data\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_open_short_detector(void) +{ + int retval; + unsigned int bit; + unsigned int byte; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned char *data; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + data_size = (rows * cols + 7) / 8; + + retval = testing_run_prod_test_item(TEST_OPEN_SHORT_DETECTOR); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (data_size * 2 != testing_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + testing_hcd->result = true; + + bit = 0; + byte = 0; + data = &testing_hcd->resp.buf[0]; + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + if (data[byte] & (1 << bit)) { + testing_hcd->result = false; + break; + } + if (bit++ > 7) { + bit = 0; + byte++; + } + } + } + + if (testing_hcd->result == true) { + bit = 0; + byte = 0; + data = &testing_hcd->resp.buf[data_size]; + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + if (data[byte] & (1 << bit)) { + testing_hcd->result = false; + break; + } + if (bit++ > 7) { + bit = 0; + byte++; + } + } + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_open_short_detector_output(); + + retval = 0; + +exit: + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + + return retval; +} + +static int testing_pt11(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT11); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt11_hi_limits); + limits_cols = ARRAY_SIZE(pt11_hi_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt11_lo_limits); + limits_cols = ARRAY_SIZE(pt11_lo_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > pt11_hi_limits[row][col] || + data < pt11_lo_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(true); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_pt12(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT12); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt12_limits); + limits_cols = ARRAY_SIZE(pt12_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data < pt12_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(true); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_pt13(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT13); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt13_limits); + limits_cols = ARRAY_SIZE(pt13_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data < pt13_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(true); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_reset_open(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->reset_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Hardware reset unavailable\n"); + return -EINVAL; + } + + mutex_lock(&tcm_hcd->reset_mutex); + + tcm_hcd->update_watchdog(tcm_hcd, false); + + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + + tcm_hcd->update_watchdog(tcm_hcd, true); + + mutex_unlock(&tcm_hcd->reset_mutex); + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } else { + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto run_app_firmware; + } + } + + if (tcm_hcd->boot_info.last_reset_reason == reset_open_limit) + testing_hcd->result = true; + else + testing_hcd->result = false; + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + return retval; +} + +static void testing_lockdown_output(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOCK_BUFFER(testing_hcd->output); + LOCK_BUFFER(testing_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output.buf\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.data_length = testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_lockdown(void) +{ + int retval; + unsigned int idx; + unsigned int lockdown_size; + unsigned int limits_size; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (tcm_hcd->read_flash_data == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Unable to read from flash\n"); + return -EINVAL; + } + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->read_flash_data(CUSTOM_OTP, true, &testing_hcd->resp); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read lockdown data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + return retval; + } + + lockdown_size = testing_hcd->resp.data_length; + + limits_size = sizeof(lockdown_limits) / sizeof(*lockdown_limits); + + if (lockdown_size != limits_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + return -EINVAL; + } + + testing_hcd->result = true; + + for (idx = 0; idx < lockdown_size; idx++) { + if (testing_hcd->resp.buf[idx] != lockdown_limits[idx]) { + testing_hcd->result = false; + break; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_lockdown_output(); + + return 0; +} + +static void testing_trx_output(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOCK_BUFFER(testing_hcd->output); + LOCK_BUFFER(testing_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output.buf\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.data_length = testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_trx(enum test_code test_code) +{ + int retval; + unsigned char pass_vector; + unsigned int idx; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + switch (test_code) { + case TEST_TRX_TRX_SHORTS: + case TEST_TRX_GROUND_SHORTS: + pass_vector = 0xff; + break; + case TEST_TRX_SENSOR_OPENS: + pass_vector = 0x00; + break; + default: + return -EINVAL; + } + + retval = testing_run_prod_test_item(test_code); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + testing_hcd->result = true; + + for (idx = 0; idx < testing_hcd->resp.data_length; idx++) { + if (testing_hcd->resp.buf[idx] != pass_vector) { + testing_hcd->result = false; + break; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_trx_output(); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static void testing_report(void) +{ + int retval; + unsigned int offset; + unsigned int report_size; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + report_size = tcm_hcd->report.buffer.data_length; + + LOCK_BUFFER(testing_hcd->report); + + if (testing_hcd->report_index == 0) { + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->report, + report_size * testing_hcd->num_of_reports); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for report.buf\n"); + UNLOCK_BUFFER(testing_hcd->report); + return; + } + } + + if (testing_hcd->report_index < testing_hcd->num_of_reports) { + offset = report_size * testing_hcd->report_index; + + retval = secure_memcpy(testing_hcd->report.buf + offset, + testing_hcd->report.buf_size - offset, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(testing_hcd->report); + return; + } + + testing_hcd->report_index++; + testing_hcd->report.data_length += report_size; + } + + UNLOCK_BUFFER(testing_hcd->report); + + if (testing_hcd->report_index == testing_hcd->num_of_reports) + complete(&report_complete); +} + +static int testing_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + + testing_hcd = kzalloc(sizeof(*testing_hcd), GFP_KERNEL); + if (!testing_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd\n"); + return -ENOMEM; + } + + testing_hcd->tcm_hcd = tcm_hcd; + + testing_hcd->collect_reports = testing_collect_reports; + + INIT_BUFFER(testing_hcd->out, false); + INIT_BUFFER(testing_hcd->resp, false); + INIT_BUFFER(testing_hcd->report, false); + INIT_BUFFER(testing_hcd->process, false); + INIT_BUFFER(testing_hcd->output, false); + + testing_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!testing_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(testing_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(testing_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(testing_hcd->sysfs_dir); + +err_sysfs_create_dir: + RELEASE_BUFFER(testing_hcd->output); + RELEASE_BUFFER(testing_hcd->process); + RELEASE_BUFFER(testing_hcd->report); + RELEASE_BUFFER(testing_hcd->resp); + RELEASE_BUFFER(testing_hcd->out); + + kfree(testing_hcd); + testing_hcd = NULL; + + return retval; +} + +static int testing_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!testing_hcd) + goto exit; + + sysfs_remove_bin_file(testing_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(testing_hcd->sysfs_dir); + + RELEASE_BUFFER(testing_hcd->output); + RELEASE_BUFFER(testing_hcd->process); + RELEASE_BUFFER(testing_hcd->report); + RELEASE_BUFFER(testing_hcd->resp); + RELEASE_BUFFER(testing_hcd->out); + + kfree(testing_hcd); + testing_hcd = NULL; + +exit: + complete(&testing_remove_complete); + + return 0; +} + +static int testing_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!testing_hcd) { + retval = testing_init(tcm_hcd); + return retval; + } + + return 0; +} + +static int testing_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!testing_hcd) + return 0; + + if (tcm_hcd->report.id == testing_hcd->report_type) + testing_report(); + + return 0; +} + +static struct syna_tcm_module_cb testing_module = { + .type = TCM_TESTING, + .init = testing_init, + .remove = testing_remove, + .syncbox = testing_syncbox, + .asyncbox = NULL, + .reset = testing_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init testing_module_init(void) +{ + return syna_tcm_add_module(&testing_module, true); +} + +static void __exit testing_module_exit(void) +{ + syna_tcm_add_module(&testing_module, false); + + wait_for_completion(&testing_remove_complete); +} + +module_init(testing_module_init); +module_exit(testing_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Testing Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_testing.h b/synaptics_tcm/synaptics_tcm_testing.h new file mode 100644 index 0000000000..c5a39a5896 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_testing.h @@ -0,0 +1,85 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_TESTING_H_ +#define _SYNAPTICS_TCM_TESTING_H_ + +static const unsigned short drt_hi_limits[32][32] = { + {0}, +}; + +static const unsigned short drt_lo_limits[32][32] = { + {0,}, +}; + +static const unsigned short noise_limits[32][32] = { + {0,}, +}; + +static const short pt11_hi_limits[32][32] = { + {0,}, +}; + +static const short pt11_lo_limits[32][32] = { + {0,}, +}; + +static const short pt12_limits[32][32] = { + {0,}, +}; + +static const short pt13_limits[32][32] = { + {0,}, +}; + +static const unsigned char lockdown_limits[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const unsigned char reset_open_limit = 0x13; + +#endif diff --git a/synaptics_tcm/synaptics_tcm_touch.c b/synaptics_tcm/synaptics_tcm_touch.c new file mode 100644 index 0000000000..fbc3b28a02 --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_touch.c @@ -0,0 +1,1272 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include "synaptics_tcm_core.h" + +#define TYPE_B_PROTOCOL + +#define USE_DEFAULT_TOUCH_REPORT_CONFIG + +#define TOUCH_REPORT_CONFIG_SIZE 128 + +enum touch_status { + LIFT = 0, + FINGER = 1, + GLOVED_FINGER = 2, + NOP = -1, +}; + +enum touch_report_code { + TOUCH_END = 0, + TOUCH_FOREACH_ACTIVE_OBJECT, + TOUCH_FOREACH_OBJECT, + TOUCH_FOREACH_END, + TOUCH_PAD_TO_NEXT_BYTE, + TOUCH_TIMESTAMP, + TOUCH_OBJECT_N_INDEX, + TOUCH_OBJECT_N_CLASSIFICATION, + TOUCH_OBJECT_N_X_POSITION, + TOUCH_OBJECT_N_Y_POSITION, + TOUCH_OBJECT_N_Z, + TOUCH_OBJECT_N_X_WIDTH, + TOUCH_OBJECT_N_Y_WIDTH, + TOUCH_OBJECT_N_TX_POSITION_TIXELS, + TOUCH_OBJECT_N_RX_POSITION_TIXELS, + TOUCH_0D_BUTTONS_STATE, + TOUCH_GESTURE_DOUBLE_TAP, + TOUCH_FRAME_RATE, + TOUCH_POWER_IM, + TOUCH_CID_IM, + TOUCH_RAIL_IM, + TOUCH_CID_VARIANCE_IM, + TOUCH_NSM_FREQUENCY, + TOUCH_NSM_STATE, + TOUCH_NUM_OF_ACTIVE_OBJECTS, + TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME, + TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80, + TOUCH_TUNING_SMALL_OBJECT_PARAMS, + TOUCH_TUNING_0D_BUTTONS_VARIANCE, +}; + +struct object_data { + unsigned char status; + unsigned int x_pos; + unsigned int y_pos; + unsigned int x_width; + unsigned int y_width; + unsigned int z; + unsigned int tx_pos; + unsigned int rx_pos; +}; + +struct input_params { + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; +}; + +struct touch_data { + struct object_data *object_data; + unsigned int timestamp; + unsigned int buttons_state; + unsigned int gesture_double_tap; + unsigned int frame_rate; + unsigned int power_im; + unsigned int cid_im; + unsigned int rail_im; + unsigned int cid_variance_im; + unsigned int nsm_frequency; + unsigned int nsm_state; + unsigned int num_of_active_objects; + unsigned int num_of_cpu_cycles; +}; + +struct touch_hcd { + bool irq_wake; + bool report_touch; + bool suspend_touch; + unsigned char *prev_status; + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; + struct mutex report_mutex; + struct input_dev *input_dev; + struct touch_data touch_data; + struct input_params input_params; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(touch_remove_complete); + +static struct touch_hcd *touch_hcd; + +/** + * touch_free_objects() - Free all touch objects + * + * Report finger lift events to the input subsystem for all touch objects. + */ +static void touch_free_objects(void) +{ +#ifdef TYPE_B_PROTOCOL + unsigned int idx; +#endif + + if (touch_hcd->input_dev == NULL) + return; + + mutex_lock(&touch_hcd->report_mutex); + +#ifdef TYPE_B_PROTOCOL + for (idx = 0; idx < touch_hcd->max_objects; idx++) { + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 0); + } +#endif + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 0); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + input_sync(touch_hcd->input_dev); + + mutex_unlock(&touch_hcd->report_mutex); +} + +/** + * touch_get_report_data() - Retrieve data from touch report + * + * Retrieve data from the touch report based on the bit offset and bit length + * information from the touch report configuration. + */ +static int touch_get_report_data(unsigned int offset, + unsigned int bits, unsigned int *data) +{ + unsigned char mask; + unsigned char byte_data; + unsigned int output_data; + unsigned int bit_offset; + unsigned int byte_offset; + unsigned int data_bits; + unsigned int available_bits; + unsigned int remaining_bits; + unsigned char *touch_report; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (bits == 0 || bits > 32) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid number of bits\n"); + return -EINVAL; + } + + if (offset + bits > tcm_hcd->report.buffer.data_length * 8) { + *data = 0; + return 0; + } + + touch_report = tcm_hcd->report.buffer.buf; + + output_data = 0; + remaining_bits = bits; + + bit_offset = offset % 8; + byte_offset = offset / 8; + + while (remaining_bits) { + byte_data = touch_report[byte_offset]; + byte_data >>= bit_offset; + + available_bits = 8 - bit_offset; + data_bits = MIN(available_bits, remaining_bits); + mask = 0xff >> (8 - data_bits); + + byte_data &= mask; + + output_data |= byte_data << (bits - remaining_bits); + + bit_offset = 0; + byte_offset += 1; + remaining_bits -= data_bits; + } + + *data = output_data; + + return 0; +} + +/** + * touch_parse_report() - Parse touch report + * + * Traverse through the touch report configuration and parse the touch report + * generated by the device accordingly to retrieve the touch data. + */ +static int touch_parse_report(void) +{ + int retval; + bool active_only; + bool num_of_active_objects; + unsigned char code; + unsigned int size; + unsigned int idx; + unsigned int obj; + unsigned int next; + unsigned int data; + unsigned int bits; + unsigned int offset; + unsigned int objects; + unsigned int active_objects; + unsigned int report_size; + unsigned int config_size; + unsigned char *config_data; + struct touch_data *touch_data; + struct object_data *object_data; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + static unsigned int end_of_foreach; + + touch_data = &touch_hcd->touch_data; + object_data = touch_hcd->touch_data.object_data; + + config_data = tcm_hcd->config.buf; + config_size = tcm_hcd->config.data_length; + + report_size = tcm_hcd->report.buffer.data_length; + + size = sizeof(*object_data) * touch_hcd->max_objects; + memset(touch_hcd->touch_data.object_data, 0x00, size); + + num_of_active_objects = false; + + idx = 0; + offset = 0; + objects = 0; + while (idx < config_size) { + code = config_data[idx++]; + switch (code) { + case TOUCH_END: + goto exit; + case TOUCH_FOREACH_ACTIVE_OBJECT: + obj = 0; + next = idx; + active_only = true; + break; + case TOUCH_FOREACH_OBJECT: + obj = 0; + next = idx; + active_only = false; + break; + case TOUCH_FOREACH_END: + end_of_foreach = idx; + if (active_only) { + if (num_of_active_objects) { + objects++; + if (objects < active_objects) + idx = next; + } else if (offset < report_size * 8) { + idx = next; + } + } else { + obj++; + if (obj < touch_hcd->max_objects) + idx = next; + } + break; + case TOUCH_PAD_TO_NEXT_BYTE: + offset = ceil_div(offset, 8) * 8; + break; + case TOUCH_TIMESTAMP: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get timestamp\n"); + return retval; + } + touch_data->timestamp = data; + offset += bits; + break; + case TOUCH_OBJECT_N_INDEX: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &obj); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object index\n"); + return retval; + } + offset += bits; + break; + case TOUCH_OBJECT_N_CLASSIFICATION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get classification data\n"); + return retval; + } + object_data[obj].status = data; + offset += bits; + break; + case TOUCH_OBJECT_N_X_POSITION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object x position\n"); + return retval; + } + object_data[obj].x_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Y_POSITION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object y position\n"); + return retval; + } + object_data[obj].y_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Z: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object z\n"); + return retval; + } + object_data[obj].z = data; + offset += bits; + break; + case TOUCH_OBJECT_N_X_WIDTH: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object x width\n"); + return retval; + } + object_data[obj].x_width = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Y_WIDTH: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object y width\n"); + return retval; + } + object_data[obj].y_width = data; + offset += bits; + break; + case TOUCH_OBJECT_N_TX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object tx position\n"); + return retval; + } + object_data[obj].tx_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_RX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object rx position\n"); + return retval; + } + object_data[obj].rx_pos = data; + offset += bits; + break; + case TOUCH_0D_BUTTONS_STATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get 0D buttons state\n"); + return retval; + } + touch_data->buttons_state = data; + offset += bits; + break; + case TOUCH_GESTURE_DOUBLE_TAP: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get gesture double tap\n"); + return retval; + } + touch_data->gesture_double_tap = data; + offset += bits; + break; + case TOUCH_FRAME_RATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get frame rate\n"); + return retval; + } + touch_data->frame_rate = data; + offset += bits; + break; + case TOUCH_POWER_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get power IM\n"); + return retval; + } + touch_data->power_im = data; + offset += bits; + break; + case TOUCH_CID_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get CID IM\n"); + return retval; + } + touch_data->cid_im = data; + offset += bits; + break; + case TOUCH_RAIL_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get rail IM\n"); + return retval; + } + touch_data->rail_im = data; + offset += bits; + break; + case TOUCH_CID_VARIANCE_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get CID variance IM\n"); + return retval; + } + touch_data->cid_variance_im = data; + offset += bits; + break; + case TOUCH_NSM_FREQUENCY: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get NSM frequency\n"); + return retval; + } + touch_data->nsm_frequency = data; + offset += bits; + break; + case TOUCH_NSM_STATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get NSM state\n"); + return retval; + } + touch_data->nsm_state = data; + offset += bits; + break; + case TOUCH_NUM_OF_ACTIVE_OBJECTS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get number of objects\n"); + return retval; + } + active_objects = data; + num_of_active_objects = true; + touch_data->num_of_active_objects = data; + offset += bits; + if (touch_data->num_of_active_objects == 0) + idx = end_of_foreach; + break; + case TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get number of CPU cycles\n"); + return retval; + } + touch_data->num_of_cpu_cycles = data; + offset += bits; + break; + case TOUCH_TUNING_GAUSSIAN_WIDTHS: + bits = config_data[idx++]; + offset += bits; + break; + case TOUCH_TUNING_SMALL_OBJECT_PARAMS: + bits = config_data[idx++]; + offset += bits; + break; + case TOUCH_TUNING_0D_BUTTONS_VARIANCE: + bits = config_data[idx++]; + offset += bits; + break; + } + } + +exit: + return 0; +} + +/** + * touch_report() - Report touch events + * + * Retrieve data from the touch report generated by the device and report touch + * events to the input subsystem. + */ +static void touch_report(void) +{ + int retval; + unsigned int idx; + unsigned int x; + unsigned int y; + unsigned int temp; + unsigned int status; + unsigned int touch_count; + struct touch_data *touch_data; + struct object_data *object_data; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!touch_hcd->report_touch) + return; + + if (touch_hcd->input_dev == NULL) + return; + + mutex_lock(&touch_hcd->report_mutex); + + retval = touch_parse_report(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse touch report\n"); + goto exit; + } + + touch_data = &touch_hcd->touch_data; + object_data = touch_hcd->touch_data.object_data; + +#ifdef WAKEUP_GESTURE + if (touch_data->gesture_double_tap && tcm_hcd->in_suspend) { + input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 1); + input_sync(touch_hcd->input_dev); + input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 0); + input_sync(touch_hcd->input_dev); + } +#endif + + if (tcm_hcd->in_suspend) + goto exit; + + touch_count = 0; + + for (idx = 0; idx < touch_hcd->max_objects; idx++) { + if (touch_hcd->prev_status[idx] == LIFT && + object_data[idx].status == LIFT) + status = NOP; + else + status = object_data[idx].status; + + switch (status) { + case LIFT: +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 0); +#endif + break; + case FINGER: + case GLOVED_FINGER: + x = object_data[idx].x_pos; + y = object_data[idx].y_pos; + if (bdata->swap_axes) { + temp = x; + x = y; + y = temp; + } + if (bdata->x_flip) + x = touch_hcd->input_params.max_x - x; + if (bdata->y_flip) + y = touch_hcd->input_params.max_y - y; +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 1); +#endif + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 1); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(touch_hcd->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(touch_hcd->input_dev, + ABS_MT_POSITION_Y, y); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + LOGD(tcm_hcd->pdev->dev.parent, + "Finger %d: x = %d, y = %d\n", + idx, x, y); + touch_count++; + break; + default: + break; + } + + touch_hcd->prev_status[idx] = object_data[idx].status; + } + + if (touch_count == 0) { + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 0); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + } + + input_sync(touch_hcd->input_dev); + +exit: + mutex_unlock(&touch_hcd->report_mutex); +} + +/** + * touch_set_input_params() - Set input parameters + * + * Set the input parameters of the input device based on the information + * retrieved from the application information packet. In addition, set up an + * array for tracking the status of touch objects. + */ +static int touch_set_input_params(void) +{ + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_POSITION_X, 0, touch_hcd->max_x, 0, 0); + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_POSITION_Y, 0, touch_hcd->max_y, 0, 0); + + input_mt_init_slots(touch_hcd->input_dev, touch_hcd->max_objects, + INPUT_MT_DIRECT); + + touch_hcd->input_params.max_x = touch_hcd->max_x; + touch_hcd->input_params.max_y = touch_hcd->max_y; + touch_hcd->input_params.max_objects = touch_hcd->max_objects; + + if (touch_hcd->max_objects == 0) + return 0; + + kfree(touch_hcd->prev_status); + touch_hcd->prev_status = kzalloc(touch_hcd->max_objects, GFP_KERNEL); + if (!touch_hcd->prev_status) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for prev_status\n"); + return -ENOMEM; + } + + return 0; +} + +/** + * touch_get_input_params() - Get input parameters + * + * Retrieve the input parameters to register with the input subsystem for + * the input device from the application information packet. In addition, + * the touch report configuration is retrieved and stored. + */ +static int touch_get_input_params(void) +{ + int retval; + unsigned int temp; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + app_info = &tcm_hcd->app_info; + touch_hcd->max_x = le2_to_uint(app_info->max_x); + touch_hcd->max_y = le2_to_uint(app_info->max_y); + touch_hcd->max_objects = le2_to_uint(app_info->max_objects); + + if (bdata->swap_axes) { + temp = touch_hcd->max_x; + touch_hcd->max_x = touch_hcd->max_y; + touch_hcd->max_y = temp; + } + + LOCK_BUFFER(tcm_hcd->config); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_TOUCH_REPORT_CONFIG, + NULL, + 0, + &tcm_hcd->config.buf, + &tcm_hcd->config.buf_size, + &tcm_hcd->config.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_TOUCH_REPORT_CONFIG)); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + UNLOCK_BUFFER(tcm_hcd->config); + + return 0; +} + +/** + * touch_set_input_dev() - Set up input device + * + * Allocate an input device, configure the input device based on the particular + * input events to be reported, and register the input device with the input + * subsystem. + */ +static int touch_set_input_dev(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + touch_hcd->input_dev = input_allocate_device(); + if (touch_hcd->input_dev == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate input device\n"); + return -ENODEV; + } + + touch_hcd->input_dev->name = TOUCH_INPUT_NAME; + touch_hcd->input_dev->phys = TOUCH_INPUT_PHYS_PATH; + touch_hcd->input_dev->id.product = SYNAPTICS_TCM_ID_PRODUCT; + touch_hcd->input_dev->id.version = SYNAPTICS_TCM_ID_VERSION; + touch_hcd->input_dev->dev.parent = tcm_hcd->pdev->dev.parent; + input_set_drvdata(touch_hcd->input_dev, tcm_hcd); + + set_bit(EV_SYN, touch_hcd->input_dev->evbit); + set_bit(EV_KEY, touch_hcd->input_dev->evbit); + set_bit(EV_ABS, touch_hcd->input_dev->evbit); + set_bit(BTN_TOUCH, touch_hcd->input_dev->keybit); + set_bit(BTN_TOOL_FINGER, touch_hcd->input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, touch_hcd->input_dev->propbit); +#endif + +#ifdef WAKEUP_GESTURE + set_bit(KEY_WAKEUP, touch_hcd->input_dev->keybit); + input_set_capability(touch_hcd->input_dev, EV_KEY, KEY_WAKEUP); +#endif + + retval = touch_set_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set input parameters\n"); + input_free_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + return retval; + } + + retval = input_register_device(touch_hcd->input_dev); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register input device\n"); + input_free_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + return retval; + } + + return 0; +} + +/** + * touch_set_report_config() - Set touch report configuration + * + * Send the SET_TOUCH_REPORT_CONFIG command to configure the format and content + * of the touch report. + */ +static int touch_set_report_config(void) +{ + int retval; + unsigned int idx; + unsigned int length; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!bdata->extend_report) + return 0; + + app_info = &tcm_hcd->app_info; + length = le2_to_uint(app_info->max_touch_report_config_size); + + if (length < TOUCH_REPORT_CONFIG_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid maximum touch report config size\n"); + return -EINVAL; + } + + LOCK_BUFFER(touch_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &touch_hcd->out, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd->out.buf\n"); + UNLOCK_BUFFER(touch_hcd->out); + return retval; + } + + idx = 0; +#ifdef WAKEUP_GESTURE + touch_hcd->out.buf[idx++] = TOUCH_GESTURE_DOUBLE_TAP; + touch_hcd->out.buf[idx++] = 8; +#endif + touch_hcd->out.buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_INDEX; + touch_hcd->out.buf[idx++] = 4; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION; + touch_hcd->out.buf[idx++] = 4; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_X_POSITION; + touch_hcd->out.buf[idx++] = 12; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_Y_POSITION; + touch_hcd->out.buf[idx++] = 12; + touch_hcd->out.buf[idx++] = TOUCH_FOREACH_END; + touch_hcd->out.buf[idx++] = TOUCH_END; + + LOCK_BUFFER(touch_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SET_TOUCH_REPORT_CONFIG, + touch_hcd->out.buf, + length, + &touch_hcd->resp.buf, + &touch_hcd->resp.buf_size, + &touch_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_SET_TOUCH_REPORT_CONFIG)); + UNLOCK_BUFFER(touch_hcd->resp); + UNLOCK_BUFFER(touch_hcd->out); + return retval; + } + + UNLOCK_BUFFER(touch_hcd->resp); + UNLOCK_BUFFER(touch_hcd->out); + + return 0; +} + +/** + * touch_check_input_params() - Check input parameters + * + * Check if any of the input parameters registered with the input subsystem for + * the input device has changed. + */ +static int touch_check_input_params(void) +{ + unsigned int size; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (touch_hcd->max_x == 0 && touch_hcd->max_y == 0) + return 0; + + if (touch_hcd->input_params.max_objects != touch_hcd->max_objects) { + kfree(touch_hcd->touch_data.object_data); + size = sizeof(*touch_hcd->touch_data.object_data); + size *= touch_hcd->max_objects; + touch_hcd->touch_data.object_data = kzalloc(size, GFP_KERNEL); + if (!touch_hcd->touch_data.object_data) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for object_data\n"); + return -ENOMEM; + } + return 1; + } + + if (touch_hcd->input_params.max_x != touch_hcd->max_x) + return 1; + + if (touch_hcd->input_params.max_y != touch_hcd->max_y) + return 1; + + return 0; +} + +/** + * touch_set_input_reporting() - Configure touch report and set up new input + * device if necessary + * + * After a device reset event, configure the touch report and set up a new input + * device if any of the input parameters has changed after the device reset. + */ +static int touch_set_input_reporting(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return 0; + } + + touch_hcd->report_touch = false; + + touch_free_objects(); + + mutex_lock(&touch_hcd->report_mutex); + + retval = touch_set_report_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set report config\n"); + goto exit; + } + + retval = touch_get_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get input parameters\n"); + goto exit; + } + + retval = touch_check_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check input parameters\n"); + goto exit; + } else if (retval == 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Input parameters unchanged\n"); + goto exit; + } + + if (touch_hcd->input_dev != NULL) { + input_unregister_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + } + + retval = touch_set_input_dev(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input device\n"); + goto exit; + } + +exit: + mutex_unlock(&touch_hcd->report_mutex); + + touch_hcd->report_touch = retval < 0 ? false : true; + + return retval; +} + +static int touch_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + touch_hcd = kzalloc(sizeof(*touch_hcd), GFP_KERNEL); + if (!touch_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd\n"); + return -ENOMEM; + } + + touch_hcd->tcm_hcd = tcm_hcd; + + mutex_init(&touch_hcd->report_mutex); + + INIT_BUFFER(touch_hcd->out, false); + INIT_BUFFER(touch_hcd->resp, false); + + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + goto err_set_input_reporting; + } + + tcm_hcd->report_touch = touch_report; + + return 0; + +err_set_input_reporting: + kfree(touch_hcd->touch_data.object_data); + kfree(touch_hcd->prev_status); + + RELEASE_BUFFER(touch_hcd->resp); + RELEASE_BUFFER(touch_hcd->out); + + kfree(touch_hcd); + touch_hcd = NULL; + + return retval; +} + +static int touch_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + goto exit; + + tcm_hcd->report_touch = NULL; + + if (touch_hcd->input_dev) + input_unregister_device(touch_hcd->input_dev); + + kfree(touch_hcd->touch_data.object_data); + kfree(touch_hcd->prev_status); + + RELEASE_BUFFER(touch_hcd->resp); + RELEASE_BUFFER(touch_hcd->out); + + kfree(touch_hcd); + touch_hcd = NULL; + +exit: + complete(&touch_remove_complete); + + return 0; +} + +static int touch_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + return 0; + + switch (tcm_hcd->report.id) { + case REPORT_IDENTIFY: + touch_free_objects(); + break; + case REPORT_TOUCH: + if (!touch_hcd->suspend_touch) + touch_report(); + break; + default: + break; + } + + return 0; +} + +static int touch_asyncbox(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!touch_hcd) + return 0; + + switch (tcm_hcd->async_report_id) { + case REPORT_IDENTIFY: + if (tcm_hcd->id_info.mode != MODE_APPLICATION) + break; + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + return retval; + } + break; + default: + break; + } + + return 0; +} + +static int touch_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!touch_hcd) { + retval = touch_init(tcm_hcd); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + return retval; + } + } + + return 0; +} + +static int touch_early_suspend(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + return 0; + +#ifdef WAKEUP_GESTURE + touch_hcd->suspend_touch = false; +#else + touch_hcd->suspend_touch = true; +#endif + + touch_free_objects(); + + return 0; +} + +static int touch_suspend(struct syna_tcm_hcd *tcm_hcd) +{ +#ifdef WAKEUP_GESTURE + int retval; +#endif + + if (!touch_hcd) + return 0; + + touch_hcd->suspend_touch = true; + + touch_free_objects(); + +#ifdef WAKEUP_GESTURE + if (!touch_hcd->irq_wake) { + enable_irq_wake(tcm_hcd->irq); + touch_hcd->irq_wake = true; + } + + touch_hcd->suspend_touch = false; + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + return retval; + } +#endif + + return 0; +} + +static int touch_resume(struct syna_tcm_hcd *tcm_hcd) +{ +#ifdef WAKEUP_GESTURE + int retval; +#endif + + if (!touch_hcd) + return 0; + + touch_hcd->suspend_touch = false; + +#ifdef WAKEUP_GESTURE + if (touch_hcd->irq_wake) { + disable_irq_wake(tcm_hcd->irq); + touch_hcd->irq_wake = false; + } + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } +#endif + + return 0; +} + +static struct syna_tcm_module_cb touch_module = { + .type = TCM_TOUCH, + .init = touch_init, + .remove = touch_remove, + .syncbox = touch_syncbox, + .asyncbox = touch_asyncbox, + .reset = touch_reset, + .suspend = touch_suspend, + .resume = touch_resume, + .early_suspend = touch_early_suspend, +}; + +static int __init touch_module_init(void) +{ + return syna_tcm_add_module(&touch_module, true); +} + +static void __exit touch_module_exit(void) +{ + syna_tcm_add_module(&touch_module, false); + + wait_for_completion(&touch_remove_complete); +} + +module_init(touch_module_init); +module_exit(touch_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Touch Module"); +MODULE_LICENSE("GPL v2"); diff --git a/synaptics_tcm/synaptics_tcm_zeroflash.c b/synaptics_tcm/synaptics_tcm_zeroflash.c new file mode 100644 index 0000000000..5e8295477c --- /dev/null +++ b/synaptics_tcm/synaptics_tcm_zeroflash.c @@ -0,0 +1,1012 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include +#include "synaptics_tcm_core.h" + +#define FW_IMAGE_NAME "synaptics/hdl_firmware.img" + +#define BOOT_CONFIG_ID "BOOT_CONFIG" + +#define F35_APP_CODE_ID "F35_APP_CODE" + +#define APP_CONFIG_ID "APP_CONFIG" + +#define DISP_CONFIG_ID "DISPLAY" + +#define IMAGE_FILE_MAGIC_VALUE 0x4818472b + +#define FLASH_AREA_MAGIC_VALUE 0x7c05e516 + +#define PDT_START_ADDR 0x00e9 + +#define PDT_END_ADDR 0x00ee + +#define UBL_FN_NUMBER 0x35 + +#define F35_CTRL3_OFFSET 18 + +#define F35_CTRL7_OFFSET 22 + +#define F35_WRITE_FW_TO_PMEM_COMMAND 4 + +#define RESET_TO_HDL_DELAY_MS 12 + +#define DOWNLOAD_RETRY_COUNT 10 + +enum f35_error_code { + SUCCESS = 0, + UNKNOWN_FLASH_PRESENT, + MAGIC_NUMBER_NOT_PRESENT, + INVALID_BLOCK_NUMBER, + BLOCK_NOT_ERASED, + NO_FLASH_PRESENT, + CHECKSUM_FAILURE, + WRITE_FAILURE, + INVALID_COMMAND, + IN_DEBUG_MODE, + INVALID_HEADER, + REQUESTING_FIRMWARE, + INVALID_CONFIGURATION, + DISABLE_BLOCK_PROTECT_FAILURE, +}; + +enum config_download { + HDL_INVALID = 0, + HDL_TOUCH_CONFIG, + HDL_DISPLAY_CONFIG, + HDL_DISPLAY_CONFIG_TO_RAM, +}; + +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; + +struct block_data { + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; +}; + +struct image_info { + unsigned int packrat_number; + struct block_data boot_config; + struct block_data app_firmware; + struct block_data app_config; + struct block_data disp_config; +}; + +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; + +struct rmi_f35_query { + unsigned char version:4; + unsigned char has_debug_mode:1; + unsigned char has_data5:1; + unsigned char has_query1:1; + unsigned char has_query2:1; + unsigned char chunk_size; + unsigned char has_ctrl7:1; + unsigned char has_host_download:1; + unsigned char has_spi_master:1; + unsigned char advanced_recovery_mode:1; + unsigned char reserved:4; +} __packed; + +struct rmi_f35_data { + unsigned char error_code:5; + unsigned char recovery_mode_forced:1; + unsigned char nvm_programmed:1; + unsigned char in_recovery:1; +} __packed; + +struct rmi_pdt_entry { + unsigned char query_base_addr; + unsigned char command_base_addr; + unsigned char control_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count:3; + unsigned char reserved_1:2; + unsigned char fn_version:2; + unsigned char reserved_2:1; + unsigned char fn_number; +} __packed; + +struct rmi_addr { + unsigned short query_base; + unsigned short command_base; + unsigned short control_base; + unsigned short data_base; +}; + +struct firmware_status { + unsigned short invalid_static_config:1; + unsigned short need_disp_config:1; + unsigned short need_app_config:1; + unsigned short hdl_version:4; + unsigned short reserved:9; +} __packed; + +struct zeroflash_hcd { + bool has_hdl; + bool f35_ready; + const unsigned char *image; + unsigned char *buf; + const struct firmware *fw_entry; + struct work_struct config_work; + struct work_struct firmware_work; + struct workqueue_struct *workqueue; + struct rmi_addr f35_addr; + struct image_info image_info; + struct firmware_status fw_status; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(zeroflash_remove_complete); + +static struct zeroflash_hcd *zeroflash_hcd; + +static int zeroflash_check_uboot(void) +{ + int retval; + unsigned char fn_number; + struct rmi_f35_query query; + struct rmi_pdt_entry p_entry; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_END_ADDR, + &fn_number, + sizeof(fn_number)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read RMI function number\n"); + return retval; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Found F$%02x\n", + fn_number); + + if (fn_number != UBL_FN_NUMBER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to find F$35\n"); + return -ENODEV; + } + + if (zeroflash_hcd->f35_ready) + return 0; + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_START_ADDR, + (unsigned char *)&p_entry, + sizeof(p_entry)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read PDT entry\n"); + return retval; + } + + zeroflash_hcd->f35_addr.query_base = p_entry.query_base_addr; + zeroflash_hcd->f35_addr.command_base = p_entry.command_base_addr; + zeroflash_hcd->f35_addr.control_base = p_entry.control_base_addr; + zeroflash_hcd->f35_addr.data_base = p_entry.data_base_addr; + + retval = syna_tcm_rmi_read(tcm_hcd, + zeroflash_hcd->f35_addr.query_base, + (unsigned char *)&query, + sizeof(query)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read F$35 query\n"); + return retval; + } + + zeroflash_hcd->f35_ready = true; + + if (query.has_query2 && query.has_ctrl7 && query.has_host_download) { + zeroflash_hcd->has_hdl = true; + } else { + LOGE(tcm_hcd->pdev->dev.parent, + "Host download not supported\n"); + zeroflash_hcd->has_hdl = false; + return -ENODEV; + } + + return 0; +} + +static int zeroflash_parse_fw_image(void) +{ + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct image_info *image_info; + struct area_descriptor *descriptor; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + const unsigned char *image; + const unsigned char *content; + + image = zeroflash_hcd->image; + image_info = &zeroflash_hcd->image_info; + header = (struct image_header *)image; + + magic_value = le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid image file magic value\n"); + return -EINVAL; + } + + memset(image_info, 0x00, sizeof(*image_info)); + + offset = sizeof(*header); + num_of_areas = le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2; + checksum = le4_to_uint(descriptor->checksum); + + if (!memcmp((char *)descriptor->id_string, + BOOT_CONFIG_ID, + strlen(BOOT_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config checksum error\n"); + return -EINVAL; + } + image_info->boot_config.size = length; + image_info->boot_config.data = content; + image_info->boot_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + F35_APP_CODE_ID, + strlen(F35_APP_CODE_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "APP firmware checksum error\n"); + return -EINVAL; + } + image_info->app_firmware.size = length; + image_info->app_firmware.data = content; + image_info->app_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + APP_CONFIG_ID, + strlen(APP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application config checksum error\n"); + return -EINVAL; + } + image_info->app_config.size = length; + image_info->app_config.data = content; + image_info->app_config.flash_addr = flash_addr; + image_info->packrat_number = le4_to_uint(&content[14]); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + DISP_CONFIG_ID, + strlen(DISP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Display config checksum error\n"); + return -EINVAL; + } + image_info->disp_config.size = length; + image_info->disp_config.data = content; + image_info->disp_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Display config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Display config flash address = 0x%08x\n", + flash_addr); + } + } + + return 0; +} + +static int zeroflash_get_fw_image(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + if (zeroflash_hcd->fw_entry != NULL) + return 0; + + do { + retval = request_firmware(&zeroflash_hcd->fw_entry, + FW_IMAGE_NAME, + tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to request %s\n", + FW_IMAGE_NAME); + msleep(100); + } else { + break; + } + } while (1); + + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware image size = %d\n", + (unsigned int)zeroflash_hcd->fw_entry->size); + + zeroflash_hcd->image = zeroflash_hcd->fw_entry->data; + + retval = zeroflash_parse_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse firmware image\n"); + release_firmware(zeroflash_hcd->fw_entry); + zeroflash_hcd->fw_entry = NULL; + zeroflash_hcd->image = NULL; + return retval; + } + + return 0; +} + +static void zeroflash_download_config(void) +{ + struct firmware_status *fw_status; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + fw_status = &zeroflash_hcd->fw_status; + + if (!fw_status->need_app_config && !fw_status->need_disp_config) { + if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_SEND_RESET_NOTIFICATION); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + } + atomic_set(&tcm_hcd->host_downloading, 0); + return; + } + + queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->config_work); +} + +static void zeroflash_download_firmware(void) +{ + queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->firmware_work); +} + +static int zeroflash_download_disp_config(void) +{ + int retval; + unsigned char response_code; + struct image_info *image_info; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + static unsigned int retry_count; + + LOGN(tcm_hcd->pdev->dev.parent, + "Downloading display config\n"); + + image_info = &zeroflash_hcd->image_info; + + if (image_info->disp_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No display config in image file\n"); + return -EINVAL; + } + + LOCK_BUFFER(zeroflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &zeroflash_hcd->out, + image_info->disp_config.size + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + goto unlock_out; + } + + switch (zeroflash_hcd->fw_status.hdl_version) { + case 0: + zeroflash_hcd->out.buf[0] = 1; + break; + case 1: + zeroflash_hcd->out.buf[0] = 2; + break; + default: + retval = -EINVAL; + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid HDL version (%d)\n", + zeroflash_hcd->fw_status.hdl_version); + goto unlock_out; + } + + zeroflash_hcd->out.buf[1] = HDL_DISPLAY_CONFIG; + + retval = secure_memcpy(&zeroflash_hcd->out.buf[2], + zeroflash_hcd->out.buf_size - 2, + image_info->disp_config.data, + image_info->disp_config.size, + image_info->disp_config.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy display config data\n"); + goto unlock_out; + } + + zeroflash_hcd->out.data_length = image_info->disp_config.size + 2; + + LOCK_BUFFER(zeroflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DOWNLOAD_CONFIG, + zeroflash_hcd->out.buf, + zeroflash_hcd->out.data_length, + &zeroflash_hcd->resp.buf, + &zeroflash_hcd->resp.buf_size, + &zeroflash_hcd->resp.data_length, + &response_code, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DOWNLOAD_CONFIG)); + if (response_code != STATUS_ERROR) + goto unlock_resp; + retry_count++; + if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) + goto unlock_resp; + } else { + retry_count = 0; + } + + retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status, + sizeof(zeroflash_hcd->fw_status), + zeroflash_hcd->resp.buf, + zeroflash_hcd->resp.buf_size, + sizeof(zeroflash_hcd->fw_status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware status\n"); + goto unlock_resp; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Display config downloaded\n"); + + retval = 0; + +unlock_resp: + UNLOCK_BUFFER(zeroflash_hcd->resp); + +unlock_out: + UNLOCK_BUFFER(zeroflash_hcd->out); + + return retval; +} + +static int zeroflash_download_app_config(void) +{ + int retval; + unsigned char padding; + unsigned char response_code; + struct image_info *image_info; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + static unsigned int retry_count; + + LOGN(tcm_hcd->pdev->dev.parent, + "Downloading application config\n"); + + image_info = &zeroflash_hcd->image_info; + + if (image_info->app_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application config in image file\n"); + return -EINVAL; + } + + padding = image_info->app_config.size % 8; + if (padding) + padding = 8 - padding; + + LOCK_BUFFER(zeroflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &zeroflash_hcd->out, + image_info->app_config.size + 2 + padding); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + goto unlock_out; + } + + switch (zeroflash_hcd->fw_status.hdl_version) { + case 0: + zeroflash_hcd->out.buf[0] = 1; + break; + case 1: + zeroflash_hcd->out.buf[0] = 2; + break; + default: + retval = -EINVAL; + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid HDL version (%d)\n", + zeroflash_hcd->fw_status.hdl_version); + goto unlock_out; + } + + zeroflash_hcd->out.buf[1] = HDL_TOUCH_CONFIG; + + retval = secure_memcpy(&zeroflash_hcd->out.buf[2], + zeroflash_hcd->out.buf_size - 2, + image_info->app_config.data, + image_info->app_config.size, + image_info->app_config.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application config data\n"); + goto unlock_out; + } + + zeroflash_hcd->out.data_length = image_info->app_config.size + 2; + zeroflash_hcd->out.data_length += padding; + + LOCK_BUFFER(zeroflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DOWNLOAD_CONFIG, + zeroflash_hcd->out.buf, + zeroflash_hcd->out.data_length, + &zeroflash_hcd->resp.buf, + &zeroflash_hcd->resp.buf_size, + &zeroflash_hcd->resp.data_length, + &response_code, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DOWNLOAD_CONFIG)); + if (response_code != STATUS_ERROR) + goto unlock_resp; + retry_count++; + if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) + goto unlock_resp; + } else { + retry_count = 0; + } + + retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status, + sizeof(zeroflash_hcd->fw_status), + zeroflash_hcd->resp.buf, + zeroflash_hcd->resp.buf_size, + sizeof(zeroflash_hcd->fw_status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware status\n"); + goto unlock_resp; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Application config downloaded\n"); + + retval = 0; + +unlock_resp: + UNLOCK_BUFFER(zeroflash_hcd->resp); + +unlock_out: + UNLOCK_BUFFER(zeroflash_hcd->out); + + return retval; +} + +static void zeroflash_download_config_work(struct work_struct *work) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + retval = zeroflash_get_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + return; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of config download\n"); + + if (zeroflash_hcd->fw_status.need_app_config) { + retval = zeroflash_download_app_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to download application config\n"); + return; + } + goto exit; + } + + if (zeroflash_hcd->fw_status.need_disp_config) { + retval = zeroflash_download_disp_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to download display config\n"); + return; + } + goto exit; + } + +exit: + LOGN(tcm_hcd->pdev->dev.parent, + "End of config download\n"); + + zeroflash_download_config(); +} + +static int zeroflash_download_app_fw(void) +{ + int retval; + unsigned char command; + struct image_info *image_info; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; +#if RESET_TO_HDL_DELAY_MS + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; +#endif + + LOGN(tcm_hcd->pdev->dev.parent, + "Downloading application firmware\n"); + + image_info = &zeroflash_hcd->image_info; + + if (image_info->app_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application firmware in image file\n"); + return -EINVAL; + } + + LOCK_BUFFER(zeroflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &zeroflash_hcd->out, + image_info->app_firmware.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + retval = secure_memcpy(zeroflash_hcd->out.buf, + zeroflash_hcd->out.buf_size, + image_info->app_firmware.data, + image_info->app_firmware.size, + image_info->app_firmware.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application firmware data\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + zeroflash_hcd->out.data_length = image_info->app_firmware.size; + + command = F35_WRITE_FW_TO_PMEM_COMMAND; + +#if RESET_TO_HDL_DELAY_MS + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(RESET_TO_HDL_DELAY_MS); +#endif + + retval = syna_tcm_rmi_write(tcm_hcd, + zeroflash_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + retval = syna_tcm_rmi_write(tcm_hcd, + zeroflash_hcd->f35_addr.control_base + F35_CTRL7_OFFSET, + zeroflash_hcd->out.buf, + zeroflash_hcd->out.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write application firmware data\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + UNLOCK_BUFFER(zeroflash_hcd->out); + + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware downloaded\n"); + + return 0; +} + +static void zeroflash_download_firmware_work(struct work_struct *work) +{ + int retval; + struct rmi_f35_data data; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + static unsigned int retry_count; + + retval = zeroflash_check_uboot(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Microbootloader support unavailable\n"); + goto exit; + } + + atomic_set(&tcm_hcd->host_downloading, 1); + + retval = syna_tcm_rmi_read(tcm_hcd, + zeroflash_hcd->f35_addr.data_base, + (unsigned char *)&data, + sizeof(data)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read F$35 data\n"); + goto exit; + } + + if (data.error_code != REQUESTING_FIRMWARE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Microbootloader error code = 0x%02x\n", + data.error_code); + if (data.error_code != CHECKSUM_FAILURE) { + retval = -EIO; + goto exit; + } else { + retry_count++; + } + } else { + retry_count = 0; + } + + retval = zeroflash_get_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of firmware download\n"); + + retval = zeroflash_download_app_fw(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to download application firmware\n"); + goto exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "End of firmware download\n"); + +exit: + if (retval < 0) + retry_count++; + + if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) { + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + } + } else { + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + } + } +} + +static int zeroflash_init(struct syna_tcm_hcd *tcm_hcd) +{ + zeroflash_hcd = kzalloc(sizeof(*zeroflash_hcd), GFP_KERNEL); + if (!zeroflash_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for zeroflash_hcd\n"); + return -ENOMEM; + } + + zeroflash_hcd->tcm_hcd = tcm_hcd; + + INIT_BUFFER(zeroflash_hcd->out, false); + INIT_BUFFER(zeroflash_hcd->resp, false); + + zeroflash_hcd->workqueue = + create_singlethread_workqueue("syna_tcm_zeroflash"); + INIT_WORK(&zeroflash_hcd->config_work, + zeroflash_download_config_work); + INIT_WORK(&zeroflash_hcd->firmware_work, + zeroflash_download_firmware_work); + + if (tcm_hcd->init_okay == false && + tcm_hcd->hw_if->bus_io->type == BUS_SPI) + zeroflash_download_firmware(); + + return 0; +} + +static int zeroflash_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!zeroflash_hcd) + goto exit; + + if (zeroflash_hcd->fw_entry) + release_firmware(zeroflash_hcd->fw_entry); + + cancel_work_sync(&zeroflash_hcd->config_work); + cancel_work_sync(&zeroflash_hcd->firmware_work); + flush_workqueue(zeroflash_hcd->workqueue); + destroy_workqueue(zeroflash_hcd->workqueue); + + RELEASE_BUFFER(zeroflash_hcd->resp); + RELEASE_BUFFER(zeroflash_hcd->out); + + kfree(zeroflash_hcd); + zeroflash_hcd = NULL; + +exit: + complete(&zeroflash_remove_complete); + + return 0; +} + +static int zeroflash_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *fw_status; + + if (!zeroflash_hcd) + return 0; + + switch (tcm_hcd->report.id) { + case REPORT_STATUS: + fw_status = (unsigned char *)&zeroflash_hcd->fw_status; + retval = secure_memcpy(fw_status, + sizeof(zeroflash_hcd->fw_status), + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + sizeof(zeroflash_hcd->fw_status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware status\n"); + return retval; + } + zeroflash_download_config(); + break; + case REPORT_HDL: + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + return retval; + } + zeroflash_download_firmware(); + break; + default: + break; + } + + return 0; +} + +static int zeroflash_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!zeroflash_hcd) { + retval = zeroflash_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb zeroflash_module = { + .type = TCM_ZEROFLASH, + .init = zeroflash_init, + .remove = zeroflash_remove, + .syncbox = zeroflash_syncbox, + .asyncbox = NULL, + .reset = zeroflash_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init zeroflash_module_init(void) +{ + return syna_tcm_add_module(&zeroflash_module, true); +} + +static void __exit zeroflash_module_exit(void) +{ + syna_tcm_add_module(&zeroflash_module, false); + + wait_for_completion(&zeroflash_remove_complete); +} + +module_init(zeroflash_module_init); +module_exit(zeroflash_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Zeroflash Module"); +MODULE_LICENSE("GPL v2");