| /* |
| * Synaptics TCM touchscreen driver |
| * |
| * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. |
| * |
| * Copyright (C) 2017-2019 Scott Lin <scott.lin@tw.synaptics.com> |
| * |
| * 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 <linux/gpio.h> |
| #include <linux/kthread.h> |
| #include <linux/interrupt.h> |
| #include <linux/regulator/consumer.h> |
| #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 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 5000 |
| |
| #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_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) { |
| 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) |
| regulator_disable(tcm_hcd->pwr_reg); |
| |
| disable_bus_reg: |
| if (tcm_hcd->bus_reg) |
| 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); |
| 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); |
| } |
| } |
| |
| if (!tcm_hcd->in_suspend) |
| return 0; |
| |
| 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); |
| |
| if (tcm_hcd->in_suspend || !tcm_hcd->init_okay) |
| return 0; |
| |
| 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); |
| } |
| } |
| |
| mutex_unlock(&mod_pool.mutex); |
| |
| tcm_hcd->in_suspend = true; |
| |
| return 0; |
| } |
| #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; |
| |
| 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 msm_drm_notifier *evdata = data; |
| struct syna_tcm_hcd *tcm_hcd = |
| container_of(nb, struct syna_tcm_hcd, fb_notifier); |
| |
| if (!evdata || (evdata->id != 0)) |
| return 0; |
| |
| if (!evdata->data || !tcm_hcd) |
| return 0; |
| |
| transition = (int *) evdata->data; |
| |
| if (atomic_read(&tcm_hcd->firmware_flashing) |
| && *transition == MSM_DRM_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 == MSM_DRM_EARLY_EVENT_BLANK && |
| *transition == MSM_DRM_BLANK_POWERDOWN) |
| retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev); |
| else if (action == MSM_DRM_EVENT_BLANK) { |
| if (*transition == MSM_DRM_BLANK_POWERDOWN) { |
| retval = syna_tcm_suspend(&tcm_hcd->pdev->dev); |
| tcm_hcd->fb_ready = 0; |
| } else if (*transition == MSM_DRM_BLANK_UNBLANK) { |
| #ifndef RESUME_EARLY_UNBLANK |
| retval = syna_tcm_resume(&tcm_hcd->pdev->dev); |
| tcm_hcd->fb_ready++; |
| #endif |
| } |
| } else if (action == MSM_DRM_EARLY_EVENT_BLANK && |
| *transition == MSM_DRM_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 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; |
| |
| 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; |
| } |
| |
| 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; |
| retval = msm_drm_register_client(&tcm_hcd->fb_notifier); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to register DRM notifier client\n"); |
| } |
| #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 |
| msm_drm_unregister_client(&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); |
| |
| 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; |
| |
| 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 |
| msm_drm_unregister_client(&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; |
| } |
| |
| static void syna_tcm_shutdown(struct platform_device *pdev) |
| { |
| syna_tcm_remove(pdev); |
| } |
| |
| #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, |
| .shutdown = syna_tcm_shutdown, |
| }; |
| |
| 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(); |
| } |
| |
| module_init(syna_tcm_module_init); |
| module_exit(syna_tcm_module_exit); |
| |
| MODULE_AUTHOR("Synaptics, Inc."); |
| MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); |
| MODULE_LICENSE("GPL v2"); |