blob: 41af42772d15def6ecaf53aef46f20bae2632e4f [file] [log] [blame]
/*
* 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, &param);
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");