浏览代码

Merge "touch: Add synaptics_tcm touch driver for khaje"

qctecmdr 2 年之前
父节点
当前提交
6f5aa12f7b

+ 10 - 0
Android.mk

@@ -66,5 +66,15 @@ ifeq ($(TOUCH_DLKM_ENABLE),  true)
 	include $(DLKM_DIR)/Build_external_kernelmodule.mk
 	###########################################################
 
+	###########################################################
+	include $(CLEAR_VARS)
+	LOCAL_SRC_FILES   := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*)
+	LOCAL_MODULE              := synaptics_tcm_ts.ko
+	LOCAL_MODULE_KBUILD_NAME  := synaptics_tcm_ts.ko
+	LOCAL_MODULE_TAGS         := optional
+	#LOCAL_MODULE_DEBUG_ENABLE := true
+	LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+	include $(DLKM_DIR)/Build_external_kernelmodule.mk
+	###########################################################
 	endif # DLKM check
 endif

+ 15 - 0
Kbuild

@@ -11,6 +11,11 @@ endif
 	LINUX_INC += -include $(TOUCH_ROOT)/config/gki_kalamatouchconf.h
 #endif
 
+#ifeq ($(CONFIG_ARCH_KHAJE), y)
+	include $(TOUCH_ROOT)/config/gki_khajetouch.conf
+	LINUX_INC += -include $(TOUCH_ROOT)/config/gki_khajetouchconf.h
+#endif
+
 LINUX_INC +=	-Iinclude/linux \
 		-Iinclude/linux/drm \
 		-Iinclude/linux/gunyah \
@@ -121,4 +126,14 @@ ifeq ($(CONFIG_TOUCHSCREEN_DUMMY), y)
 	obj-$(CONFIG_MSM_TOUCH) += dummy_ts.o
 endif
 
+ifeq ($(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM), y)
+	synaptics_tcm_ts-y := \
+		 ./synaptics_tcm/synaptics_tcm_core.o \
+		 ./synaptics_tcm/synaptics_tcm_i2c.o \
+		 ./synaptics_tcm/synaptics_tcm_touch.o
+
+	obj-$(CONFIG_MSM_TOUCH) += synaptics_tcm_ts.o
+
+endif
+
 CDEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\"

+ 5 - 0
config/gki_khajetouch.conf

@@ -0,0 +1,5 @@
+export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM=y
+export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C=y
+export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE=y
+export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH=y
+export CONFIG_MSM_TOUCH=m

+ 10 - 0
config/gki_khajetouchconf.h

@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM 1
+#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C 1
+#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE 1
+#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH 1
+

+ 65 - 0
synaptics_tcm/synaptics_tcm.h

@@ -0,0 +1,65 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
+ * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
+ * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
+ * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
+ * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
+ * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
+ * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
+ * DOLLARS.
+ */
+
+#ifndef _SYNAPTICS_TCM_H_
+#define _SYNAPTICS_TCM_H_
+
+#define I2C_MODULE_NAME "synaptics_tcm_i2c"
+#define SPI_MODULE_NAME "synaptics_tcm_spi"
+
+struct syna_tcm_board_data {
+	bool x_flip;
+	bool y_flip;
+	bool swap_axes;
+	int irq_gpio;
+	int irq_on_state;
+	int power_gpio;
+	int power_on_state;
+	int reset_gpio;
+	int reset_on_state;
+	unsigned int spi_mode;
+	unsigned int power_delay_ms;
+	unsigned int reset_delay_ms;
+	unsigned int reset_active_ms;
+	unsigned int byte_delay_us;
+	unsigned int block_delay_us;
+	unsigned int ubl_i2c_addr;
+	unsigned int ubl_max_freq;
+	unsigned int ubl_byte_delay_us;
+	unsigned long irq_flags;
+	const char *pwr_reg_name;
+	const char *bus_reg_name;
+	const char *fw_name;
+	bool extend_report;
+};
+
+#endif

+ 3812 - 0
synaptics_tcm/synaptics_tcm_core.c

@@ -0,0 +1,3812 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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 SYNA_VTG_MIN_UV	2800000
+
+#define SYNA_VTG_MAX_UV	3300000
+
+#define SYNA_LOAD_MAX_UA 30000
+
+#define SYNA_VDD_VTG_MIN_UV 1800000
+
+#define SYNA_VDD_VTG_MAX_UV 2000000
+
+#define NOTIFIER_PRIORITY 2
+
+#define RESPONSE_TIMEOUT_MS 3000
+
+#define APP_STATUS_POLL_TIMEOUT_MS 1000
+
+#define APP_STATUS_POLL_MS 100
+
+#define ENABLE_IRQ_DELAY_MS 20
+
+#define FALL_BACK_ON_POLLING
+
+#define POLLING_DELAY_MS 5
+
+#define RUN_WATCHDOG true
+
+#define WATCHDOG_TRIGGER_COUNT 2
+
+#define WATCHDOG_DELAY_MS 50000
+
+#define MODE_SWITCH_DELAY_MS 100
+
+#define READ_RETRY_US_MIN 5000
+
+#define READ_RETRY_US_MAX 10000
+
+#define WRITE_DELAY_US_MIN 500
+
+#define WRITE_DELAY_US_MAX 1000
+
+#define HOST_DOWNLOAD_WAIT_MS 100
+
+#define HOST_DOWNLOAD_TIMEOUT_MS 1000
+
+#define DYNAMIC_CONFIG_SYSFS_DIR_NAME "dynamic_config"
+
+#define dynamic_config_sysfs(c_name, id) \
+static ssize_t syna_tcm_sysfs_##c_name##_show(struct device *dev, \
+		struct device_attribute *attr, char *buf) \
+{ \
+	int retval; \
+	unsigned short value; \
+	struct device *p_dev; \
+	struct kobject *p_kobj; \
+	struct syna_tcm_hcd *tcm_hcd; \
+\
+	p_kobj = sysfs_dir->parent; \
+	p_dev = container_of(p_kobj, struct device, kobj); \
+	tcm_hcd = dev_get_drvdata(p_dev); \
+\
+	mutex_lock(&tcm_hcd->extif_mutex); \
+\
+	retval = tcm_hcd->get_dynamic_config(tcm_hcd, id, &value); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to get dynamic config\n"); \
+		goto exit; \
+	} \
+\
+	retval = snprintf(buf, PAGE_SIZE, "%u\n", value); \
+\
+exit: \
+	mutex_unlock(&tcm_hcd->extif_mutex); \
+\
+	return retval; \
+} \
+\
+static ssize_t syna_tcm_sysfs_##c_name##_store(struct device *dev, \
+		struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+	int retval; \
+	unsigned int input; \
+	struct device *p_dev; \
+	struct kobject *p_kobj; \
+	struct syna_tcm_hcd *tcm_hcd; \
+\
+	p_kobj = sysfs_dir->parent; \
+	p_dev = container_of(p_kobj, struct device, kobj); \
+	tcm_hcd = dev_get_drvdata(p_dev); \
+\
+	if (kstrtouint(buf, 10, &input)) \
+		return -EINVAL; \
+\
+	mutex_lock(&tcm_hcd->extif_mutex); \
+\
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd, id, input); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to set dynamic config\n"); \
+		goto exit; \
+	} \
+\
+	retval = count; \
+\
+exit: \
+	mutex_unlock(&tcm_hcd->extif_mutex); \
+\
+	return retval; \
+}
+
+DECLARE_COMPLETION(response_complete);
+
+static struct kobject *sysfs_dir;
+
+static struct syna_tcm_module_pool mod_pool;
+
+SHOW_PROTOTYPE(syna_tcm, info);
+STORE_PROTOTYPE(syna_tcm, irq_en);
+STORE_PROTOTYPE(syna_tcm, reset);
+STORE_PROTOTYPE(syna_tcm, watchdog);
+SHOW_STORE_PROTOTYPE(syna_tcm, no_doze);
+SHOW_STORE_PROTOTYPE(syna_tcm, disable_noise_mitigation);
+SHOW_STORE_PROTOTYPE(syna_tcm, inhibit_frequency_shift);
+SHOW_STORE_PROTOTYPE(syna_tcm, requested_frequency);
+SHOW_STORE_PROTOTYPE(syna_tcm, disable_hsync);
+SHOW_STORE_PROTOTYPE(syna_tcm, rezero_on_exit_deep_sleep);
+SHOW_STORE_PROTOTYPE(syna_tcm, charger_connected);
+SHOW_STORE_PROTOTYPE(syna_tcm, no_baseline_relaxation);
+SHOW_STORE_PROTOTYPE(syna_tcm, in_wakeup_gesture_mode);
+SHOW_STORE_PROTOTYPE(syna_tcm, stimulus_fingers);
+SHOW_STORE_PROTOTYPE(syna_tcm, grip_suppression_enabled);
+SHOW_STORE_PROTOTYPE(syna_tcm, enable_thick_glove);
+SHOW_STORE_PROTOTYPE(syna_tcm, enable_glove);
+
+static struct device_attribute *attrs[] = {
+	ATTRIFY(info),
+	ATTRIFY(irq_en),
+	ATTRIFY(reset),
+	ATTRIFY(watchdog),
+};
+
+static struct device_attribute *dynamic_config_attrs[] = {
+	ATTRIFY(no_doze),
+	ATTRIFY(disable_noise_mitigation),
+	ATTRIFY(inhibit_frequency_shift),
+	ATTRIFY(requested_frequency),
+	ATTRIFY(disable_hsync),
+	ATTRIFY(rezero_on_exit_deep_sleep),
+	ATTRIFY(charger_connected),
+	ATTRIFY(no_baseline_relaxation),
+	ATTRIFY(in_wakeup_gesture_mode),
+	ATTRIFY(stimulus_fingers),
+	ATTRIFY(grip_suppression_enabled),
+	ATTRIFY(enable_thick_glove),
+	ATTRIFY(enable_glove),
+};
+
+extern int touch_module_init(void);
+extern void touch_module_exit(void);
+
+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_set_voltage(tcm_hcd->bus_reg,
+				SYNA_VDD_VTG_MIN_UV, SYNA_VDD_VTG_MAX_UV);
+		if (retval) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"set bus regulator voltage failed\n");
+			goto exit;
+		}
+
+		retval = regulator_set_load(tcm_hcd->bus_reg,
+						SYNA_LOAD_MAX_UA);
+		if (retval) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"set bus regulator load failed\n");
+			goto exit;
+		}
+
+		retval = regulator_enable(tcm_hcd->bus_reg);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable bus regulator\n");
+			goto exit;
+		}
+	}
+
+	if (tcm_hcd->pwr_reg) {
+		if (regulator_count_voltages(tcm_hcd->pwr_reg) > 0) {
+			retval = regulator_set_voltage(tcm_hcd->pwr_reg,
+				SYNA_VTG_MIN_UV, SYNA_VTG_MAX_UV);
+			if (retval) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"set power regulator voltage failed\n");
+				goto disable_bus_reg;
+			}
+			retval = regulator_set_load(tcm_hcd->pwr_reg,
+							SYNA_LOAD_MAX_UA);
+			if (retval) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"set power regulator load failed\n");
+				goto disable_bus_reg;
+			}
+		}
+
+		retval = regulator_enable(tcm_hcd->pwr_reg);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to enable power regulator\n");
+			goto disable_bus_reg;
+		}
+		msleep(bdata->power_delay_ms);
+	}
+
+	return 0;
+
+disable_pwr_reg:
+	if (tcm_hcd->pwr_reg) {
+		if (regulator_count_voltages(tcm_hcd->pwr_reg) > 0) {
+			regulator_set_load(tcm_hcd->pwr_reg, 0);
+			regulator_set_voltage(tcm_hcd->pwr_reg, 0,
+							SYNA_VTG_MAX_UV);
+		}
+		regulator_disable(tcm_hcd->pwr_reg);
+	}
+
+disable_bus_reg:
+	if (tcm_hcd->bus_reg) {
+		regulator_set_load(tcm_hcd->bus_reg, 0);
+		regulator_set_voltage(tcm_hcd->bus_reg, 0,
+						SYNA_VDD_VTG_MAX_UV);
+		regulator_disable(tcm_hcd->bus_reg);
+	}
+
+exit:
+	return retval;
+}
+
+static int syna_tcm_get_regulator(struct syna_tcm_hcd *tcm_hcd, bool get)
+{
+	int retval;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	if (!get) {
+		retval = 0;
+		goto regulator_put;
+	}
+
+	if (bdata->bus_reg_name != NULL && *bdata->bus_reg_name != 0) {
+		tcm_hcd->bus_reg = regulator_get(tcm_hcd->pdev->dev.parent,
+				bdata->bus_reg_name);
+		if (IS_ERR(tcm_hcd->bus_reg)) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get bus regulator\n");
+			retval = PTR_ERR(tcm_hcd->bus_reg);
+			goto regulator_put;
+		}
+	}
+
+	if (bdata->pwr_reg_name != NULL && *bdata->pwr_reg_name != 0) {
+		tcm_hcd->pwr_reg = regulator_get(tcm_hcd->pdev->dev.parent,
+				bdata->pwr_reg_name);
+		if (IS_ERR(tcm_hcd->pwr_reg)) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get power regulator\n");
+			retval = PTR_ERR(tcm_hcd->pwr_reg);
+			goto regulator_put;
+		}
+	}
+
+	return 0;
+
+regulator_put:
+	if (tcm_hcd->bus_reg) {
+		regulator_put(tcm_hcd->bus_reg);
+		tcm_hcd->bus_reg = NULL;
+	}
+
+	if (tcm_hcd->pwr_reg) {
+		regulator_put(tcm_hcd->pwr_reg);
+		tcm_hcd->pwr_reg = NULL;
+	}
+
+	return retval;
+}
+
+static int syna_tcm_get_app_info(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+	unsigned int timeout;
+
+	timeout = APP_STATUS_POLL_TIMEOUT_MS;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+get_app_info:
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_GET_APPLICATION_INFO,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_GET_APPLICATION_INFO));
+		goto exit;
+	}
+
+	retval = secure_memcpy((unsigned char *)&tcm_hcd->app_info,
+			sizeof(tcm_hcd->app_info),
+			resp_buf,
+			resp_buf_size,
+			MIN(sizeof(tcm_hcd->app_info), resp_length));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy application info\n");
+		goto exit;
+	}
+
+	tcm_hcd->app_status = le2_to_uint(tcm_hcd->app_info.status);
+
+	if (tcm_hcd->app_status == APP_STATUS_BOOTING ||
+			tcm_hcd->app_status == APP_STATUS_UPDATING) {
+		if (timeout > 0) {
+			msleep(APP_STATUS_POLL_MS);
+			timeout -= APP_STATUS_POLL_MS;
+			goto get_app_info;
+		}
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_get_boot_info(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_GET_BOOT_INFO,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_GET_BOOT_INFO));
+		goto exit;
+	}
+
+	retval = secure_memcpy((unsigned char *)&tcm_hcd->boot_info,
+			sizeof(tcm_hcd->boot_info),
+			resp_buf,
+			resp_buf_size,
+			MIN(sizeof(tcm_hcd->boot_info), resp_length));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy boot info\n");
+		goto exit;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_identify(struct syna_tcm_hcd *tcm_hcd, bool id)
+{
+	int retval;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+	unsigned int max_write_size;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	mutex_lock(&tcm_hcd->identify_mutex);
+
+	if (!id)
+		goto get_info;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_IDENTIFY,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_IDENTIFY));
+		goto exit;
+	}
+
+	retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info,
+			sizeof(tcm_hcd->id_info),
+			resp_buf,
+			resp_buf_size,
+			MIN(sizeof(tcm_hcd->id_info), resp_length));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy identification info\n");
+		goto exit;
+	}
+
+	tcm_hcd->packrat_number = le4_to_uint(tcm_hcd->id_info.build_id);
+
+	max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size);
+	tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE);
+	if (tcm_hcd->wr_chunk_size == 0)
+		tcm_hcd->wr_chunk_size = max_write_size;
+
+get_info:
+	switch (tcm_hcd->id_info.mode) {
+	case MODE_APPLICATION:
+		retval = syna_tcm_get_app_info(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get application info\n");
+			goto exit;
+		}
+		break;
+	case MODE_BOOTLOADER:
+	case MODE_TDDI_BOOTLOADER:
+		retval = syna_tcm_get_boot_info(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get boot info\n");
+			goto exit;
+		}
+		break;
+	default:
+		break;
+	}
+
+	retval = 0;
+
+exit:
+	mutex_unlock(&tcm_hcd->identify_mutex);
+
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_run_production_test_firmware(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	bool retry;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	retry = true;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+retry:
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_ENTER_PRODUCTION_TEST_MODE,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			MODE_SWITCH_DELAY_MS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_ENTER_PRODUCTION_TEST_MODE));
+		goto exit;
+	}
+
+	if (tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run production test firmware\n");
+		if (retry) {
+			retry = false;
+			goto retry;
+		}
+		retval = -EINVAL;
+		goto exit;
+	} else if (tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Application status = 0x%02x\n",
+				tcm_hcd->app_status);
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_run_application_firmware(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	bool retry;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	retry = true;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+retry:
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_RUN_APPLICATION_FIRMWARE,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			MODE_SWITCH_DELAY_MS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_RUN_APPLICATION_FIRMWARE));
+		goto exit;
+	}
+
+	retval = tcm_hcd->identify(tcm_hcd, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		goto exit;
+	}
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to run application (status = 0x%02x)\n",
+			tcm_hcd->boot_info.status);
+		if (retry) {
+			retry = false;
+			goto retry;
+		}
+		retval = -EINVAL;
+		goto exit;
+	} else if (tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Application status = 0x%02x\n",
+				tcm_hcd->app_status);
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_run_bootloader_firmware(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_RUN_BOOTLOADER_FIRMWARE,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			MODE_SWITCH_DELAY_MS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_RUN_BOOTLOADER_FIRMWARE));
+		goto exit;
+	}
+
+	retval = tcm_hcd->identify(tcm_hcd, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		goto exit;
+	}
+
+	if (tcm_hcd->id_info.mode == MODE_APPLICATION) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enter bootloader mode\n");
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_switch_mode(struct syna_tcm_hcd *tcm_hcd,
+		enum firmware_mode mode)
+{
+	int retval;
+
+	mutex_lock(&tcm_hcd->reset_mutex);
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	switch (mode) {
+	case FW_MODE_BOOTLOADER:
+		retval = syna_tcm_run_bootloader_firmware(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to switch to bootloader mode\n");
+			goto exit;
+		}
+		break;
+	case FW_MODE_APPLICATION:
+		retval = syna_tcm_run_application_firmware(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to switch to application mode\n");
+			goto exit;
+		}
+		break;
+	case FW_MODE_PRODUCTION_TEST:
+		retval = syna_tcm_run_production_test_firmware(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to switch to production test mode\n");
+			goto exit;
+		}
+		break;
+	default:
+		LOGE(tcm_hcd->pdev->dev.parent, "Invalid firmware mode\n");
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	retval = 0;
+
+exit:
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	mutex_unlock(&tcm_hcd->reset_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_get_dynamic_config(struct syna_tcm_hcd *tcm_hcd,
+		enum dynamic_config_id id, unsigned short *value)
+{
+	int retval;
+	unsigned char out_buf;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	out_buf = (unsigned char)id;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_GET_DYNAMIC_CONFIG,
+			&out_buf,
+			sizeof(out_buf),
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_GET_DYNAMIC_CONFIG));
+		goto exit;
+	}
+
+	if (resp_length < 2) {
+		LOGE(tcm_hcd->pdev->dev.parent, "Invalid data length\n");
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	*value = (unsigned short)le2_to_uint(resp_buf);
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_set_dynamic_config(struct syna_tcm_hcd *tcm_hcd,
+		enum dynamic_config_id id, unsigned short value)
+{
+	int retval;
+	unsigned char out_buf[3];
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	out_buf[0] = (unsigned char)id;
+	out_buf[1] = (unsigned char)value;
+	out_buf[2] = (unsigned char)(value >> 8);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_SET_DYNAMIC_CONFIG,
+			out_buf,
+			sizeof(out_buf),
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_SET_DYNAMIC_CONFIG));
+		goto exit;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_get_data_location(struct syna_tcm_hcd *tcm_hcd,
+		enum flash_area area, unsigned int *addr, unsigned int *length)
+{
+	int retval;
+	unsigned char out_buf;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	switch (area) {
+	case CUSTOM_LCM:
+		out_buf = LCM_DATA;
+		break;
+	case CUSTOM_OEM:
+		out_buf = OEM_DATA;
+		break;
+	case PPDT:
+		out_buf = PPDT_DATA;
+		break;
+	default:
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid flash area\n");
+		return -EINVAL;
+	}
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_GET_DATA_LOCATION,
+			&out_buf,
+			sizeof(out_buf),
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_GET_DATA_LOCATION));
+		goto exit;
+	}
+
+	if (resp_length != 4) {
+		LOGE(tcm_hcd->pdev->dev.parent, "Invalid data length\n");
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	*addr = le2_to_uint(&resp_buf[0]);
+	*length = le2_to_uint(&resp_buf[2]);
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_sleep(struct syna_tcm_hcd *tcm_hcd, bool en)
+{
+	int retval;
+	unsigned char command;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	command = en ? CMD_ENTER_DEEP_SLEEP : CMD_EXIT_DEEP_SLEEP;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			command,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				en ?
+				STR(CMD_ENTER_DEEP_SLEEP) :
+				STR(CMD_EXIT_DEEP_SLEEP));
+		goto exit;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_reset(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd)
+{
+	int retval;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+	struct syna_tcm_module_handler *mod_handler;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	mutex_lock(&tcm_hcd->reset_mutex);
+
+	if (update_wd)
+		tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	if (hw) {
+		if (bdata->reset_gpio < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Hardware reset unavailable\n");
+			retval = -EINVAL;
+			goto exit;
+		}
+		gpio_set_value(bdata->reset_gpio, bdata->reset_on_state);
+		msleep(bdata->reset_active_ms);
+		gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state);
+	} else {
+		retval = tcm_hcd->write_message(tcm_hcd,
+				CMD_RESET,
+				NULL,
+				0,
+				&resp_buf,
+				&resp_buf_size,
+				&resp_length,
+				NULL,
+				bdata->reset_delay_ms);
+		if (retval < 0 && !tcm_hcd->host_download_mode) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to write command %s\n",
+					STR(CMD_RESET));
+			goto exit;
+		}
+	}
+
+	if (tcm_hcd->host_download_mode) {
+		mutex_unlock(&tcm_hcd->reset_mutex);
+		kfree(resp_buf);
+		retval = syna_tcm_wait_hdl(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to wait for completion of download\n");
+			return retval;
+		}
+		if (update_wd)
+			tcm_hcd->update_watchdog(tcm_hcd, true);
+		return 0;
+	}
+
+	msleep(bdata->reset_delay_ms);
+
+	retval = tcm_hcd->identify(tcm_hcd, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		goto exit;
+	}
+
+	if (tcm_hcd->id_info.mode == MODE_APPLICATION)
+		goto get_features;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_RUN_APPLICATION_FIRMWARE,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			MODE_SWITCH_DELAY_MS);
+	if (retval < 0) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_RUN_APPLICATION_FIRMWARE));
+	}
+
+	retval = tcm_hcd->identify(tcm_hcd, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		goto exit;
+	}
+
+get_features:
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Firmware mode = 0x%02x\n",
+			tcm_hcd->id_info.mode);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Boot status = 0x%02x\n",
+				tcm_hcd->boot_info.status);
+	} else if (tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Application status = 0x%02x\n",
+				tcm_hcd->app_status);
+	}
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION)
+		goto dispatch_reset;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_GET_FEATURES,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0)
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_GET_FEATURES));
+	else {
+		retval = secure_memcpy((unsigned char *)&tcm_hcd->features,
+				sizeof(tcm_hcd->features),
+				resp_buf,
+				resp_buf_size,
+				MIN(sizeof(tcm_hcd->features), resp_length));
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy feature description\n");
+		}
+	}
+
+dispatch_reset:
+	mutex_lock(&mod_pool.mutex);
+	mod_pool.reconstructing = true;
+
+	if (!list_empty(&mod_pool.list)) {
+		list_for_each_entry(mod_handler, &mod_pool.list, link) {
+			if (!mod_handler->insert &&
+					!mod_handler->detach &&
+					(mod_handler->mod_cb->reset))
+				mod_handler->mod_cb->reset(tcm_hcd);
+		}
+	}
+
+	mod_pool.reconstructing = false;
+	mutex_unlock(&mod_pool.mutex);
+
+	retval = 0;
+
+exit:
+	if (update_wd)
+		tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	mutex_unlock(&tcm_hcd->reset_mutex);
+
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_rezero(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	unsigned char *resp_buf;
+	unsigned int resp_buf_size;
+	unsigned int resp_length;
+
+	resp_buf = NULL;
+	resp_buf_size = 0;
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_REZERO,
+			NULL,
+			0,
+			&resp_buf,
+			&resp_buf_size,
+			&resp_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_REZERO));
+		goto exit;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static void syna_tcm_helper_work(struct work_struct *work)
+{
+	int retval;
+	unsigned char task;
+	struct syna_tcm_module_handler *mod_handler;
+	struct syna_tcm_helper *helper =
+			container_of(work, struct syna_tcm_helper, work);
+	struct syna_tcm_hcd *tcm_hcd =
+			container_of(helper, struct syna_tcm_hcd, helper);
+
+	task = atomic_read(&helper->task);
+
+	switch (task) {
+	case HELP_RUN_APPLICATION_FIRMWARE:
+		mutex_lock(&tcm_hcd->reset_mutex);
+		tcm_hcd->update_watchdog(tcm_hcd, false);
+		retval = syna_tcm_run_application_firmware(tcm_hcd);
+		if (retval < 0)
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to switch to application mode\n");
+		tcm_hcd->update_watchdog(tcm_hcd, true);
+		mutex_unlock(&tcm_hcd->reset_mutex);
+		break;
+	case HELP_SEND_RESET_NOTIFICATION:
+		mutex_lock(&tcm_hcd->reset_mutex);
+		retval = tcm_hcd->identify(tcm_hcd, true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do identification\n");
+			mutex_unlock(&tcm_hcd->reset_mutex);
+			break;
+		}
+		mutex_lock(&mod_pool.mutex);
+		if (!list_empty(&mod_pool.list)) {
+			list_for_each_entry(mod_handler, &mod_pool.list, link) {
+				if (!mod_handler->insert &&
+						!mod_handler->detach &&
+						(mod_handler->mod_cb->reset))
+					mod_handler->mod_cb->reset(tcm_hcd);
+			}
+		}
+		mutex_unlock(&mod_pool.mutex);
+		mutex_unlock(&tcm_hcd->reset_mutex);
+		wake_up_interruptible(&tcm_hcd->hdl_wq);
+		break;
+	default:
+		break;
+	}
+
+	atomic_set(&helper->task, HELP_NONE);
+}
+
+#if defined(CONFIG_PM) || defined(CONFIG_DRM) || defined(CONFIG_FB)
+
+static int syna_tcm_deferred_probe(struct device *dev);
+
+static int syna_tcm_resume(struct device *dev)
+{
+	int retval;
+	struct syna_tcm_module_handler *mod_handler;
+	struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev);
+
+	if (!tcm_hcd->init_okay)
+		syna_tcm_deferred_probe(dev);
+
+	if (!tcm_hcd->in_suspend)
+		return 0;
+	else {
+		if (tcm_hcd->irq_enabled) {
+			tcm_hcd->watchdog.run = false;
+			tcm_hcd->update_watchdog(tcm_hcd, false);
+			tcm_hcd->enable_irq(tcm_hcd, false, false);
+		}
+	}
+
+	retval = syna_tcm_enable_regulator(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable regulators\n");
+	}
+
+	retval = pinctrl_select_state(
+			tcm_hcd->ts_pinctrl,
+			tcm_hcd->pinctrl_state_active);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"%s: Failed to select %s pinstate %d\n",
+			__func__, PINCTRL_STATE_ACTIVE, retval);
+	}
+
+	if (tcm_hcd->host_download_mode) {
+#ifndef WAKEUP_GESTURE
+		syna_tcm_check_hdl(tcm_hcd);
+		retval = syna_tcm_wait_hdl(tcm_hcd);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to wait for completion of download\n");
+			goto exit;
+		}
+#endif
+	} else {
+		tcm_hcd->enable_irq(tcm_hcd, true, NULL);
+#ifdef RESET_ON_RESUME
+		msleep(RESET_ON_RESUME_DELAY_MS);
+		goto do_reset;
+#endif
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Application firmware not running\n");
+		goto do_reset;
+	}
+
+	retval = tcm_hcd->sleep(tcm_hcd, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to exit deep sleep\n");
+		goto exit;
+	}
+
+	retval = syna_tcm_rezero(tcm_hcd);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to rezero\n");
+		goto exit;
+	}
+
+	goto mod_resume;
+
+do_reset:
+	retval = tcm_hcd->reset(tcm_hcd, false, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do reset\n");
+		goto exit;
+	}
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Application firmware not running\n");
+		retval = 0;
+		goto exit;
+	}
+
+mod_resume:
+	mutex_lock(&mod_pool.mutex);
+
+	if (!list_empty(&mod_pool.list)) {
+		list_for_each_entry(mod_handler, &mod_pool.list, link) {
+			if (!mod_handler->insert &&
+					!mod_handler->detach &&
+					(mod_handler->mod_cb->resume))
+				mod_handler->mod_cb->resume(tcm_hcd);
+		}
+	}
+
+	mutex_unlock(&mod_pool.mutex);
+
+	retval = 0;
+
+exit:
+	tcm_hcd->in_suspend = false;
+
+	return retval;
+}
+
+static int syna_tcm_suspend(struct device *dev)
+{
+	struct syna_tcm_module_handler *mod_handler;
+	struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev);
+	int retval;
+
+	if (tcm_hcd->in_suspend || !tcm_hcd->init_okay)
+		return 0;
+
+	if (pinctrl_select_state(
+			tcm_hcd->ts_pinctrl,
+			tcm_hcd->pinctrl_state_suspend))
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"%s: Failed to select %s pinstate\n",
+			__func__, PINCTRL_STATE_RELEASE);
+
+	mutex_lock(&mod_pool.mutex);
+
+	if (!list_empty(&mod_pool.list)) {
+		list_for_each_entry(mod_handler, &mod_pool.list, link) {
+			if (!mod_handler->insert &&
+					!mod_handler->detach &&
+					(mod_handler->mod_cb->suspend))
+				mod_handler->mod_cb->suspend(tcm_hcd);
+		}
+	}
+
+	retval = syna_tcm_enable_regulator(tcm_hcd, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to disable regulators\n");
+	}
+
+	mutex_unlock(&mod_pool.mutex);
+
+	tcm_hcd->in_suspend = true;
+
+	return retval;
+}
+#endif
+
+
+
+#ifdef CONFIG_DRM
+#if 0
+static int syna_tcm_early_suspend(struct device *dev)
+{
+#ifndef WAKEUP_GESTURE
+	int retval;
+#endif
+
+	struct syna_tcm_module_handler *mod_handler;
+	struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev);
+
+	if (tcm_hcd->in_suspend || !tcm_hcd->init_okay)
+		return 0;
+
+	if (pinctrl_select_state(
+			tcm_hcd->ts_pinctrl,
+			tcm_hcd->pinctrl_state_suspend))
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"%s: Failed to select %s pinstate\n",
+			__func__, PINCTRL_STATE_RELEASE);
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Application firmware not running\n");
+		return 0;
+	}
+
+#ifndef WAKEUP_GESTURE
+	retval = tcm_hcd->sleep(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enter deep sleep\n");
+		return retval;
+	}
+#endif
+
+	mutex_lock(&mod_pool.mutex);
+
+	if (!list_empty(&mod_pool.list)) {
+		list_for_each_entry(mod_handler, &mod_pool.list, link) {
+			if (!mod_handler->insert &&
+					!mod_handler->detach &&
+					(mod_handler->mod_cb->early_suspend))
+				mod_handler->mod_cb->early_suspend(tcm_hcd);
+		}
+	}
+
+	mutex_unlock(&mod_pool.mutex);
+
+#ifndef WAKEUP_GESTURE
+	tcm_hcd->enable_irq(tcm_hcd, false, true);
+#endif
+
+	return 0;
+}
+#endif
+
+static void syna_tcm_fb_notifier_cb(enum panel_event_notifier_tag tag,
+                                struct panel_event_notification *notification,
+                                        void *pvt_data)
+{
+	int retval = 0;
+	struct syna_tcm_hcd *tcm_hcd = (struct syna_tcm_hcd *) pvt_data;
+
+	 if (!notification) {
+                pr_err("Invalid notification\n");
+                return;
+        }
+
+        pr_debug("Notification type:%d, early_trigger:%d",
+                        notification->notif_type,
+                        notification->notif_data.early_trigger);
+        switch (notification->notif_type) {
+        case DRM_PANEL_EVENT_UNBLANK:
+                if (notification->notif_data.early_trigger)
+                        pr_debug("resume notification pre commit\n");
+                else
+                  retval = syna_tcm_resume(&tcm_hcd->pdev->dev);
+                break;
+        case DRM_PANEL_EVENT_BLANK:
+                if (notification->notif_data.early_trigger) {
+                  retval = syna_tcm_suspend(&tcm_hcd->pdev->dev);
+                } else {
+                        pr_debug("suspend notification post commit\n");
+                }
+                break;
+        case DRM_PANEL_EVENT_BLANK_LP:
+                pr_debug("received lp event\n");
+                break;
+        case DRM_PANEL_EVENT_FPS_CHANGE:
+                pr_debug("Received fps change old fps:%d new fps:%d\n",
+                                notification->notif_data.old_fps,
+                                notification->notif_data.new_fps);
+                break;
+        default:
+                pr_debug("notification not serviced :%d\n",
+                                notification->notif_type);
+		break;
+	}
+#if 0
+	if (atomic_read(&tcm_hcd->firmware_flashing)
+		&& transition == DRM_PANEL_BLANK_POWERDOWN) {
+		retval = wait_event_interruptible_timeout(tcm_hcd->reflash_wq,
+				!atomic_read(&tcm_hcd->firmware_flashing),
+				msecs_to_jiffies(RESPONSE_TIMEOUT_MS));
+		if (retval == 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Timed out waiting for flashing firmware\n");
+			atomic_set(&tcm_hcd->firmware_flashing, 0);
+			return -EIO;
+		}
+	}
+
+	if (action == DRM_PANEL_EARLY_EVENT_BLANK &&
+			transition == DRM_PANEL_BLANK_POWERDOWN)
+		retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev);
+	else if (action == DRM_PANEL_EVENT_BLANK) {
+		if (transition == DRM_PANEL_BLANK_POWERDOWN) {
+			retval = syna_tcm_suspend(&tcm_hcd->pdev->dev);
+			tcm_hcd->fb_ready = 0;
+		} else if (transition == DRM_PANEL_BLANK_UNBLANK) {
+#ifndef RESUME_EARLY_UNBLANK
+			retval = syna_tcm_resume(&tcm_hcd->pdev->dev);
+			tcm_hcd->fb_ready++;
+#endif
+		}
+	} else if (action == DRM_PANEL_EARLY_EVENT_BLANK &&
+			transition == DRM_PANEL_BLANK_UNBLANK) {
+#ifdef RESUME_EARLY_UNBLANK
+		retval = syna_tcm_resume(&tcm_hcd->pdev->dev);
+		tcm_hcd->fb_ready++;
+#endif
+	}
+#endif
+
+}
+
+#elif CONFIG_FB
+
+static int syna_tcm_early_suspend(struct device *dev)
+{
+#ifndef WAKEUP_GESTURE
+	int retval;
+#endif
+
+	struct syna_tcm_module_handler *mod_handler;
+	struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev);
+
+	if (tcm_hcd->in_suspend)
+		return 0;
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Application firmware not running\n");
+		return 0;
+	}
+
+#ifndef WAKEUP_GESTURE
+	retval = tcm_hcd->sleep(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enter deep sleep\n");
+		return retval;
+	}
+#endif
+
+	mutex_lock(&mod_pool.mutex);
+
+	if (!list_empty(&mod_pool.list)) {
+		list_for_each_entry(mod_handler, &mod_pool.list, link) {
+			if (!mod_handler->insert &&
+					!mod_handler->detach &&
+					(mod_handler->mod_cb->early_suspend))
+				mod_handler->mod_cb->early_suspend(tcm_hcd);
+		}
+	}
+
+	mutex_unlock(&mod_pool.mutex);
+
+	return 0;
+}
+
+static int syna_tcm_fb_notifier_cb(struct notifier_block *nb,
+		unsigned long action, void *data)
+{
+	int retval = 0;
+	int *transition;
+	struct fb_event *evdata = data;
+	struct syna_tcm_hcd *tcm_hcd =
+			container_of(nb, struct syna_tcm_hcd, fb_notifier);
+
+	if (!evdata || !evdata->data || !tcm_hcd)
+		return 0;
+
+	transition = (int *)evdata->data;
+
+	if (atomic_read(&tcm_hcd->firmware_flashing)
+		&& *transition == FB_BLANK_POWERDOWN) {
+		retval = wait_event_interruptible_timeout(tcm_hcd->reflash_wq,
+				!atomic_read(&tcm_hcd->firmware_flashing),
+				msecs_to_jiffies(RESPONSE_TIMEOUT_MS));
+		if (retval == 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Timed out waiting for flashing firmware\n");
+			atomic_set(&tcm_hcd->firmware_flashing, 0);
+			return -EIO;
+		}
+	}
+
+	if (action == FB_EARLY_EVENT_BLANK &&
+			*transition == FB_BLANK_POWERDOWN)
+		retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev);
+	else if (action == FB_EVENT_BLANK) {
+		if (*transition == FB_BLANK_POWERDOWN) {
+			retval = syna_tcm_suspend(&tcm_hcd->pdev->dev);
+			tcm_hcd->fb_ready = 0;
+		} else if (*transition == FB_BLANK_UNBLANK) {
+#ifndef RESUME_EARLY_UNBLANK
+			retval = syna_tcm_resume(&tcm_hcd->pdev->dev);
+			tcm_hcd->fb_ready++;
+#endif
+		}
+	} else if (action == FB_EARLY_EVENT_BLANK &&
+			*transition == FB_BLANK_UNBLANK) {
+#ifdef RESUME_EARLY_UNBLANK
+		retval = syna_tcm_resume(&tcm_hcd->pdev->dev);
+		tcm_hcd->fb_ready++;
+#endif
+	}
+
+	return 0;
+}
+#endif
+
+static int synaptics_tcm_pinctrl_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval = 0;
+
+	/* Get pinctrl if target uses pinctrl */
+	tcm_hcd->ts_pinctrl = devm_pinctrl_get((tcm_hcd->pdev->dev.parent));
+	if (IS_ERR_OR_NULL(tcm_hcd->ts_pinctrl)) {
+		retval = PTR_ERR(tcm_hcd->ts_pinctrl);
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Target does not use pinctrl %d\n", retval);
+		goto err_pinctrl_get;
+	}
+
+	tcm_hcd->pinctrl_state_active
+		= pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_active");
+	if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_active)) {
+		retval = PTR_ERR(tcm_hcd->pinctrl_state_active);
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Can not lookup %s pinstate %d\n",
+			PINCTRL_STATE_ACTIVE, retval);
+		goto err_pinctrl_lookup;
+	}
+
+	tcm_hcd->pinctrl_state_suspend
+		= pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_suspend");
+	if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_suspend)) {
+		retval = PTR_ERR(tcm_hcd->pinctrl_state_suspend);
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Can not lookup %s pinstate %d\n",
+			PINCTRL_STATE_SUSPEND, retval);
+		goto err_pinctrl_lookup;
+	}
+
+	tcm_hcd->pinctrl_state_release
+		= pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_release");
+	if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_release)) {
+		retval = PTR_ERR(tcm_hcd->pinctrl_state_release);
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Can not lookup %s pinstate %d\n",
+			PINCTRL_STATE_RELEASE, retval);
+	}
+
+	return retval;
+
+err_pinctrl_lookup:
+	devm_pinctrl_put(tcm_hcd->ts_pinctrl);
+err_pinctrl_get:
+	tcm_hcd->ts_pinctrl = NULL;
+	return retval;
+}
+
+static int syna_tcm_probe(struct platform_device *pdev)
+{
+	int retval;
+	int idx;
+	struct syna_tcm_hcd *tcm_hcd;
+	const struct syna_tcm_board_data *bdata;
+	const struct syna_tcm_hw_interface *hw_if;
+	struct drm_panel *active_panel = tcm_get_panel();
+	void *cookie;
+
+	hw_if = pdev->dev.platform_data;
+	if (!hw_if) {
+		LOGE(&pdev->dev,
+				"Hardware interface not found\n");
+		return -ENODEV;
+	}
+
+	bdata = hw_if->bdata;
+	if (!bdata) {
+		LOGE(&pdev->dev,
+				"Board data not found\n");
+		return -ENODEV;
+	}
+
+	tcm_hcd = kzalloc(sizeof(*tcm_hcd), GFP_KERNEL);
+	if (!tcm_hcd) {
+		LOGE(&pdev->dev,
+				"Failed to allocate memory for tcm_hcd\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, tcm_hcd);
+
+	tcm_hcd->pdev = pdev;
+	tcm_hcd->hw_if = hw_if;
+	tcm_hcd->reset = syna_tcm_reset;
+	tcm_hcd->sleep = syna_tcm_sleep;
+	tcm_hcd->identify = syna_tcm_identify;
+	tcm_hcd->enable_irq = syna_tcm_enable_irq;
+	tcm_hcd->switch_mode = syna_tcm_switch_mode;
+	tcm_hcd->read_message = syna_tcm_read_message;
+	tcm_hcd->write_message = syna_tcm_write_message;
+	tcm_hcd->get_dynamic_config = syna_tcm_get_dynamic_config;
+	tcm_hcd->set_dynamic_config = syna_tcm_set_dynamic_config;
+	tcm_hcd->get_data_location = syna_tcm_get_data_location;
+
+	tcm_hcd->rd_chunk_size = RD_CHUNK_SIZE;
+	tcm_hcd->wr_chunk_size = WR_CHUNK_SIZE;
+
+#ifdef PREDICTIVE_READING
+	tcm_hcd->read_length = MIN_READ_LENGTH;
+#else
+	tcm_hcd->read_length = MESSAGE_HEADER_SIZE;
+#endif
+
+	tcm_hcd->watchdog.run = RUN_WATCHDOG;
+	tcm_hcd->update_watchdog = syna_tcm_update_watchdog;
+
+	if (bdata->irq_gpio >= 0)
+		tcm_hcd->irq = gpio_to_irq(bdata->irq_gpio);
+	else
+		tcm_hcd->irq = bdata->irq_gpio;
+
+	mutex_init(&tcm_hcd->extif_mutex);
+	mutex_init(&tcm_hcd->reset_mutex);
+	mutex_init(&tcm_hcd->irq_en_mutex);
+	mutex_init(&tcm_hcd->io_ctrl_mutex);
+	mutex_init(&tcm_hcd->rw_ctrl_mutex);
+	mutex_init(&tcm_hcd->command_mutex);
+	mutex_init(&tcm_hcd->identify_mutex);
+
+	INIT_BUFFER(tcm_hcd->in, false);
+	INIT_BUFFER(tcm_hcd->out, false);
+	INIT_BUFFER(tcm_hcd->resp, true);
+	INIT_BUFFER(tcm_hcd->temp, false);
+	INIT_BUFFER(tcm_hcd->config, false);
+	INIT_BUFFER(tcm_hcd->report.buffer, true);
+
+	LOCK_BUFFER(tcm_hcd->in);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&tcm_hcd->in,
+			tcm_hcd->read_length + 1);
+	if (retval < 0) {
+		LOGE(&pdev->dev,
+			"Failed to allocate memory for tcm_hcd->in.buf\n");
+		UNLOCK_BUFFER(tcm_hcd->in);
+		goto err_alloc_mem;
+	}
+
+	UNLOCK_BUFFER(tcm_hcd->in);
+
+	atomic_set(&tcm_hcd->command_status, CMD_IDLE);
+
+	atomic_set(&tcm_hcd->helper.task, HELP_NONE);
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	init_waitqueue_head(&tcm_hcd->hdl_wq);
+
+	init_waitqueue_head(&tcm_hcd->reflash_wq);
+	atomic_set(&tcm_hcd->firmware_flashing, 0);
+
+	if (!mod_pool.initialized) {
+		mutex_init(&mod_pool.mutex);
+		INIT_LIST_HEAD(&mod_pool.list);
+		mod_pool.initialized = true;
+	}
+
+	retval = syna_tcm_get_regulator(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get regulators\n");
+		goto err_get_regulator;
+	}
+
+	retval = syna_tcm_enable_regulator(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable regulators\n");
+		goto err_enable_regulator;
+	}
+
+	retval = syna_tcm_config_gpio(tcm_hcd);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to configure GPIO's\n");
+		goto err_config_gpio;
+	}
+
+	retval = synaptics_tcm_pinctrl_init(tcm_hcd);
+	if (!retval && tcm_hcd->ts_pinctrl) {
+		retval = pinctrl_select_state(
+				tcm_hcd->ts_pinctrl,
+				tcm_hcd->pinctrl_state_active);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"%s: Failed to select %s pinstate %d\n",
+					__func__, PINCTRL_STATE_ACTIVE, retval);
+		}
+	}
+
+	sysfs_dir = kobject_create_and_add(PLATFORM_DRIVER_NAME,
+			&pdev->dev.kobj);
+	if (!sysfs_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs directory\n");
+		retval = -EINVAL;
+		goto err_sysfs_create_dir;
+	}
+
+	tcm_hcd->sysfs_dir = sysfs_dir;
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+		retval = sysfs_create_file(tcm_hcd->sysfs_dir,
+				&(*attrs[idx]).attr);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to create sysfs file\n");
+			goto err_sysfs_create_file;
+		}
+	}
+
+	tcm_hcd->dynamnic_config_sysfs_dir =
+			kobject_create_and_add(DYNAMIC_CONFIG_SYSFS_DIR_NAME,
+			tcm_hcd->sysfs_dir);
+	if (!tcm_hcd->dynamnic_config_sysfs_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to create dynamic config sysfs directory\n");
+		retval = -EINVAL;
+		goto err_sysfs_create_dynamic_config_dir;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) {
+		retval = sysfs_create_file(tcm_hcd->dynamnic_config_sysfs_dir,
+				&(*dynamic_config_attrs[idx]).attr);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create dynamic config sysfs file\n");
+			goto err_sysfs_create_dynamic_config_file;
+		}
+	}
+
+#ifdef CONFIG_DRM
+	if (active_panel) {
+                cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY,
+                        PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_panel,
+                        &syna_tcm_fb_notifier_cb, tcm_hcd);
+		if (!cookie) {
+			dev_err(&pdev->dev,
+					"%s: Failed to register fb notifier client\n",
+					__func__);
+			goto err_drm_reg;
+		}
+		tcm_hcd->notifier_cookie = cookie;
+	}
+
+#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;
+	touch_module_init();
+	return 0;
+
+
+err_create_run_kthread:
+#ifdef CONFIG_DRM
+	if (active_panel)
+		panel_event_notifier_unregister(tcm_hcd->notifier_cookie);
+#elif CONFIG_FB
+	fb_unregister_client(&tcm_hcd->fb_notifier);
+#endif
+
+err_sysfs_create_dynamic_config_file:
+	for (idx--; idx >= 0; idx--) {
+		sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir,
+				&(*dynamic_config_attrs[idx]).attr);
+	}
+
+	kobject_put(tcm_hcd->dynamnic_config_sysfs_dir);
+
+	idx = ARRAY_SIZE(attrs);
+
+err_sysfs_create_dynamic_config_dir:
+err_sysfs_create_file:
+	for (idx--; idx >= 0; idx--)
+		sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(tcm_hcd->sysfs_dir);
+
+err_sysfs_create_dir:
+	if (bdata->irq_gpio >= 0)
+		syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0);
+
+	if (bdata->power_gpio >= 0)
+		syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0);
+
+	if (bdata->reset_gpio >= 0)
+		syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0);
+
+err_config_gpio:
+	syna_tcm_enable_regulator(tcm_hcd, false);
+
+err_enable_regulator:
+	syna_tcm_get_regulator(tcm_hcd, false);
+
+err_get_regulator:
+	device_init_wakeup(&pdev->dev, 0);
+
+err_alloc_mem:
+	RELEASE_BUFFER(tcm_hcd->report.buffer);
+	RELEASE_BUFFER(tcm_hcd->config);
+	RELEASE_BUFFER(tcm_hcd->temp);
+	RELEASE_BUFFER(tcm_hcd->resp);
+	RELEASE_BUFFER(tcm_hcd->out);
+	RELEASE_BUFFER(tcm_hcd->in);
+
+err_drm_reg:
+	kfree(tcm_hcd);
+
+	return retval;
+}
+
+static int syna_tcm_deferred_probe(struct device *dev)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev);
+
+	retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable interrupt\n");
+		goto err_enable_irq;
+	}
+	retval = tcm_hcd->reset(tcm_hcd, false, false);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do reset\n");
+		tcm_hcd->init_okay = false;
+		tcm_hcd->watchdog.run = false;
+		tcm_hcd->update_watchdog(tcm_hcd, false);
+		tcm_hcd->enable_irq(tcm_hcd, false, false);
+#ifndef KEEP_DRIVER_ON_ERROR
+		goto err_reset;
+#endif
+	} else {
+		tcm_hcd->init_okay = true;
+		tcm_hcd->update_watchdog(tcm_hcd, true);
+	}
+
+	queue_work(mod_pool.workqueue, &mod_pool.work);
+
+	return 0;
+#ifndef KEEP_DRIVER_ON_ERROR
+err_reset:
+#endif
+err_enable_irq:
+
+	return retval;
+}
+
+
+static int syna_tcm_remove(struct platform_device *pdev)
+{
+	int idx;
+	struct syna_tcm_module_handler *mod_handler;
+	struct syna_tcm_module_handler *tmp_handler;
+	struct syna_tcm_hcd *tcm_hcd = platform_get_drvdata(pdev);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+	struct drm_panel *active_panel = tcm_get_panel();
+
+	touch_module_exit();
+	mutex_lock(&mod_pool.mutex);
+
+	if (!list_empty(&mod_pool.list)) {
+		list_for_each_entry_safe(mod_handler, tmp_handler,
+				&mod_pool.list, link) {
+			if (mod_handler->mod_cb->remove)
+				mod_handler->mod_cb->remove(tcm_hcd);
+			list_del(&mod_handler->link);
+			kfree(mod_handler);
+		}
+	}
+
+	mod_pool.queue_work = false;
+	cancel_work_sync(&mod_pool.work);
+	flush_workqueue(mod_pool.workqueue);
+	destroy_workqueue(mod_pool.workqueue);
+
+	mutex_unlock(&mod_pool.mutex);
+
+	if (tcm_hcd->irq_enabled && bdata->irq_gpio >= 0) {
+		disable_irq(tcm_hcd->irq);
+		free_irq(tcm_hcd->irq, tcm_hcd);
+	}
+
+	cancel_delayed_work_sync(&tcm_hcd->polling_work);
+	flush_workqueue(tcm_hcd->polling_workqueue);
+	destroy_workqueue(tcm_hcd->polling_workqueue);
+
+	cancel_delayed_work_sync(&tcm_hcd->watchdog.work);
+	flush_workqueue(tcm_hcd->watchdog.workqueue);
+	destroy_workqueue(tcm_hcd->watchdog.workqueue);
+
+	cancel_work_sync(&tcm_hcd->helper.work);
+	flush_workqueue(tcm_hcd->helper.workqueue);
+	destroy_workqueue(tcm_hcd->helper.workqueue);
+
+	kthread_stop(tcm_hcd->notifier_thread);
+
+#ifdef CONFIG_DRM
+	if (active_panel)
+		panel_event_notifier_unregister(tcm_hcd->notifier_cookie);
+#elif CONFIG_FB
+	fb_unregister_client(&tcm_hcd->fb_notifier);
+#endif
+
+	for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) {
+		sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir,
+				&(*dynamic_config_attrs[idx]).attr);
+	}
+
+	kobject_put(tcm_hcd->dynamnic_config_sysfs_dir);
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++)
+		sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(tcm_hcd->sysfs_dir);
+
+	if (bdata->irq_gpio >= 0)
+		syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0);
+
+	if (bdata->power_gpio >= 0)
+		syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0);
+
+	if (bdata->reset_gpio >= 0)
+		syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0);
+
+	syna_tcm_enable_regulator(tcm_hcd, false);
+
+	syna_tcm_get_regulator(tcm_hcd, false);
+
+	device_init_wakeup(&pdev->dev, 0);
+
+	RELEASE_BUFFER(tcm_hcd->report.buffer);
+	RELEASE_BUFFER(tcm_hcd->config);
+	RELEASE_BUFFER(tcm_hcd->temp);
+	RELEASE_BUFFER(tcm_hcd->resp);
+	RELEASE_BUFFER(tcm_hcd->out);
+	RELEASE_BUFFER(tcm_hcd->in);
+
+	kfree(tcm_hcd);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops syna_tcm_dev_pm_ops = {
+#if !defined(CONFIG_DRM) && !defined(CONFIG_FB)
+	.suspend = syna_tcm_suspend,
+	.resume = syna_tcm_resume,
+#endif
+};
+#endif
+
+static struct platform_driver syna_tcm_driver = {
+	.driver = {
+		.name = PLATFORM_DRIVER_NAME,
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &syna_tcm_dev_pm_ops,
+#endif
+	},
+	.probe = syna_tcm_probe,
+	.remove = syna_tcm_remove,
+};
+
+static int __init syna_tcm_module_init(void)
+{
+	int retval;
+
+	retval = syna_tcm_bus_init();
+	if (retval < 0)
+		return retval;
+
+	return platform_driver_register(&syna_tcm_driver);
+}
+
+static void __exit syna_tcm_module_exit(void)
+{
+	platform_driver_unregister(&syna_tcm_driver);
+
+	syna_tcm_bus_exit();
+}
+
+late_initcall(syna_tcm_module_init);
+module_exit(syna_tcm_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Touch Driver");
+MODULE_LICENSE("GPL v2");

+ 686 - 0
synaptics_tcm/synaptics_tcm_core.h

@@ -0,0 +1,686 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
+ * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
+ * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
+ * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
+ * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
+ * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
+ * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
+ * DOLLARS.
+ */
+
+#ifndef _SYNAPTICS_TCM_CORE_H_
+#define _SYNAPTICS_TCM_CORE_H_
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include "synaptics_tcm.h"
+#ifdef CONFIG_DRM
+#include <drm/drm_panel.h>
+#include <linux/soc/qcom/panel_event_notifier.h>
+#elif CONFIG_FB
+#include <linux/fb.h>
+#include <linux/notifier.h>
+#endif
+#include <uapi/linux/sched/types.h>
+
+#define SYNAPTICS_TCM_ID_PRODUCT (1 << 0)
+#define SYNAPTICS_TCM_ID_VERSION 0x0101
+#define SYNAPTICS_TCM_ID_SUBVERSION 0
+
+#define PLATFORM_DRIVER_NAME "synaptics_tcm"
+
+#define TOUCH_INPUT_NAME "synaptics_tcm_touch"
+#define TOUCH_INPUT_PHYS_PATH "synaptics_tcm/touch_input"
+
+/* #define WAKEUP_GESTURE */
+
+#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */
+#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */
+
+#define MESSAGE_HEADER_SIZE 4
+#define MESSAGE_MARKER 0xa5
+#define MESSAGE_PADDING 0x5a
+
+#define LOGx(func, dev, log, ...) \
+	func(dev, "%s: " log, __func__, ##__VA_ARGS__)
+
+#define LOGy(func, dev, log, ...) \
+	func(dev, "%s (line %d): " log, __func__, __LINE__, ##__VA_ARGS__)
+
+#define LOGD(dev, log, ...) LOGx(dev_dbg, dev, log, ##__VA_ARGS__)
+#define LOGI(dev, log, ...) LOGx(dev_info, dev, log, ##__VA_ARGS__)
+#define LOGN(dev, log, ...) LOGx(dev_notice, dev, log, ##__VA_ARGS__)
+#define LOGW(dev, log, ...) LOGy(dev_warn, dev, log, ##__VA_ARGS__)
+#define LOGE(dev, log, ...) LOGy(dev_err, dev, log, ##__VA_ARGS__)
+
+#define INIT_BUFFER(buffer, is_clone) \
+	mutex_init(&buffer.buf_mutex); \
+	buffer.clone = is_clone
+
+#define LOCK_BUFFER(buffer) \
+	mutex_lock(&buffer.buf_mutex)
+
+#define UNLOCK_BUFFER(buffer) \
+	mutex_unlock(&buffer.buf_mutex)
+
+#define RELEASE_BUFFER(buffer) \
+	do { \
+		if (buffer.clone == false) { \
+			kfree(buffer.buf); \
+			buffer.buf_size = 0; \
+			buffer.data_length = 0; \
+		} \
+	} while (0)
+
+#define MAX(a, b) \
+	({__typeof__(a) _a = (a); \
+	__typeof__(b) _b = (b); \
+	_a > _b ? _a : _b; })
+
+#define MIN(a, b) \
+	({__typeof__(a) _a = (a); \
+	__typeof__(b) _b = (b); \
+	_a < _b ? _a : _b; })
+
+#define STR(x) #x
+
+#define CONCAT(a, b) a##b
+
+#define SHOW_PROTOTYPE(m_name, a_name) \
+static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \
+		struct device_attribute *attr, char *buf); \
+\
+static struct device_attribute dev_attr_##a_name = \
+		__ATTR(a_name, 0444, \
+		CONCAT(m_name##_sysfs, _##a_name##_show), \
+		syna_tcm_store_error)
+
+#define STORE_PROTOTYPE(m_name, a_name) \
+static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \
+		struct device_attribute *attr, const char *buf, size_t count); \
+\
+static struct device_attribute dev_attr_##a_name = \
+		__ATTR(a_name, 0220, \
+		syna_tcm_show_error, \
+		CONCAT(m_name##_sysfs, _##a_name##_store))
+
+#define SHOW_STORE_PROTOTYPE(m_name, a_name) \
+static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \
+		struct device_attribute *attr, char *buf); \
+\
+static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \
+		struct device_attribute *attr, const char *buf, size_t count); \
+\
+static struct device_attribute dev_attr_##a_name = \
+		__ATTR(a_name, 0664, \
+		CONCAT(m_name##_sysfs, _##a_name##_show), \
+		CONCAT(m_name##_sysfs, _##a_name##_store))
+
+#define ATTRIFY(a_name) (&dev_attr_##a_name)
+
+#define PINCTRL_STATE_ACTIVE    "pmx_ts_active"
+#define PINCTRL_STATE_SUSPEND   "pmx_ts_suspend"
+#define PINCTRL_STATE_RELEASE   "pmx_ts_release"
+
+enum module_type {
+	TCM_TOUCH = 0,
+	TCM_DEVICE = 1,
+	TCM_TESTING = 2,
+	TCM_REFLASH = 3,
+	TCM_RECOVERY = 4,
+	TCM_ZEROFLASH = 5,
+	TCM_DIAGNOSTICS = 6,
+	TCM_LAST,
+};
+
+enum boot_mode {
+	MODE_APPLICATION = 0x01,
+	MODE_HOST_DOWNLOAD = 0x02,
+	MODE_BOOTLOADER = 0x0b,
+	MODE_TDDI_BOOTLOADER = 0x0c,
+	MODE_PRODUCTION_TEST = 0x0e,
+};
+
+enum boot_status {
+	BOOT_STATUS_OK = 0x00,
+	BOOT_STATUS_BOOTING = 0x01,
+	BOOT_STATUS_APP_BAD_DISPLAY_CRC = 0xfc,
+	BOOT_STATUS_BAD_DISPLAY_CONFIG = 0xfd,
+	BOOT_STATUS_BAD_APP_FIRMWARE = 0xfe,
+	BOOT_STATUS_WARM_BOOT = 0xff,
+};
+
+enum app_status {
+	APP_STATUS_OK = 0x00,
+	APP_STATUS_BOOTING = 0x01,
+	APP_STATUS_UPDATING = 0x02,
+	APP_STATUS_BAD_APP_CONFIG = 0xff,
+};
+
+enum firmware_mode {
+	FW_MODE_BOOTLOADER = 0,
+	FW_MODE_APPLICATION = 1,
+	FW_MODE_PRODUCTION_TEST = 2,
+};
+
+enum dynamic_config_id {
+	DC_UNKNOWN = 0x00,
+	DC_NO_DOZE,
+	DC_DISABLE_NOISE_MITIGATION,
+	DC_INHIBIT_FREQUENCY_SHIFT,
+	DC_REQUESTED_FREQUENCY,
+	DC_DISABLE_HSYNC,
+	DC_REZERO_ON_EXIT_DEEP_SLEEP,
+	DC_CHARGER_CONNECTED,
+	DC_NO_BASELINE_RELAXATION,
+	DC_IN_WAKEUP_GESTURE_MODE,
+	DC_STIMULUS_FINGERS,
+	DC_GRIP_SUPPRESSION_ENABLED,
+	DC_ENABLE_THICK_GLOVE,
+	DC_ENABLE_GLOVE,
+};
+
+enum command {
+	CMD_NONE = 0x00,
+	CMD_CONTINUE_WRITE = 0x01,
+	CMD_IDENTIFY = 0x02,
+	CMD_RESET = 0x04,
+	CMD_ENABLE_REPORT = 0x05,
+	CMD_DISABLE_REPORT = 0x06,
+	CMD_GET_BOOT_INFO = 0x10,
+	CMD_ERASE_FLASH = 0x11,
+	CMD_WRITE_FLASH = 0x12,
+	CMD_READ_FLASH = 0x13,
+	CMD_RUN_APPLICATION_FIRMWARE = 0x14,
+	CMD_SPI_MASTER_WRITE_THEN_READ = 0x15,
+	CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16,
+	CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f,
+	CMD_GET_APPLICATION_INFO = 0x20,
+	CMD_GET_STATIC_CONFIG = 0x21,
+	CMD_SET_STATIC_CONFIG = 0x22,
+	CMD_GET_DYNAMIC_CONFIG = 0x23,
+	CMD_SET_DYNAMIC_CONFIG = 0x24,
+	CMD_GET_TOUCH_REPORT_CONFIG = 0x25,
+	CMD_SET_TOUCH_REPORT_CONFIG = 0x26,
+	CMD_REZERO = 0x27,
+	CMD_COMMIT_CONFIG = 0x28,
+	CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29,
+	CMD_PRODUCTION_TEST = 0x2a,
+	CMD_SET_CONFIG_ID = 0x2b,
+	CMD_ENTER_DEEP_SLEEP = 0x2c,
+	CMD_EXIT_DEEP_SLEEP = 0x2d,
+	CMD_GET_TOUCH_INFO = 0x2e,
+	CMD_GET_DATA_LOCATION = 0x2f,
+	CMD_DOWNLOAD_CONFIG = 0x30,
+	CMD_ENTER_PRODUCTION_TEST_MODE = 0x31,
+	CMD_GET_FEATURES = 0x32,
+};
+
+enum status_code {
+	STATUS_IDLE = 0x00,
+	STATUS_OK = 0x01,
+	STATUS_BUSY = 0x02,
+	STATUS_CONTINUED_READ = 0x03,
+	STATUS_NOT_EXECUTED_IN_DEEP_SLEEP = 0x0b,
+	STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
+	STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
+	STATUS_NOT_IMPLEMENTED = 0x0e,
+	STATUS_ERROR = 0x0f,
+	STATUS_INVALID = 0xff,
+};
+
+enum report_type {
+	REPORT_IDENTIFY = 0x10,
+	REPORT_TOUCH = 0x11,
+	REPORT_DELTA = 0x12,
+	REPORT_RAW = 0x13,
+	REPORT_STATUS = 0x1b,
+	REPORT_PRINTF = 0x82,
+	REPORT_HDL = 0xfe,
+};
+
+enum command_status {
+	CMD_IDLE = 0,
+	CMD_BUSY = 1,
+	CMD_ERROR = -1,
+};
+
+enum flash_area {
+	BOOTLOADER = 0,
+	BOOT_CONFIG,
+	APP_FIRMWARE,
+	APP_CONFIG,
+	DISP_CONFIG,
+	CUSTOM_OTP,
+	CUSTOM_LCM,
+	CUSTOM_OEM,
+	PPDT,
+};
+
+enum flash_data {
+	LCM_DATA = 1,
+	OEM_DATA,
+	PPDT_DATA,
+};
+
+enum helper_task {
+	HELP_NONE = 0,
+	HELP_RUN_APPLICATION_FIRMWARE,
+	HELP_SEND_RESET_NOTIFICATION,
+};
+
+struct syna_tcm_helper {
+	atomic_t task;
+	struct work_struct work;
+	struct workqueue_struct *workqueue;
+};
+
+struct syna_tcm_watchdog {
+	bool run;
+	unsigned char count;
+	struct delayed_work work;
+	struct workqueue_struct *workqueue;
+};
+
+struct syna_tcm_buffer {
+	bool clone;
+	unsigned char *buf;
+	unsigned int buf_size;
+	unsigned int data_length;
+	struct mutex buf_mutex;
+};
+
+struct syna_tcm_report {
+	unsigned char id;
+	struct syna_tcm_buffer buffer;
+};
+
+struct syna_tcm_identification {
+	unsigned char version;
+	unsigned char mode;
+	unsigned char part_number[16];
+	unsigned char build_id[4];
+	unsigned char max_write_size[2];
+};
+
+struct syna_tcm_boot_info {
+	unsigned char version;
+	unsigned char status;
+	unsigned char asic_id[2];
+	unsigned char write_block_size_words;
+	unsigned char erase_page_size_words[2];
+	unsigned char max_write_payload_size[2];
+	unsigned char last_reset_reason;
+	unsigned char pc_at_time_of_last_reset[2];
+	unsigned char boot_config_start_block[2];
+	unsigned char boot_config_size_blocks[2];
+	unsigned char display_config_start_block[4];
+	unsigned char display_config_length_blocks[2];
+	unsigned char backup_display_config_start_block[4];
+	unsigned char backup_display_config_length_blocks[2];
+	unsigned char custom_otp_start_block[2];
+	unsigned char custom_otp_length_blocks[2];
+};
+
+struct syna_tcm_app_info {
+	unsigned char version[2];
+	unsigned char status[2];
+	unsigned char static_config_size[2];
+	unsigned char dynamic_config_size[2];
+	unsigned char app_config_start_write_block[2];
+	unsigned char app_config_size[2];
+	unsigned char max_touch_report_config_size[2];
+	unsigned char max_touch_report_payload_size[2];
+	unsigned char customer_config_id[16];
+	unsigned char max_x[2];
+	unsigned char max_y[2];
+	unsigned char max_objects[2];
+	unsigned char num_of_buttons[2];
+	unsigned char num_of_image_rows[2];
+	unsigned char num_of_image_cols[2];
+	unsigned char has_hybrid_data[2];
+};
+
+struct syna_tcm_touch_info {
+	unsigned char image_2d_scale_factor[4];
+	unsigned char image_0d_scale_factor[4];
+	unsigned char hybrid_x_scale_factor[4];
+	unsigned char hybrid_y_scale_factor[4];
+};
+
+struct syna_tcm_message_header {
+	unsigned char marker;
+	unsigned char code;
+	unsigned char length[2];
+};
+
+struct syna_tcm_features {
+	unsigned char byte_0_reserved;
+	unsigned char byte_1_reserved;
+	unsigned char dual_firmware:1;
+	unsigned char byte_2_reserved:7;
+} __packed;
+
+struct syna_tcm_hcd {
+	pid_t isr_pid;
+	atomic_t command_status;
+	atomic_t host_downloading;
+	atomic_t firmware_flashing;
+	wait_queue_head_t hdl_wq;
+	wait_queue_head_t reflash_wq;
+	int irq;
+	bool init_okay;
+	bool do_polling;
+	bool in_suspend;
+	bool irq_enabled;
+	bool host_download_mode;
+	unsigned char marker;
+	unsigned char fb_ready;
+	unsigned char command;
+	unsigned char async_report_id;
+	unsigned char status_report_code;
+	unsigned char response_code;
+	unsigned int read_length;
+	unsigned int payload_length;
+	unsigned int packrat_number;
+	unsigned int rd_chunk_size;
+	unsigned int wr_chunk_size;
+	unsigned int app_status;
+	struct platform_device *pdev;
+	struct regulator *pwr_reg;
+	struct regulator *bus_reg;
+	struct kobject *sysfs_dir;
+	struct kobject *dynamnic_config_sysfs_dir;
+	struct mutex extif_mutex;
+	struct mutex reset_mutex;
+	struct mutex irq_en_mutex;
+	struct mutex io_ctrl_mutex;
+	struct mutex rw_ctrl_mutex;
+	struct mutex command_mutex;
+	struct mutex identify_mutex;
+	struct delayed_work polling_work;
+	struct workqueue_struct *polling_workqueue;
+	struct task_struct *notifier_thread;
+	struct pinctrl *ts_pinctrl;
+	struct pinctrl_state *pinctrl_state_active;
+	struct pinctrl_state *pinctrl_state_suspend;
+	struct pinctrl_state *pinctrl_state_release;
+#if defined(CONFIG_DRM) || defined(CONFIG_FB)
+	struct notifier_block fb_notifier;
+#endif
+	void   *notifier_cookie;
+	struct syna_tcm_buffer in;
+	struct syna_tcm_buffer out;
+	struct syna_tcm_buffer resp;
+	struct syna_tcm_buffer temp;
+	struct syna_tcm_buffer config;
+	struct syna_tcm_report report;
+	struct syna_tcm_app_info app_info;
+	struct syna_tcm_boot_info boot_info;
+	struct syna_tcm_touch_info touch_info;
+	struct syna_tcm_identification id_info;
+	struct syna_tcm_helper helper;
+	struct syna_tcm_watchdog watchdog;
+	struct syna_tcm_features features;
+	const struct syna_tcm_hw_interface *hw_if;
+	int (*reset)(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd);
+	int (*sleep)(struct syna_tcm_hcd *tcm_hcd, bool en);
+	int (*identify)(struct syna_tcm_hcd *tcm_hcd, bool id);
+	int (*enable_irq)(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns);
+	int (*switch_mode)(struct syna_tcm_hcd *tcm_hcd,
+			enum firmware_mode mode);
+	int (*read_message)(struct syna_tcm_hcd *tcm_hcd,
+			unsigned char *in_buf, unsigned int length);
+	int (*write_message)(struct syna_tcm_hcd *tcm_hcd,
+			unsigned char command, unsigned char *payload,
+			unsigned int length, unsigned char **resp_buf,
+			unsigned int *resp_buf_size, unsigned int *resp_length,
+			unsigned char *response_code,
+			unsigned int polling_delay_ms);
+	int (*get_dynamic_config)(struct syna_tcm_hcd *tcm_hcd,
+			enum dynamic_config_id id, unsigned short *value);
+	int (*set_dynamic_config)(struct syna_tcm_hcd *tcm_hcd,
+			enum dynamic_config_id id, unsigned short value);
+	int (*get_data_location)(struct syna_tcm_hcd *tcm_hcd,
+			enum flash_area area, unsigned int *addr,
+			unsigned int *length);
+	int (*read_flash_data)(enum flash_area area, bool run_app_firmware,
+			struct syna_tcm_buffer *output);
+	void (*report_touch)(void);
+	void (*update_watchdog)(struct syna_tcm_hcd *tcm_hcd, bool en);
+};
+
+struct syna_tcm_module_cb {
+	enum module_type type;
+	int (*init)(struct syna_tcm_hcd *tcm_hcd);
+	int (*remove)(struct syna_tcm_hcd *tcm_hcd);
+	int (*syncbox)(struct syna_tcm_hcd *tcm_hcd);
+	int (*asyncbox)(struct syna_tcm_hcd *tcm_hcd);
+	int (*reset)(struct syna_tcm_hcd *tcm_hcd);
+	int (*suspend)(struct syna_tcm_hcd *tcm_hcd);
+	int (*resume)(struct syna_tcm_hcd *tcm_hcd);
+	int (*early_suspend)(struct syna_tcm_hcd *tcm_hcd);
+};
+
+struct syna_tcm_module_handler {
+	bool insert;
+	bool detach;
+	struct list_head link;
+	struct syna_tcm_module_cb *mod_cb;
+};
+
+struct syna_tcm_module_pool {
+	bool initialized;
+	bool queue_work;
+	bool reconstructing;
+	struct mutex mutex;
+	struct list_head list;
+	struct work_struct work;
+	struct workqueue_struct *workqueue;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+struct syna_tcm_bus_io {
+	unsigned char type;
+	int (*rmi_read)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr,
+			unsigned char *data, unsigned int length);
+	int (*rmi_write)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr,
+			unsigned char *data, unsigned int length);
+	int (*read)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data,
+			unsigned int length);
+	int (*write)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data,
+			unsigned int length);
+};
+
+struct syna_tcm_hw_interface {
+	struct syna_tcm_board_data *bdata;
+	const struct syna_tcm_bus_io *bus_io;
+};
+
+struct drm_panel *tcm_get_panel(void);
+
+int syna_tcm_bus_init(void);
+
+void syna_tcm_bus_exit(void);
+
+int syna_tcm_bus_init_spi(void);
+
+void syna_tcm_bus_exit_spi(void);
+
+int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert);
+
+static inline int syna_tcm_rmi_read(struct syna_tcm_hcd *tcm_hcd,
+		unsigned short addr, unsigned char *data, unsigned int length)
+{
+	return tcm_hcd->hw_if->bus_io->rmi_read(tcm_hcd, addr, data, length);
+}
+
+static inline int syna_tcm_rmi_write(struct syna_tcm_hcd *tcm_hcd,
+		unsigned short addr, unsigned char *data, unsigned int length)
+{
+	return tcm_hcd->hw_if->bus_io->rmi_write(tcm_hcd, addr, data, length);
+}
+
+static inline int syna_tcm_read(struct syna_tcm_hcd *tcm_hcd,
+		unsigned char *data, unsigned int length)
+{
+	return tcm_hcd->hw_if->bus_io->read(tcm_hcd, data, length);
+}
+
+static inline int syna_tcm_write(struct syna_tcm_hcd *tcm_hcd,
+		unsigned char *data, unsigned int length)
+{
+	return tcm_hcd->hw_if->bus_io->write(tcm_hcd, data, length);
+}
+
+static inline ssize_t syna_tcm_show_error(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	pr_err("%s: Attribute not readable\n",
+			__func__);
+
+	return -EPERM;
+}
+
+static inline ssize_t syna_tcm_store_error(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	pr_err("%s: Attribute not writable\n",
+			__func__);
+
+	return -EPERM;
+}
+
+static inline int secure_memcpy(unsigned char *dest, unsigned int dest_size,
+		const unsigned char *src, unsigned int src_size,
+		unsigned int count)
+{
+	if (dest == NULL || src == NULL)
+		return -EINVAL;
+
+	if (count > dest_size || count > src_size) {
+		pr_err("%s: src_size = %d, dest_size = %d, count = %d\n",
+				__func__, src_size, dest_size, count);
+		return -EINVAL;
+	}
+
+	memcpy((void *)dest, (const void *)src, count);
+
+	return 0;
+}
+
+static inline int syna_tcm_realloc_mem(struct syna_tcm_hcd *tcm_hcd,
+		struct syna_tcm_buffer *buffer, unsigned int size)
+{
+	int retval;
+	unsigned char *temp;
+
+	if (size > buffer->buf_size) {
+		temp = buffer->buf;
+
+		buffer->buf = kmalloc(size, GFP_KERNEL);
+		if (!(buffer->buf)) {
+			dev_err(tcm_hcd->pdev->dev.parent,
+					"%s: Failed to allocate memory\n",
+					__func__);
+			kfree(temp);
+			buffer->buf_size = 0;
+			return -ENOMEM;
+		}
+
+		retval = secure_memcpy(buffer->buf,
+				size,
+				temp,
+				buffer->buf_size,
+				buffer->buf_size);
+		if (retval < 0) {
+			dev_err(tcm_hcd->pdev->dev.parent,
+					"%s: Failed to copy data\n",
+					__func__);
+			kfree(temp);
+			kfree(buffer->buf);
+			buffer->buf_size = 0;
+			return retval;
+		}
+
+		kfree(temp);
+		buffer->buf_size = size;
+	}
+
+	return 0;
+}
+
+static inline int syna_tcm_alloc_mem(struct syna_tcm_hcd *tcm_hcd,
+		struct syna_tcm_buffer *buffer, unsigned int size)
+{
+	if (size > buffer->buf_size) {
+		kfree(buffer->buf);
+		buffer->buf = kmalloc(size, GFP_KERNEL);
+		if (!(buffer->buf)) {
+			dev_err(tcm_hcd->pdev->dev.parent,
+					"%s: Failed to allocate memory\n",
+					__func__);
+			dev_err(tcm_hcd->pdev->dev.parent,
+					"%s: Allocation size = %d\n",
+					__func__, size);
+			buffer->buf_size = 0;
+			buffer->data_length = 0;
+			return -ENOMEM;
+		}
+		buffer->buf_size = size;
+	}
+
+	memset(buffer->buf, 0x00, buffer->buf_size);
+	buffer->data_length = 0;
+
+	return 0;
+}
+
+static inline unsigned int le2_to_uint(const unsigned char *src)
+{
+	return (unsigned int)src[0] +
+			(unsigned int)src[1] * 0x100;
+}
+
+static inline unsigned int le4_to_uint(const unsigned char *src)
+{
+	return (unsigned int)src[0] +
+			(unsigned int)src[1] * 0x100 +
+			(unsigned int)src[2] * 0x10000 +
+			(unsigned int)src[3] * 0x1000000;
+}
+
+static inline unsigned int ceil_div(unsigned int dividend,
+		unsigned int divisor)
+{
+	return (dividend + divisor - 1) / divisor;
+}
+
+#endif

+ 707 - 0
synaptics_tcm/synaptics_tcm_device.c

@@ -0,0 +1,707 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/cdev.h>
+#include <linux/gpio.h>
+#include <linux/uaccess.h>
+#include "synaptics_tcm_core.h"
+
+#define CHAR_DEVICE_NAME "tcm"
+
+#define CONCURRENT true
+
+#define DEVICE_IOC_MAGIC 's'
+#define DEVICE_IOC_RESET _IO(DEVICE_IOC_MAGIC, 0) /* 0x00007300 */
+#define DEVICE_IOC_IRQ _IOW(DEVICE_IOC_MAGIC, 1, int) /* 0x40047301 */
+#define DEVICE_IOC_RAW _IOW(DEVICE_IOC_MAGIC, 2, int) /* 0x40047302 */
+#define DEVICE_IOC_CONCURRENT _IOW(DEVICE_IOC_MAGIC, 3, int) /* 0x40047303 */
+
+struct device_hcd {
+	dev_t dev_num;
+	bool raw_mode;
+	bool concurrent;
+	unsigned int ref_count;
+	struct cdev char_dev;
+	struct class *class;
+	struct device *device;
+	struct syna_tcm_buffer out;
+	struct syna_tcm_buffer resp;
+	struct syna_tcm_buffer report;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+DECLARE_COMPLETION(device_remove_complete);
+
+static struct device_hcd *device_hcd;
+
+static int rmidev_major_num;
+
+static void device_capture_touch_report(unsigned int count)
+{
+	int retval;
+	unsigned char id;
+	unsigned int idx;
+	unsigned int size;
+	unsigned char *data;
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+	static bool report;
+	static unsigned int offset;
+	static unsigned int remaining_size;
+
+	if (count < 2)
+		return;
+
+	data = &device_hcd->resp.buf[0];
+
+	if (data[0] != MESSAGE_MARKER)
+		return;
+
+	id = data[1];
+
+	size = 0;
+
+	LOCK_BUFFER(device_hcd->report);
+
+	switch (id) {
+	case REPORT_TOUCH:
+		if (count >= 4) {
+			remaining_size = le2_to_uint(&data[2]);
+		} else {
+			report = false;
+			goto exit;
+		}
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				&device_hcd->report,
+				remaining_size);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for report.buf\n");
+			report = false;
+			goto exit;
+		}
+		idx = 4;
+		size = count - idx;
+		offset = 0;
+		report = true;
+		break;
+	case STATUS_CONTINUED_READ:
+		if (report == false)
+			goto exit;
+		if (count >= 2) {
+			idx = 2;
+			size = count - idx;
+		}
+		break;
+	default:
+		goto exit;
+	}
+
+	if (size) {
+		size = MIN(size, remaining_size);
+		retval = secure_memcpy(&device_hcd->report.buf[offset],
+				device_hcd->report.buf_size - offset,
+				&data[idx],
+				count - idx,
+				size);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy touch report data\n");
+			report = false;
+			goto exit;
+		} else {
+			offset += size;
+			remaining_size -= size;
+			device_hcd->report.data_length += size;
+		}
+	}
+
+	if (remaining_size)
+		goto exit;
+
+	LOCK_BUFFER(tcm_hcd->report.buffer);
+
+	tcm_hcd->report.buffer.buf = device_hcd->report.buf;
+	tcm_hcd->report.buffer.buf_size = device_hcd->report.buf_size;
+	tcm_hcd->report.buffer.data_length = device_hcd->report.data_length;
+
+	tcm_hcd->report_touch();
+
+	UNLOCK_BUFFER(tcm_hcd->report.buffer);
+
+	report = false;
+
+exit:
+	UNLOCK_BUFFER(device_hcd->report);
+}
+
+static int device_capture_touch_report_config(unsigned int count)
+{
+	int retval;
+	unsigned int size;
+	unsigned int buf_size;
+	unsigned char *data;
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	if (device_hcd->raw_mode) {
+		if (count < 3) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Invalid write data\n");
+			return -EINVAL;
+		}
+
+		size = le2_to_uint(&device_hcd->out.buf[1]);
+
+		if (count - 3 < size) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Incomplete write data\n");
+			return -EINVAL;
+		}
+
+		if (!size)
+			return 0;
+
+		data = &device_hcd->out.buf[3];
+		buf_size = device_hcd->out.buf_size - 3;
+	} else {
+		size = count - 1;
+
+		if (!size)
+			return 0;
+
+		data = &device_hcd->out.buf[1];
+		buf_size = device_hcd->out.buf_size - 1;
+	}
+
+	LOCK_BUFFER(tcm_hcd->config);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&tcm_hcd->config,
+			size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for tcm_hcd->config.buf\n");
+		UNLOCK_BUFFER(tcm_hcd->config);
+		return retval;
+	}
+
+	retval = secure_memcpy(tcm_hcd->config.buf,
+			tcm_hcd->config.buf_size,
+			data,
+			buf_size,
+			size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy touch report config data\n");
+		UNLOCK_BUFFER(tcm_hcd->config);
+		return retval;
+	}
+
+	tcm_hcd->config.data_length = size;
+
+	UNLOCK_BUFFER(tcm_hcd->config);
+
+	return 0;
+}
+
+#ifdef HAVE_UNLOCKED_IOCTL
+static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+#else
+static int device_ioctl(struct inode *inp, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+#endif
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = 0;
+
+	switch (cmd) {
+	case DEVICE_IOC_RESET:
+		retval = tcm_hcd->reset(tcm_hcd, false, true);
+		break;
+	case DEVICE_IOC_IRQ:
+		if (arg == 0)
+			retval = tcm_hcd->enable_irq(tcm_hcd, false, false);
+		else if (arg == 1)
+			retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
+		break;
+	case DEVICE_IOC_RAW:
+		if (arg == 0) {
+			device_hcd->raw_mode = false;
+			tcm_hcd->update_watchdog(tcm_hcd, true);
+		} else if (arg == 1) {
+			device_hcd->raw_mode = true;
+			tcm_hcd->update_watchdog(tcm_hcd, false);
+		}
+		break;
+	case DEVICE_IOC_CONCURRENT:
+		if (arg == 0)
+			device_hcd->concurrent = false;
+		else if (arg == 1)
+			device_hcd->concurrent = true;
+		break;
+	default:
+		retval = -ENOTTY;
+		break;
+	}
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static loff_t device_llseek(struct file *filp, loff_t off, int whence)
+{
+	return -EINVAL;
+}
+
+static ssize_t device_read(struct file *filp, char __user *buf,
+		size_t count, loff_t *f_pos)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	if (count == 0)
+		return 0;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	LOCK_BUFFER(device_hcd->resp);
+
+	if (device_hcd->raw_mode) {
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				&device_hcd->resp,
+				count);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for resp.buf\n");
+			UNLOCK_BUFFER(device_hcd->resp);
+			goto exit;
+		}
+
+		retval = tcm_hcd->read_message(tcm_hcd,
+				device_hcd->resp.buf,
+				count);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to read message\n");
+			UNLOCK_BUFFER(device_hcd->resp);
+			goto exit;
+		}
+	} else {
+		if (count != device_hcd->resp.data_length) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Invalid length information\n");
+			UNLOCK_BUFFER(device_hcd->resp);
+			retval = -EINVAL;
+			goto exit;
+		}
+	}
+
+	if (copy_to_user(buf, device_hcd->resp.buf, count)) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy data to user space\n");
+		UNLOCK_BUFFER(device_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	if (!device_hcd->concurrent)
+		goto skip_concurrent;
+
+	if (tcm_hcd->report_touch == NULL) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Unable to report touch\n");
+		device_hcd->concurrent = false;
+	}
+
+	if (device_hcd->raw_mode)
+		device_capture_touch_report(count);
+
+skip_concurrent:
+	UNLOCK_BUFFER(device_hcd->resp);
+
+	retval = count;
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t device_write(struct file *filp, const char __user *buf,
+		size_t count, loff_t *f_pos)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	if (count == 0)
+		return 0;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	LOCK_BUFFER(device_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&device_hcd->out,
+			count == 1 ? count + 1 : count);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for device_hcd->out.buf\n");
+		UNLOCK_BUFFER(device_hcd->out);
+		goto exit;
+	}
+
+	if (copy_from_user(device_hcd->out.buf, buf, count)) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to copy data from user space\n");
+		UNLOCK_BUFFER(device_hcd->out);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	LOCK_BUFFER(device_hcd->resp);
+
+	if (device_hcd->raw_mode) {
+		retval = tcm_hcd->write_message(tcm_hcd,
+				device_hcd->out.buf[0],
+				&device_hcd->out.buf[1],
+				count - 1,
+				NULL,
+				NULL,
+				NULL,
+				NULL,
+				0);
+	} else {
+		mutex_lock(&tcm_hcd->reset_mutex);
+		retval = tcm_hcd->write_message(tcm_hcd,
+				device_hcd->out.buf[0],
+				&device_hcd->out.buf[1],
+				count - 1,
+				&device_hcd->resp.buf,
+				&device_hcd->resp.buf_size,
+				&device_hcd->resp.data_length,
+				NULL,
+				0);
+		mutex_unlock(&tcm_hcd->reset_mutex);
+	}
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to write command 0x%02x\n",
+			device_hcd->out.buf[0]);
+		UNLOCK_BUFFER(device_hcd->resp);
+		UNLOCK_BUFFER(device_hcd->out);
+		goto exit;
+	}
+
+	if (count && device_hcd->out.buf[0] == CMD_SET_TOUCH_REPORT_CONFIG) {
+		retval = device_capture_touch_report_config(count);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to capture touch report config\n");
+		}
+	}
+
+	UNLOCK_BUFFER(device_hcd->out);
+
+	if (device_hcd->raw_mode)
+		retval = count;
+	else
+		retval = device_hcd->resp.data_length;
+
+	UNLOCK_BUFFER(device_hcd->resp);
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static int device_open(struct inode *inp, struct file *filp)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (device_hcd->ref_count < 1) {
+		device_hcd->ref_count++;
+		retval = 0;
+	} else {
+		retval = -EACCES;
+	}
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static int device_release(struct inode *inp, struct file *filp)
+{
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (device_hcd->ref_count)
+		device_hcd->ref_count--;
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return 0;
+}
+
+static char *device_devnode(struct device *dev, umode_t *mode)
+{
+	if (!mode)
+		return NULL;
+
+	/* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; */
+	*mode = 0666;
+
+	return kasprintf(GFP_KERNEL, "%s/%s", PLATFORM_DRIVER_NAME,
+			dev_name(dev));
+}
+
+static int device_create_class(void)
+{
+	struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
+
+	if (device_hcd->class != NULL)
+		return 0;
+
+	device_hcd->class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME);
+
+	if (IS_ERR(device_hcd->class)) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create class\n");
+		return -ENODEV;
+	}
+
+	device_hcd->class->devnode = device_devnode;
+
+	return 0;
+}
+
+static const struct file_operations device_fops = {
+	.owner = THIS_MODULE,
+#ifdef HAVE_UNLOCKED_IOCTL
+	.unlocked_ioctl = device_ioctl,
+#ifdef HAVE_COMPAT_IOCTL
+	.compat_ioctl = device_ioctl,
+#endif
+#else
+	.ioctl = device_ioctl,
+#endif
+	.llseek = device_llseek,
+	.read = device_read,
+	.write = device_write,
+	.open = device_open,
+	.release = device_release,
+};
+
+static int device_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	dev_t dev_num;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	device_hcd = kzalloc(sizeof(*device_hcd), GFP_KERNEL);
+	if (!device_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for device_hcd\n");
+		return -ENOMEM;
+	}
+
+	device_hcd->tcm_hcd = tcm_hcd;
+
+	device_hcd->concurrent = CONCURRENT;
+
+	INIT_BUFFER(device_hcd->out, false);
+	INIT_BUFFER(device_hcd->resp, false);
+	INIT_BUFFER(device_hcd->report, false);
+
+	if (rmidev_major_num) {
+		dev_num = MKDEV(rmidev_major_num, 0);
+		retval = register_chrdev_region(dev_num, 1,
+				PLATFORM_DRIVER_NAME);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to register char device\n");
+			goto err_register_chrdev_region;
+		}
+	} else {
+		retval = alloc_chrdev_region(&dev_num, 0, 1,
+				PLATFORM_DRIVER_NAME);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to allocate char device\n");
+			goto err_alloc_chrdev_region;
+		}
+
+		rmidev_major_num = MAJOR(dev_num);
+	}
+
+	device_hcd->dev_num = dev_num;
+
+	cdev_init(&device_hcd->char_dev, &device_fops);
+
+	retval = cdev_add(&device_hcd->char_dev, dev_num, 1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to add char device\n");
+		goto err_add_chardev;
+	}
+
+	retval = device_create_class();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create class\n");
+		goto err_create_class;
+	}
+
+	device_hcd->device = device_create(device_hcd->class, NULL,
+			device_hcd->dev_num, NULL, CHAR_DEVICE_NAME"%d",
+			MINOR(device_hcd->dev_num));
+	if (IS_ERR(device_hcd->device)) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create device\n");
+		retval = -ENODEV;
+		goto err_create_device;
+	}
+
+	if (bdata->irq_gpio >= 0) {
+		retval = gpio_export(bdata->irq_gpio, false);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to export GPIO\n");
+		} else {
+			retval = gpio_export_link(&tcm_hcd->pdev->dev,
+					"attn", bdata->irq_gpio);
+			if (retval < 0)
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to export GPIO link\n");
+		}
+	}
+
+	return 0;
+
+err_create_device:
+	class_destroy(device_hcd->class);
+
+err_create_class:
+	cdev_del(&device_hcd->char_dev);
+
+err_add_chardev:
+	unregister_chrdev_region(dev_num, 1);
+
+err_alloc_chrdev_region:
+err_register_chrdev_region:
+	RELEASE_BUFFER(device_hcd->report);
+	RELEASE_BUFFER(device_hcd->resp);
+	RELEASE_BUFFER(device_hcd->out);
+
+	kfree(device_hcd);
+	device_hcd = NULL;
+
+	return retval;
+}
+
+static int device_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!device_hcd)
+		goto exit;
+
+	device_destroy(device_hcd->class, device_hcd->dev_num);
+
+	class_destroy(device_hcd->class);
+
+	cdev_del(&device_hcd->char_dev);
+
+	unregister_chrdev_region(device_hcd->dev_num, 1);
+
+	RELEASE_BUFFER(device_hcd->report);
+	RELEASE_BUFFER(device_hcd->resp);
+	RELEASE_BUFFER(device_hcd->out);
+
+	kfree(device_hcd);
+	device_hcd = NULL;
+
+exit:
+	complete(&device_remove_complete);
+
+	return 0;
+}
+
+static int device_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!device_hcd) {
+		retval = device_init(tcm_hcd);
+		return retval;
+	}
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb device_module = {
+	.type = TCM_DEVICE,
+	.init = device_init,
+	.remove = device_remove,
+	.syncbox = NULL,
+	.asyncbox = NULL,
+	.reset = device_reset,
+	.suspend = NULL,
+	.resume = NULL,
+	.early_suspend = NULL,
+};
+
+static int __init device_module_init(void)
+{
+	return syna_tcm_add_module(&device_module, true);
+}
+
+static void __exit device_module_exit(void)
+{
+	syna_tcm_add_module(&device_module, false);
+
+	wait_for_completion(&device_remove_complete);
+}
+
+module_init(device_module_init);
+module_exit(device_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Device Module");
+MODULE_LICENSE("GPL v2");

+ 561 - 0
synaptics_tcm/synaptics_tcm_diagnostics.c

@@ -0,0 +1,561 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/sched/signal.h>
+#include "synaptics_tcm_core.h"
+
+#define SYSFS_DIR_NAME "diagnostics"
+
+enum pingpong_state {
+	PING = 0,
+	PONG = 1,
+};
+
+struct diag_hcd {
+	pid_t pid;
+	unsigned char report_type;
+	enum pingpong_state state;
+	struct kobject *sysfs_dir;
+	struct siginfo sigio;
+	struct task_struct *task;
+	struct syna_tcm_buffer ping;
+	struct syna_tcm_buffer pong;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+DECLARE_COMPLETION(diag_remove_complete);
+
+static struct diag_hcd *diag_hcd;
+
+STORE_PROTOTYPE(diag, pid);
+SHOW_PROTOTYPE(diag, size);
+STORE_PROTOTYPE(diag, type);
+SHOW_PROTOTYPE(diag, rows);
+SHOW_PROTOTYPE(diag, cols);
+SHOW_PROTOTYPE(diag, hybrid);
+SHOW_PROTOTYPE(diag, buttons);
+
+static struct device_attribute *attrs[] = {
+	ATTRIFY(pid),
+	ATTRIFY(size),
+	ATTRIFY(type),
+	ATTRIFY(rows),
+	ATTRIFY(cols),
+	ATTRIFY(hybrid),
+	ATTRIFY(buttons),
+};
+
+static ssize_t diag_sysfs_data_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static struct bin_attribute bin_attr = {
+	.attr = {
+		.name = "data",
+		.mode = 0444,
+	},
+	.size = 0,
+	.read = diag_sysfs_data_show,
+};
+
+static ssize_t diag_sysfs_pid_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int retval;
+	unsigned int input;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	if (kstrtouint(buf, 10, &input))
+		return -EINVAL;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	diag_hcd->pid = input;
+
+	if (diag_hcd->pid) {
+		diag_hcd->task = pid_task(find_vpid(diag_hcd->pid),
+				PIDTYPE_PID);
+		if (!diag_hcd->task) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to locate task\n");
+			retval = -EINVAL;
+			goto exit;
+		}
+	}
+
+	retval = count;
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t diag_sysfs_size_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (diag_hcd->state == PING) {
+		LOCK_BUFFER(diag_hcd->ping);
+
+		retval = snprintf(buf, PAGE_SIZE,
+				"%u\n",
+				diag_hcd->ping.data_length);
+
+		UNLOCK_BUFFER(diag_hcd->ping);
+	} else {
+		LOCK_BUFFER(diag_hcd->pong);
+
+		retval = snprintf(buf, PAGE_SIZE,
+				"%u\n",
+				diag_hcd->pong.data_length);
+
+		UNLOCK_BUFFER(diag_hcd->pong);
+	}
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t diag_sysfs_type_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned int input;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	if (kstrtouint(buf, 10, &input))
+		return -EINVAL;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	diag_hcd->report_type = (unsigned char)input;
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return count;
+}
+
+static ssize_t diag_sysfs_rows_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	unsigned int rows;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		retval = -ENODEV;
+		goto exit;
+	}
+
+	app_info = &tcm_hcd->app_info;
+	rows = le2_to_uint(app_info->num_of_image_rows);
+
+	retval = snprintf(buf, PAGE_SIZE, "%u\n", rows);
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t diag_sysfs_cols_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	unsigned int cols;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		retval = -ENODEV;
+		goto exit;
+	}
+
+	app_info = &tcm_hcd->app_info;
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	retval = snprintf(buf, PAGE_SIZE, "%u\n", cols);
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t diag_sysfs_hybrid_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	unsigned int hybrid;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		retval = -ENODEV;
+		goto exit;
+	}
+
+	app_info = &tcm_hcd->app_info;
+	hybrid = le2_to_uint(app_info->has_hybrid_data);
+
+	retval = snprintf(buf, PAGE_SIZE, "%u\n", hybrid);
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t diag_sysfs_buttons_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	unsigned int buttons;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		retval = -ENODEV;
+		goto exit;
+	}
+
+	app_info = &tcm_hcd->app_info;
+	buttons = le2_to_uint(app_info->num_of_buttons);
+
+	retval = snprintf(buf, PAGE_SIZE, "%u\n", buttons);
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t diag_sysfs_data_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	unsigned int readlen;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = 0;
+
+	if (diag_hcd->state == PING) {
+		LOCK_BUFFER(diag_hcd->ping);
+
+		if (diag_hcd->ping.data_length == 0) {
+			readlen = 0;
+			goto exit;
+		}
+
+		readlen = MIN(count, diag_hcd->ping.data_length - pos);
+
+		if (diag_hcd->ping.data_length) {
+			retval = secure_memcpy(buf,
+					count,
+					&diag_hcd->ping.buf[pos],
+					diag_hcd->ping.buf_size - pos,
+					readlen);
+		}
+
+		UNLOCK_BUFFER(diag_hcd->ping);
+	} else {
+		LOCK_BUFFER(diag_hcd->pong);
+
+		if (diag_hcd->pong.data_length == 0) {
+			readlen = 0;
+			goto exit;
+		}
+
+		readlen = MIN(count, diag_hcd->pong.data_length - pos);
+
+		if (diag_hcd->pong.data_length) {
+			retval = secure_memcpy(buf,
+					count,
+					&diag_hcd->pong.buf[pos],
+					diag_hcd->pong.buf_size - pos,
+					readlen);
+		}
+
+		UNLOCK_BUFFER(diag_hcd->pong);
+	}
+
+exit:
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy report data\n");
+	} else {
+		retval = readlen;
+	}
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static void diag_report(void)
+{
+	int retval;
+	static enum pingpong_state state = PING;
+	struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
+
+	if (state == PING) {
+		LOCK_BUFFER(diag_hcd->ping);
+
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				&diag_hcd->ping,
+				tcm_hcd->report.buffer.data_length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for ping.buf\n");
+			UNLOCK_BUFFER(diag_hcd->ping);
+			return;
+		}
+
+		retval = secure_memcpy(diag_hcd->ping.buf,
+				diag_hcd->ping.buf_size,
+				tcm_hcd->report.buffer.buf,
+				tcm_hcd->report.buffer.buf_size,
+				tcm_hcd->report.buffer.data_length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy report data\n");
+			UNLOCK_BUFFER(diag_hcd->ping);
+			return;
+		}
+
+		diag_hcd->ping.data_length = tcm_hcd->report.buffer.data_length;
+
+		UNLOCK_BUFFER(diag_hcd->ping);
+
+		diag_hcd->state = state;
+		state = PONG;
+	} else {
+		LOCK_BUFFER(diag_hcd->pong);
+
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				&diag_hcd->pong,
+				tcm_hcd->report.buffer.data_length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for pong.buf\n");
+			UNLOCK_BUFFER(diag_hcd->pong);
+			return;
+		}
+
+		retval = secure_memcpy(diag_hcd->pong.buf,
+				diag_hcd->pong.buf_size,
+				tcm_hcd->report.buffer.buf,
+				tcm_hcd->report.buffer.buf_size,
+				tcm_hcd->report.buffer.data_length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy report data\n");
+			UNLOCK_BUFFER(diag_hcd->pong);
+			return;
+		}
+
+		diag_hcd->pong.data_length = tcm_hcd->report.buffer.data_length;
+
+		UNLOCK_BUFFER(diag_hcd->pong);
+
+		diag_hcd->state = state;
+		state = PING;
+	}
+}
+
+static int diag_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	int idx;
+
+	diag_hcd = kzalloc(sizeof(*diag_hcd), GFP_KERNEL);
+	if (!diag_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for diag_hcd\n");
+		return -ENOMEM;
+	}
+
+	diag_hcd->tcm_hcd = tcm_hcd;
+	diag_hcd->state = PING;
+
+	INIT_BUFFER(diag_hcd->ping, false);
+	INIT_BUFFER(diag_hcd->pong, false);
+
+	memset(&diag_hcd->sigio, 0x00, sizeof(diag_hcd->sigio));
+	diag_hcd->sigio.si_signo = SIGIO;
+	diag_hcd->sigio.si_code = SI_USER;
+
+	diag_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
+			tcm_hcd->sysfs_dir);
+	if (!diag_hcd->sysfs_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs directory\n");
+		retval = -EINVAL;
+		goto err_sysfs_create_dir;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+		retval = sysfs_create_file(diag_hcd->sysfs_dir,
+				&(*attrs[idx]).attr);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to create sysfs file\n");
+			goto err_sysfs_create_file;
+		}
+	}
+
+	retval = sysfs_create_bin_file(diag_hcd->sysfs_dir, &bin_attr);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs bin file\n");
+		goto err_sysfs_create_bin_file;
+	}
+
+	return 0;
+
+err_sysfs_create_bin_file:
+err_sysfs_create_file:
+	for (idx--; idx >= 0; idx--)
+		sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(diag_hcd->sysfs_dir);
+
+err_sysfs_create_dir:
+	RELEASE_BUFFER(diag_hcd->pong);
+	RELEASE_BUFFER(diag_hcd->ping);
+
+	kfree(diag_hcd);
+	diag_hcd = NULL;
+
+	return retval;
+}
+
+static int diag_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	int idx;
+
+	if (!diag_hcd)
+		goto exit;
+
+	sysfs_remove_bin_file(diag_hcd->sysfs_dir, &bin_attr);
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++)
+		sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(diag_hcd->sysfs_dir);
+
+	RELEASE_BUFFER(diag_hcd->pong);
+	RELEASE_BUFFER(diag_hcd->ping);
+
+	kfree(diag_hcd);
+	diag_hcd = NULL;
+
+exit:
+	complete(&diag_remove_complete);
+
+	return 0;
+}
+
+static int diag_syncbox(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!diag_hcd)
+		return 0;
+
+	if (tcm_hcd->report.id == diag_hcd->report_type)
+		diag_report();
+
+	return 0;
+}
+
+static int diag_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!diag_hcd) {
+		retval = diag_init(tcm_hcd);
+		return retval;
+	}
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb diag_module = {
+	.type = TCM_DIAGNOSTICS,
+	.init = diag_init,
+	.remove = diag_remove,
+	.syncbox = diag_syncbox,
+	.asyncbox = NULL,
+	.reset = diag_reset,
+	.suspend = NULL,
+	.resume = NULL,
+	.early_suspend = NULL,
+};
+
+static int __init diag_module_init(void)
+{
+	return syna_tcm_add_module(&diag_module, true);
+}
+
+static void __exit diag_module_exit(void)
+{
+	syna_tcm_add_module(&diag_module, false);
+
+	wait_for_completion(&diag_remove_complete);
+}
+
+module_init(diag_module_init);
+module_exit(diag_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Diagnostics Module");
+MODULE_LICENSE("GPL v2");

+ 523 - 0
synaptics_tcm/synaptics_tcm_i2c.c

@@ -0,0 +1,523 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/i2c.h>
+#include <linux/of_gpio.h>
+#include "synaptics_tcm_core.h"
+#include "linux/moduleparam.h"
+
+#define XFER_ATTEMPTS 10
+
+static unsigned char *buf;
+
+static unsigned int buf_size;
+
+static struct syna_tcm_bus_io bus_io;
+
+static struct syna_tcm_hw_interface hw_if;
+
+static struct platform_device *syna_tcm_i2c_device;
+
+static struct drm_panel *active_tcm_panel;
+
+struct drm_panel *tcm_get_panel(void)
+{
+	return active_tcm_panel;
+}
+EXPORT_SYMBOL(tcm_get_panel);
+
+#ifdef CONFIG_OF
+static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata)
+{
+	int retval;
+	struct device_node *np = dev->of_node;
+
+	retval = of_get_named_gpio_flags(np,
+			"synaptics,irq-gpio", 0,
+			(enum of_gpio_flags *)&bdata->irq_flags);
+	if (!gpio_is_valid(retval)) {
+		if (retval != -EPROBE_DEFER)
+			dev_err(dev, "Error getting irq_gpio\n");
+		return retval;
+	}
+	bdata->irq_gpio = retval;
+
+	of_property_read_u32(np, "synaptics,irq-on-state",
+			&bdata->irq_on_state);
+	of_property_read_string(np, "synaptics,pwr-reg-name",
+			&bdata->pwr_reg_name);
+	of_property_read_string(np, "synaptics,bus-reg-name",
+			&bdata->bus_reg_name);
+	of_property_read_string(np, "synaptics,firmware-name",
+			&bdata->fw_name);
+
+	bdata->power_gpio = of_get_named_gpio_flags(np,
+			"synaptics,power-gpio", 0, NULL);
+
+	retval = of_property_read_u32(np, "synaptics,power-on-state",
+			&bdata->power_on_state);
+	if (retval < 0) {
+		LOGD(dev, "Failed to read synaptics,power-on-state\n");
+		bdata->power_on_state = 0;
+	}
+
+	retval = of_property_read_u32(np, "synaptics,power-delay-ms",
+			&bdata->power_delay_ms);
+	if (retval < 0) {
+		LOGE(dev, "Failed to read synaptics,power-delay-ms\n");
+		return retval;
+	}
+
+	retval = of_get_named_gpio_flags(np,
+			"synaptics,reset-gpio", 0, NULL);
+	if (!gpio_is_valid(retval)) {
+		if (retval != -EPROBE_DEFER)
+			dev_err(dev, "Error getting reset gpio\n");
+		return retval;
+	}
+	bdata->reset_gpio = retval;
+
+	retval = of_property_read_u32(np, "synaptics,reset-on-state",
+			&bdata->reset_on_state);
+	if (retval < 0) {
+		LOGE(dev, "Failed to read synaptics,reset-on-state\n");
+		return retval;
+	}
+
+	retval = of_property_read_u32(np, "synaptics,reset-active-ms",
+			&bdata->reset_active_ms);
+	if (retval < 0) {
+		LOGE(dev, "Failed to read synaptics,reset-active-ms\n");
+		return retval;
+	}
+
+	retval = of_property_read_u32(np, "synaptics,reset-delay-ms",
+			&bdata->reset_delay_ms);
+	if (retval < 0) {
+		LOGE(dev, "Unable to read synaptics,reset-delay-ms\n");
+		return retval;
+	}
+
+	bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip");
+	bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip");
+	bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes");
+
+	retval = of_property_read_u32(np, "synaptics,ubl-i2c-addr",
+			&bdata->ubl_i2c_addr);
+	if (retval < 0) {
+		LOGE(dev, "Unable to read synaptics,ubl-i2c-addr\n");
+		return retval;
+	}
+
+	bdata->extend_report = of_property_read_bool(np,
+			"synaptics,extend_report");
+
+	return 0;
+}
+#endif
+
+static int syna_tcm_i2c_alloc_mem(struct syna_tcm_hcd *tcm_hcd,
+		unsigned int size)
+{
+	struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent);
+
+	if (size > buf_size) {
+		if (buf_size)
+			kfree(buf);
+		buf = kmalloc(size, GFP_KERNEL);
+		if (!buf) {
+			LOGE(&i2c->dev,
+					"Failed to allocate memory for buf\n");
+			buf_size = 0;
+			return -ENOMEM;
+		}
+		buf_size = size;
+	}
+
+	return 0;
+}
+
+static int syna_tcm_i2c_rmi_read(struct syna_tcm_hcd *tcm_hcd,
+		unsigned short addr, unsigned char *data, unsigned int length)
+{
+	int retval = 0;
+	unsigned char address;
+	unsigned int attempt;
+	struct i2c_msg msg[2];
+	struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	address = (unsigned char)addr;
+
+	msg[0].addr = bdata->ubl_i2c_addr;
+	msg[0].flags = 0;
+	msg[0].len = 1;
+	msg[0].buf = &address;
+
+	msg[1].addr = bdata->ubl_i2c_addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].len = length;
+	msg[1].buf = data;
+
+	for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) {
+		if (i2c_transfer(i2c->adapter, msg, 2) == 2) {
+			retval = length;
+			goto exit;
+		}
+
+		LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1);
+
+		if (attempt + 1 == XFER_ATTEMPTS) {
+			LOGE(&i2c->dev, "Transfer failed\n");
+			retval = -EIO;
+			goto exit;
+		}
+
+		msleep(20);
+	}
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_i2c_rmi_write(struct syna_tcm_hcd *tcm_hcd,
+		unsigned short addr, unsigned char *data, unsigned int length)
+{
+	int retval;
+	unsigned int attempt;
+	unsigned int byte_count;
+	struct i2c_msg msg;
+	struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	byte_count = length + 1;
+
+	retval = syna_tcm_i2c_alloc_mem(tcm_hcd, byte_count);
+	if (retval < 0) {
+		LOGE(&i2c->dev,
+				"Failed to allocate memory\n");
+		goto exit;
+	}
+
+	buf[0] = (unsigned char)addr;
+	retval = secure_memcpy(&buf[1],
+			buf_size - 1,
+			data,
+			length,
+			length);
+	if (retval < 0) {
+		LOGE(&i2c->dev,
+				"Failed to copy write data\n");
+		goto exit;
+	}
+
+	msg.addr = bdata->ubl_i2c_addr;
+	msg.flags = 0;
+	msg.len = byte_count;
+	msg.buf = buf;
+
+	for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) {
+		if (i2c_transfer(i2c->adapter, &msg, 1) == 1) {
+			retval = length;
+			goto exit;
+		}
+
+		LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1);
+
+		if (attempt + 1 == XFER_ATTEMPTS) {
+			LOGE(&i2c->dev, "Transfer failed\n");
+			retval = -EIO;
+			goto exit;
+		}
+
+		msleep(20);
+	}
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_i2c_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data,
+		unsigned int length)
+{
+	int retval = 0;
+	unsigned int attempt;
+	struct i2c_msg msg;
+	struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent);
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	msg.addr = i2c->addr;
+	msg.flags = I2C_M_RD;
+	msg.len = length;
+	msg.buf = data;
+
+	for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) {
+		if (i2c_transfer(i2c->adapter, &msg, 1) == 1) {
+			retval = length;
+			goto exit;
+		}
+
+		LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1);
+
+		if (attempt + 1 == XFER_ATTEMPTS) {
+			LOGE(&i2c->dev, "Transfer failed\n");
+			retval = -EIO;
+			goto exit;
+		}
+
+		msleep(20);
+	}
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_i2c_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data,
+		unsigned int length)
+{
+	int retval = 0;
+	unsigned int attempt;
+	struct i2c_msg msg;
+	struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent);
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	msg.addr = i2c->addr;
+	msg.flags = 0;
+	msg.len = length;
+	msg.buf = data;
+
+	for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) {
+		if (i2c_transfer(i2c->adapter, &msg, 1) == 1) {
+			retval = length;
+			goto exit;
+		}
+
+		LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1);
+
+		if (attempt + 1 == XFER_ATTEMPTS) {
+			LOGE(&i2c->dev, "Transfer failed\n");
+			retval = -EIO;
+			goto exit;
+		}
+
+		msleep(20);
+	}
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_check_dt(struct device_node *np)
+{
+	int i;
+	int count;
+	struct device_node *node;
+	struct drm_panel *panel;
+
+	count = of_count_phandle_with_args(np, "panel", NULL);
+	if (count <= 0)
+		return 0;
+
+	for (i = 0; i < count; i++) {
+		node = of_parse_phandle(np, "panel", i);
+		panel = of_drm_find_panel(node);
+		of_node_put(node);
+		if (!IS_ERR(panel)) {
+			active_tcm_panel = panel;
+			return 0;
+		}
+	}
+
+	return PTR_ERR(panel);
+}
+
+static int syna_tcm_check_default_tp(struct device_node *dt, const char *prop)
+{
+	const char *active_tp;
+	const char *compatible;
+	char *start;
+	int ret;
+
+	ret = of_property_read_string(dt->parent, prop, &active_tp);
+	if (ret) {
+		pr_err(" %s:fail to read %s %d\n", __func__, prop, ret);
+		return -ENODEV;
+	}
+
+	ret = of_property_read_string(dt, "compatible", &compatible);
+	if (ret < 0) {
+		pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret);
+		return -ENODEV;
+	}
+
+	start = strnstr(active_tp, compatible, strlen(active_tp));
+	if (start == NULL) {
+		pr_err(" %s:no match compatible, %s, %s\n",
+			__func__, compatible, active_tp);
+		ret = -ENODEV;
+	}
+
+	return ret;
+}
+
+static int syna_tcm_i2c_probe(struct i2c_client *i2c,
+		const struct i2c_device_id *dev_id)
+{
+	int retval;
+	struct device_node *dt = i2c->dev.of_node;
+
+	retval = syna_tcm_check_dt(dt);
+	if (retval == -EPROBE_DEFER)
+		return retval;
+
+	if (retval) {
+		if (!syna_tcm_check_default_tp(dt, "qcom,i2c-touch-active"))
+			retval = -EPROBE_DEFER;
+		else
+			retval = -ENODEV;
+
+		return retval;
+	}
+
+	syna_tcm_i2c_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0);
+	if (!syna_tcm_i2c_device) {
+		LOGE(&i2c->dev,
+				"Failed to allocate platform device\n");
+		return -ENOMEM;
+	}
+
+#ifdef CONFIG_OF
+	hw_if.bdata = devm_kzalloc(&i2c->dev, sizeof(*hw_if.bdata), GFP_KERNEL);
+	if (!hw_if.bdata) {
+		LOGE(&i2c->dev,
+				"Failed to allocate memory for board data\n");
+		return -ENOMEM;
+	}
+	retval = parse_dt(&i2c->dev, hw_if.bdata);
+	if (retval < 0) {
+		LOGE(&i2c->dev, "Failed to parse dt\n");
+		return retval;
+	}
+#else
+	hw_if.bdata = i2c->dev.platform_data;
+#endif
+
+	bus_io.type = BUS_I2C;
+	bus_io.read = syna_tcm_i2c_read;
+	bus_io.write = syna_tcm_i2c_write;
+	bus_io.rmi_read = syna_tcm_i2c_rmi_read;
+	bus_io.rmi_write = syna_tcm_i2c_rmi_write;
+
+	hw_if.bus_io = &bus_io;
+
+	syna_tcm_i2c_device->dev.parent = &i2c->dev;
+	syna_tcm_i2c_device->dev.platform_data = &hw_if;
+
+	retval = platform_device_add(syna_tcm_i2c_device);
+	if (retval < 0) {
+		LOGE(&i2c->dev,
+				"Failed to add platform device\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int syna_tcm_i2c_remove(struct i2c_client *i2c)
+{
+	syna_tcm_i2c_device->dev.platform_data = NULL;
+
+	platform_device_unregister(syna_tcm_i2c_device);
+
+	return 0;
+}
+
+static const struct i2c_device_id syna_tcm_id_table[] = {
+	{I2C_MODULE_NAME, 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, syna_tcm_id_table);
+
+#ifdef CONFIG_OF
+static const struct of_device_id syna_tcm_of_match_table[] = {
+	{
+		.compatible = "synaptics,tcm-i2c",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table);
+#else
+#define syna_tcm_of_match_table NULL
+#endif
+
+static struct i2c_driver syna_tcm_i2c_driver = {
+	.driver = {
+		.name = I2C_MODULE_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = syna_tcm_of_match_table,
+	},
+	.probe = syna_tcm_i2c_probe,
+	.remove = syna_tcm_i2c_remove,
+	.id_table = syna_tcm_id_table,
+};
+
+int syna_tcm_bus_init(void)
+{
+	return i2c_add_driver(&syna_tcm_i2c_driver);
+}
+EXPORT_SYMBOL(syna_tcm_bus_init);
+
+void syna_tcm_bus_exit(void)
+{
+	kfree(buf);
+
+	i2c_del_driver(&syna_tcm_i2c_driver);
+}
+EXPORT_SYMBOL(syna_tcm_bus_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM I2C Bus Module");
+MODULE_LICENSE("GPL v2");

+ 898 - 0
synaptics_tcm/synaptics_tcm_recovery.c

@@ -0,0 +1,898 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
+ * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
+ * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
+ * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
+ * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
+ * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
+ * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
+ * DOLLARS.
+ */
+
+#include "synaptics_tcm_core.h"
+
+#define SET_UP_RECOVERY_MODE true
+
+#define ENABLE_SYSFS_INTERFACE true
+
+#define SYSFS_DIR_NAME "recovery"
+
+#define IHEX_BUF_SIZE (2048 * 1024)
+
+#define DATA_BUF_SIZE (512 * 1024)
+
+#define IHEX_RECORD_SIZE 14
+
+#define PDT_START_ADDR 0x00e9
+
+#define UBL_FN_NUMBER 0x35
+
+#define F35_CHUNK_SIZE 16
+
+#define F35_CHUNK_SIZE_WORDS 8
+
+#define F35_ERASE_ALL_WAIT_MS 5000
+
+#define F35_ERASE_ALL_POLL_MS 100
+
+#define F35_DATA5_OFFSET 5
+
+#define F35_CTRL3_OFFSET 18
+
+#define F35_RESET_COMMAND 16
+
+#define F35_ERASE_ALL_COMMAND 3
+
+#define F35_WRITE_CHUNK_COMMAND 2
+
+#define F35_READ_FLASH_STATUS_COMMAND 1
+
+struct rmi_pdt_entry {
+	unsigned char query_base_addr;
+	unsigned char command_base_addr;
+	unsigned char control_base_addr;
+	unsigned char data_base_addr;
+	unsigned char intr_src_count:3;
+	unsigned char reserved_1:2;
+	unsigned char fn_version:2;
+	unsigned char reserved_2:1;
+	unsigned char fn_number;
+} __packed;
+
+struct rmi_addr {
+	unsigned short query_base;
+	unsigned short command_base;
+	unsigned short control_base;
+	unsigned short data_base;
+};
+
+struct recovery_hcd {
+	bool set_up_recovery_mode;
+	unsigned char chunk_buf[F35_CHUNK_SIZE + 3];
+	unsigned char out_buf[3];
+	unsigned char *ihex_buf;
+	unsigned char *data_buf;
+	unsigned int ihex_size;
+	unsigned int ihex_records;
+	unsigned int data_entries;
+	struct kobject *sysfs_dir;
+	struct rmi_addr f35_addr;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+DECLARE_COMPLETION(recovery_remove_complete);
+
+static struct recovery_hcd *recovery_hcd;
+
+static int recovery_do_recovery(void);
+
+STORE_PROTOTYPE(recovery, recovery);
+
+static struct device_attribute *attrs[] = {
+	ATTRIFY(recovery),
+};
+
+static ssize_t recovery_sysfs_ihex_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static struct bin_attribute bin_attr = {
+	.attr = {
+		.name = "ihex",
+		.mode = 0220,
+	},
+	.size = 0,
+	.write = recovery_sysfs_ihex_store,
+};
+
+static ssize_t recovery_sysfs_recovery_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int retval;
+	unsigned int input;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	if (kstrtouint(buf, 10, &input))
+		return -EINVAL;
+
+	if (input == 1)
+		recovery_hcd->set_up_recovery_mode = true;
+	else if (input == 2)
+		recovery_hcd->set_up_recovery_mode = false;
+	else
+		return -EINVAL;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	if (recovery_hcd->ihex_size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get ihex data\n");
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	if (recovery_hcd->ihex_size % IHEX_RECORD_SIZE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid ihex data\n");
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	recovery_hcd->ihex_records = recovery_hcd->ihex_size / IHEX_RECORD_SIZE;
+
+	retval = recovery_do_recovery();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do recovery\n");
+		goto exit;
+	}
+
+	retval = count;
+
+exit:
+	recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE;
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t recovery_sysfs_ihex_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = secure_memcpy(&recovery_hcd->ihex_buf[pos],
+			IHEX_BUF_SIZE - pos,
+			buf,
+			count,
+			count);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy ihex data\n");
+		recovery_hcd->ihex_size = 0;
+		goto exit;
+	}
+
+	recovery_hcd->ihex_size = pos + count;
+
+	retval = count;
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static int recovery_device_reset(void)
+{
+	int retval;
+	unsigned char command;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	command = F35_RESET_COMMAND;
+
+	retval = syna_tcm_rmi_write(tcm_hcd,
+			recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET,
+			&command,
+			sizeof(command));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write F$35 command\n");
+		return retval;
+	}
+
+	msleep(bdata->reset_delay_ms);
+
+	return 0;
+}
+
+static int recovery_add_data_entry(unsigned char data)
+{
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	if (recovery_hcd->data_entries >= DATA_BUF_SIZE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Reached data buffer size limit\n");
+		return -EINVAL;
+	}
+
+	recovery_hcd->data_buf[recovery_hcd->data_entries++] = data;
+
+	return 0;
+}
+
+static int recovery_add_padding(unsigned int *words)
+{
+	int retval;
+	unsigned int padding;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	padding = (F35_CHUNK_SIZE_WORDS - *words % F35_CHUNK_SIZE_WORDS);
+	padding %= F35_CHUNK_SIZE_WORDS;
+
+	while (padding) {
+		retval = recovery_add_data_entry(0xff);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to add data entry\n");
+			return retval;
+		}
+
+		retval = recovery_add_data_entry(0xff);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to add data entry\n");
+			return retval;
+		}
+
+		(*words)++;
+		padding--;
+	}
+
+	return 0;
+}
+
+static int recovery_parse_ihex(void)
+{
+	int retval;
+	unsigned char colon;
+	unsigned char *buf;
+	unsigned int addr;
+	unsigned int type;
+	unsigned int addrl;
+	unsigned int addrh;
+	unsigned int data0;
+	unsigned int data1;
+	unsigned int count;
+	unsigned int words;
+	unsigned int offset;
+	unsigned int record;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	words = 0;
+
+	offset = 0;
+
+	buf = recovery_hcd->ihex_buf;
+
+	recovery_hcd->data_entries = 0;
+
+	for (record = 0; record < recovery_hcd->ihex_records; record++) {
+		buf[(record + 1) * IHEX_RECORD_SIZE - 1] = 0x00;
+		retval = sscanf(&buf[record * IHEX_RECORD_SIZE],
+				"%c%02x%02x%02x%02x%02x%02x",
+				&colon,
+				&count,
+				&addrh,
+				&addrl,
+				&type,
+				&data0,
+				&data1);
+		if (retval != 7) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to read ihex record\n");
+			return -EINVAL;
+		}
+
+		if (type == 0x00) {
+			if ((words % F35_CHUNK_SIZE_WORDS) == 0) {
+				addr = (addrh << 8) + addrl;
+				addr += offset;
+				addr >>= 4;
+
+				retval = recovery_add_data_entry(addr);
+				if (retval < 0) {
+					LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to add data entry\n");
+					return retval;
+				}
+
+				retval = recovery_add_data_entry(addr >> 8);
+				if (retval < 0) {
+					LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to add data entry\n");
+					return retval;
+				}
+			}
+
+			retval = recovery_add_data_entry(data0);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to add data entry\n");
+				return retval;
+			}
+
+			retval = recovery_add_data_entry(data1);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to add data entry\n");
+				return retval;
+			}
+
+			words++;
+		} else if (type == 0x02) {
+			retval = recovery_add_padding(&words);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to add padding\n");
+				return retval;
+			}
+
+			offset = (data0 << 8) + data1;
+			offset <<= 4;
+		}
+	}
+
+	retval = recovery_add_padding(&words);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to add padding\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int recovery_check_status(void)
+{
+	int retval;
+	unsigned char status;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	retval = syna_tcm_rmi_read(tcm_hcd,
+			recovery_hcd->f35_addr.data_base,
+			&status,
+			sizeof(status));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read status\n");
+		return retval;
+	}
+
+	status = status & 0x1f;
+
+	if (status != 0x00) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Recovery mode status = 0x%02x\n",
+				status);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int recovery_write_flash(void)
+{
+	int retval;
+	unsigned char *data_ptr;
+	unsigned int chunk_buf_size;
+	unsigned int chunk_data_size;
+	unsigned int entries_written;
+	unsigned int entries_to_write;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	entries_written = 0;
+
+	data_ptr = recovery_hcd->data_buf;
+
+	chunk_buf_size = sizeof(recovery_hcd->chunk_buf);
+
+	chunk_data_size = chunk_buf_size - 1;
+
+	recovery_hcd->chunk_buf[chunk_buf_size - 1] = F35_WRITE_CHUNK_COMMAND;
+
+	while (entries_written < recovery_hcd->data_entries) {
+		entries_to_write = F35_CHUNK_SIZE + 2;
+
+		retval = secure_memcpy(recovery_hcd->chunk_buf,
+				chunk_buf_size - 1,
+				data_ptr,
+				recovery_hcd->data_entries - entries_written,
+				entries_to_write);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy chunk data\n");
+			return retval;
+		}
+
+		retval = syna_tcm_rmi_write(tcm_hcd,
+				recovery_hcd->f35_addr.control_base,
+				recovery_hcd->chunk_buf,
+				chunk_buf_size);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to write chunk data\n");
+			return retval;
+		}
+
+		data_ptr += entries_to_write;
+		entries_written += entries_to_write;
+	}
+
+	retval = recovery_check_status();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to get no error recovery mode status\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int recovery_poll_erase_completion(void)
+{
+	int retval;
+	unsigned char status;
+	unsigned char command;
+	unsigned char data_base;
+	unsigned int timeout;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	timeout = F35_ERASE_ALL_WAIT_MS;
+
+	data_base = recovery_hcd->f35_addr.data_base;
+
+	do {
+		command = F35_READ_FLASH_STATUS_COMMAND;
+
+		retval = syna_tcm_rmi_write(tcm_hcd,
+				recovery_hcd->f35_addr.command_base,
+				&command,
+				sizeof(command));
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to write F$35 command\n");
+			return retval;
+		}
+
+		do {
+			retval = syna_tcm_rmi_read(tcm_hcd,
+					recovery_hcd->f35_addr.command_base,
+					&command,
+					sizeof(command));
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to read command status\n");
+				return retval;
+			}
+
+			if (command == 0x00)
+				break;
+
+			if (timeout == 0)
+				break;
+
+			msleep(F35_ERASE_ALL_POLL_MS);
+			timeout -= F35_ERASE_ALL_POLL_MS;
+		} while (true);
+
+		if (command != 0 && timeout == 0) {
+			retval = -EINVAL;
+			goto exit;
+		}
+
+		retval = syna_tcm_rmi_read(tcm_hcd,
+				data_base + F35_DATA5_OFFSET,
+				&status,
+				sizeof(status));
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to read flash status\n");
+			return retval;
+		}
+
+		if ((status & 0x01) == 0x00)
+			break;
+
+		if (timeout == 0) {
+			retval = -EINVAL;
+			goto exit;
+		}
+
+		msleep(F35_ERASE_ALL_POLL_MS);
+		timeout -= F35_ERASE_ALL_POLL_MS;
+	} while (true);
+
+	retval = 0;
+
+exit:
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get erase completion\n");
+	}
+
+	return retval;
+}
+
+static int recovery_erase_flash(void)
+{
+	int retval;
+	unsigned char command;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	command = F35_ERASE_ALL_COMMAND;
+
+	retval = syna_tcm_rmi_write(tcm_hcd,
+			recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET,
+			&command,
+			sizeof(command));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write F$35 command\n");
+		return retval;
+	}
+
+	if (recovery_hcd->f35_addr.command_base) {
+		retval = recovery_poll_erase_completion();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to wait for erase completion\n");
+			return retval;
+		}
+	} else {
+		msleep(F35_ERASE_ALL_WAIT_MS);
+	}
+
+	retval = recovery_check_status();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to get no error recovery mode status\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int recovery_set_up_recovery_mode(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	retval = tcm_hcd->identify(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		return retval;
+	}
+
+	if (tcm_hcd->id_info.mode == MODE_APPLICATION) {
+		retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enter bootloader mode\n");
+			return retval;
+		}
+	}
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			recovery_hcd->out_buf[0],
+			&recovery_hcd->out_buf[1],
+			2,
+			NULL,
+			NULL,
+			NULL,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_REBOOT_TO_ROM_BOOTLOADER));
+		return retval;
+	}
+
+	msleep(bdata->reset_delay_ms);
+
+	return 0;
+}
+
+static int recovery_do_recovery(void)
+{
+	int retval;
+	struct rmi_pdt_entry p_entry;
+	struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
+
+	retval = recovery_parse_ihex();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to parse ihex data\n");
+		return retval;
+	}
+
+	if (recovery_hcd->set_up_recovery_mode) {
+		retval = recovery_set_up_recovery_mode();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to set up recovery mode\n");
+			return retval;
+		}
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	retval = syna_tcm_rmi_read(tcm_hcd,
+			PDT_START_ADDR,
+			(unsigned char *)&p_entry,
+			sizeof(p_entry));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read PDT entry\n");
+		return retval;
+	}
+
+	if (p_entry.fn_number != UBL_FN_NUMBER) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to find F$35\n");
+		return -ENODEV;
+	}
+
+	recovery_hcd->f35_addr.query_base = p_entry.query_base_addr;
+	recovery_hcd->f35_addr.command_base = p_entry.command_base_addr;
+	recovery_hcd->f35_addr.control_base = p_entry.control_base_addr;
+	recovery_hcd->f35_addr.data_base = p_entry.data_base_addr;
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Start of recovery\n");
+
+	retval = recovery_erase_flash();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to erase flash\n");
+		return retval;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Flash erased\n");
+
+	retval = recovery_write_flash();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write to flash\n");
+		return retval;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Flash written\n");
+
+	retval = recovery_device_reset();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do reset\n");
+		return retval;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"End of recovery\n");
+
+	if (recovery_hcd->set_up_recovery_mode)
+		return 0;
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable interrupt\n");
+		return retval;
+	}
+
+	retval = tcm_hcd->identify(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		return retval;
+	}
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION) {
+		retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to run application firmware\n");
+			return retval;
+		}
+	}
+
+	return 0;
+}
+
+static int recovery_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval = 0;
+	int idx;
+
+	recovery_hcd = kzalloc(sizeof(*recovery_hcd), GFP_KERNEL);
+	if (!recovery_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for recovery_hcd\n");
+		return -ENOMEM;
+	}
+
+	recovery_hcd->ihex_buf = kzalloc(IHEX_BUF_SIZE, GFP_KERNEL);
+	if (!recovery_hcd->ihex_buf) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for ihex_buf\n");
+		goto err_allocate_ihex_buf;
+	}
+
+	recovery_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL);
+	if (!recovery_hcd->data_buf) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for data_buf\n");
+		goto err_allocate_data_buf;
+	}
+
+	recovery_hcd->tcm_hcd = tcm_hcd;
+
+	recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE;
+
+	recovery_hcd->out_buf[0] = CMD_REBOOT_TO_ROM_BOOTLOADER;
+	recovery_hcd->out_buf[1] = 0;
+	recovery_hcd->out_buf[2] = 0;
+
+	if (!ENABLE_SYSFS_INTERFACE)
+		return 0;
+
+	recovery_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
+			tcm_hcd->sysfs_dir);
+	if (!recovery_hcd->sysfs_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs directory\n");
+		retval = -EINVAL;
+		goto err_sysfs_create_dir;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+		retval = sysfs_create_file(recovery_hcd->sysfs_dir,
+				&(*attrs[idx]).attr);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to create sysfs file\n");
+			goto err_sysfs_create_file;
+		}
+	}
+
+	retval = sysfs_create_bin_file(recovery_hcd->sysfs_dir, &bin_attr);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs bin file\n");
+		goto err_sysfs_create_bin_file;
+	}
+
+	return 0;
+
+err_sysfs_create_bin_file:
+err_sysfs_create_file:
+	for (idx--; idx >= 0; idx--)
+		sysfs_remove_file(recovery_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(recovery_hcd->sysfs_dir);
+
+err_sysfs_create_dir:
+	kfree(recovery_hcd->data_buf);
+err_allocate_data_buf:
+	kfree(recovery_hcd->ihex_buf);
+err_allocate_ihex_buf:
+	kfree(recovery_hcd);
+	recovery_hcd = NULL;
+
+	return retval;
+}
+
+static int recovery_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	int idx;
+
+	if (!recovery_hcd)
+		goto exit;
+
+	if (ENABLE_SYSFS_INTERFACE) {
+		sysfs_remove_bin_file(recovery_hcd->sysfs_dir, &bin_attr);
+
+		for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+			sysfs_remove_file(recovery_hcd->sysfs_dir,
+					&(*attrs[idx]).attr);
+		}
+
+		kobject_put(recovery_hcd->sysfs_dir);
+	}
+
+	kfree(recovery_hcd->data_buf);
+	kfree(recovery_hcd->ihex_buf);
+	kfree(recovery_hcd);
+	recovery_hcd = NULL;
+
+exit:
+	complete(&recovery_remove_complete);
+
+	return 0;
+}
+
+static int recovery_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!recovery_hcd) {
+		retval = recovery_init(tcm_hcd);
+		return retval;
+	}
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb recovery_module = {
+	.type = TCM_RECOVERY,
+	.init = recovery_init,
+	.remove = recovery_remove,
+	.syncbox = NULL,
+	.asyncbox = NULL,
+	.reset = recovery_reset,
+	.suspend = NULL,
+	.resume = NULL,
+	.early_suspend = NULL,
+};
+
+static int __init recovery_module_init(void)
+{
+	return syna_tcm_add_module(&recovery_module, true);
+}
+
+static void __exit recovery_module_exit(void)
+{
+	syna_tcm_add_module(&recovery_module, false);
+
+	wait_for_completion(&recovery_remove_complete);
+}
+
+module_init(recovery_module_init);
+module_exit(recovery_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Recovery Module");
+MODULE_LICENSE("GPL v2");

+ 2193 - 0
synaptics_tcm/synaptics_tcm_reflash.c

@@ -0,0 +1,2193 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/crc32.h>
+#include <linux/firmware.h>
+#include "synaptics_tcm_core.h"
+
+#define STARTUP_REFLASH
+
+#define FORCE_REFLASH false
+
+#define ENABLE_SYSFS_INTERFACE true
+
+#define SYSFS_DIR_NAME "reflash"
+
+#define CUSTOM_DIR_NAME "custom"
+
+#define FW_IMAGE_NAME "synaptics_firmware.img"
+
+#define BOOT_CONFIG_ID "BOOT_CONFIG"
+
+#define APP_CODE_ID "APP_CODE"
+
+#define PROD_TEST_ID "APP_PROD_TEST"
+
+#define APP_CONFIG_ID "APP_CONFIG"
+
+#define DISP_CONFIG_ID "DISPLAY"
+
+#define FB_READY_COUNT 2
+
+#define FB_READY_WAIT_MS 100
+
+#define FB_READY_TIMEOUT_S 80
+
+#define IMAGE_FILE_MAGIC_VALUE 0x4818472b
+
+#define FLASH_AREA_MAGIC_VALUE 0x7c05e516
+
+#define BOOT_CONFIG_SIZE 8
+
+#define BOOT_CONFIG_SLOTS 16
+
+#define IMAGE_BUF_SIZE (512 * 1024)
+
+#define ERASE_FLASH_DELAY_MS 500
+
+#define WRITE_FLASH_DELAY_MS 20
+
+#define REFLASH (1 << 0)
+
+#define FORCE_UPDATE (1 << 1)
+
+#define APP_CFG_UPDATE (1 << 2)
+
+#define DISP_CFG_UPDATE (1 << 3)
+
+#define BOOT_CFG_UPDATE (1 << 4)
+
+#define BOOT_CFG_LOCKDOWN (1 << 5)
+
+#define reflash_write(p_name) \
+static int reflash_write_##p_name(void) \
+{ \
+	int retval; \
+	unsigned int size; \
+	unsigned int flash_addr; \
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \
+	const unsigned char *data; \
+\
+	data = reflash_hcd->image_info.p_name.data; \
+	size = reflash_hcd->image_info.p_name.size; \
+	flash_addr = reflash_hcd->image_info.p_name.flash_addr; \
+\
+	retval = reflash_write_flash(flash_addr, data, size); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to write to flash\n"); \
+		return retval; \
+	} \
+\
+	return 0; \
+}
+
+#define reflash_erase(p_name) \
+static int reflash_erase_##p_name(void) \
+{ \
+	int retval; \
+	unsigned int size; \
+	unsigned int flash_addr; \
+	unsigned int page_start; \
+	unsigned int page_count; \
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \
+\
+	flash_addr = reflash_hcd->image_info.p_name.flash_addr; \
+\
+	page_start = flash_addr / reflash_hcd->page_size; \
+\
+	size = reflash_hcd->image_info.p_name.size; \
+	page_count = ceil_div(size, reflash_hcd->page_size); \
+\
+	LOGD(tcm_hcd->pdev->dev.parent, \
+			"Page start = %d\n", \
+			page_start); \
+\
+	LOGD(tcm_hcd->pdev->dev.parent, \
+			"Page count = %d\n", \
+			page_count); \
+\
+	retval = reflash_erase_flash(page_start, page_count); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to erase flash pages\n"); \
+		return retval; \
+	} \
+\
+	return 0; \
+}
+
+#define reflash_update(p_name) \
+static int reflash_update_##p_name(bool reset) \
+{ \
+	int retval; \
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \
+\
+	retval = reflash_set_up_flash_access(); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to set up flash access\n"); \
+		return retval; \
+	} \
+\
+	tcm_hcd->update_watchdog(tcm_hcd, false); \
+\
+	retval = reflash_check_##p_name(); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed "#p_name" partition check\n"); \
+		reset = true; \
+		goto reset; \
+	} \
+\
+	retval = reflash_erase_##p_name(); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to erase "#p_name" partition\n"); \
+		reset = true; \
+		goto reset; \
+	} \
+\
+	LOGN(tcm_hcd->pdev->dev.parent, \
+			"Partition erased ("#p_name")\n"); \
+\
+	retval = reflash_write_##p_name(); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to write "#p_name" partition\n"); \
+		reset = true; \
+		goto reset; \
+	} \
+\
+	LOGN(tcm_hcd->pdev->dev.parent, \
+			"Partition written ("#p_name")\n"); \
+\
+	retval = 0; \
+\
+reset: \
+	if (!reset) \
+		goto exit; \
+\
+	if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to do reset\n"); \
+	} \
+\
+exit: \
+	tcm_hcd->update_watchdog(tcm_hcd, true); \
+\
+	return retval; \
+}
+
+#define reflash_show_data() \
+{ \
+	LOCK_BUFFER(reflash_hcd->read); \
+\
+	readlen = MIN(count, reflash_hcd->read.data_length - pos); \
+\
+	retval = secure_memcpy(buf, \
+			count, \
+			&reflash_hcd->read.buf[pos], \
+			reflash_hcd->read.buf_size - pos, \
+			readlen); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+			"Failed to copy read data\n"); \
+	} else { \
+		retval = readlen; \
+	} \
+\
+	UNLOCK_BUFFER(reflash_hcd->read); \
+}
+
+enum update_area {
+	NONE = 0,
+	FIRMWARE_CONFIG,
+	CONFIG_ONLY,
+};
+
+struct app_config_header {
+	unsigned short magic_value[4];
+	unsigned char checksum[4];
+	unsigned char length[2];
+	unsigned char build_id[4];
+	unsigned char customer_config_id[16];
+};
+
+struct area_descriptor {
+	unsigned char magic_value[4];
+	unsigned char id_string[16];
+	unsigned char flags[4];
+	unsigned char flash_addr_words[4];
+	unsigned char length[4];
+	unsigned char checksum[4];
+};
+
+struct block_data {
+	const unsigned char *data;
+	unsigned int size;
+	unsigned int flash_addr;
+};
+
+struct image_info {
+	struct block_data boot_config;
+	struct block_data app_firmware;
+	struct block_data prod_test_firmware;
+	struct block_data app_config;
+	struct block_data disp_config;
+};
+
+struct image_header {
+	unsigned char magic_value[4];
+	unsigned char num_of_areas[4];
+};
+
+struct boot_config {
+	union {
+		unsigned char i2c_address;
+		struct {
+			unsigned char cpha:1;
+			unsigned char cpol:1;
+			unsigned char word0_b2__7:6;
+		} __packed;
+	};
+	unsigned char attn_polarity:1;
+	unsigned char attn_drive:2;
+	unsigned char attn_pullup:1;
+	unsigned char word0_b12__14:3;
+	unsigned char used:1;
+	unsigned short customer_part_id;
+	unsigned short boot_timeout;
+	unsigned short continue_on_reset:1;
+	unsigned short word3_b1__15:15;
+} __packed;
+
+struct reflash_hcd {
+	bool force_update;
+	bool disp_cfg_update;
+	const unsigned char *image;
+	unsigned char *image_buf;
+	unsigned int image_size;
+	unsigned int page_size;
+	unsigned int write_block_size;
+	unsigned int max_write_payload_size;
+	const struct firmware *fw_entry;
+	struct mutex reflash_mutex;
+	struct kobject *sysfs_dir;
+	struct kobject *custom_dir;
+	struct work_struct work;
+	struct workqueue_struct *workqueue;
+	struct image_info image_info;
+	struct syna_tcm_buffer out;
+	struct syna_tcm_buffer resp;
+	struct syna_tcm_buffer read;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+DECLARE_COMPLETION(reflash_remove_complete);
+
+static struct reflash_hcd *reflash_hcd;
+
+static int reflash_get_fw_image(void);
+
+static int reflash_read_data(enum flash_area area, bool run_app_firmware,
+		struct syna_tcm_buffer *output);
+
+static int reflash_update_custom_otp(const unsigned char *data,
+		unsigned int offset, unsigned int datalen);
+
+static int reflash_update_custom_lcm(const unsigned char *data,
+		unsigned int offset, unsigned int datalen);
+
+static int reflash_update_custom_oem(const unsigned char *data,
+		unsigned int offset, unsigned int datalen);
+
+static int reflash_update_boot_config(bool lock);
+
+static int reflash_update_app_config(bool reset);
+
+static int reflash_update_disp_config(bool reset);
+
+static int reflash_do_reflash(void);
+
+STORE_PROTOTYPE(reflash, reflash);
+
+static struct device_attribute *attrs[] = {
+	ATTRIFY(reflash),
+};
+
+static ssize_t reflash_sysfs_image_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static ssize_t reflash_sysfs_lockdown_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static ssize_t reflash_sysfs_lockdown_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static ssize_t reflash_sysfs_lcm_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static ssize_t reflash_sysfs_lcm_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static ssize_t reflash_sysfs_oem_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static ssize_t reflash_sysfs_oem_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static struct bin_attribute bin_attrs[] = {
+	{
+		.attr = {
+			.name = "image",
+			.mode = 0220,
+		},
+		.size = 0,
+		.write = reflash_sysfs_image_store,
+	},
+	{
+		.attr = {
+			.name = "lockdown",
+			.mode = 0664,
+		},
+		.size = 0,
+		.read = reflash_sysfs_lockdown_show,
+		.write = reflash_sysfs_lockdown_store,
+	},
+	{
+		.attr = {
+			.name = "lcm",
+			.mode = 0664,
+		},
+		.size = 0,
+		.read = reflash_sysfs_lcm_show,
+		.write = reflash_sysfs_lcm_store,
+	},
+	{
+		.attr = {
+			.name = "oem",
+			.mode = 0664,
+		},
+		.size = 0,
+		.read = reflash_sysfs_oem_show,
+		.write = reflash_sysfs_oem_store,
+	},
+};
+
+static ssize_t reflash_sysfs_reflash_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int retval;
+	unsigned int input;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	if (kstrtouint(buf, 10, &input))
+		return -EINVAL;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	pm_stay_awake(&tcm_hcd->pdev->dev);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	if (reflash_hcd->image_size != 0)
+		reflash_hcd->image = reflash_hcd->image_buf;
+
+	reflash_hcd->force_update = input & FORCE_UPDATE ? true : false;
+
+	if (input & REFLASH || input & FORCE_UPDATE) {
+		retval = reflash_do_reflash();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reflash\n");
+			goto exit;
+		}
+	}
+
+	if ((input & ~(REFLASH | FORCE_UPDATE)) == 0) {
+		retval = count;
+		goto exit;
+	}
+
+	retval = reflash_get_fw_image();
+	if (retval < 0) {
+		LOGD(tcm_hcd->pdev->dev.parent,
+				"Failed to get firmware image\n");
+		goto exit;
+	}
+
+	if (input & BOOT_CFG_LOCKDOWN) {
+		retval = reflash_update_boot_config(true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to lockdown boot config\n");
+			goto exit;
+		}
+	} else if (input & BOOT_CFG_UPDATE) {
+		retval = reflash_update_boot_config(false);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to update boot config\n");
+			goto exit;
+		}
+	}
+
+	if (input & REFLASH || input & FORCE_UPDATE) {
+		retval = count;
+		goto exit;
+	}
+
+	if (input & DISP_CFG_UPDATE) {
+		if (input & APP_CFG_UPDATE)
+			retval = reflash_update_disp_config(false);
+		else
+			retval = reflash_update_disp_config(true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to reflash display config\n");
+			goto exit;
+		}
+	}
+
+	if (input & APP_CFG_UPDATE) {
+		retval = reflash_update_app_config(true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to reflash application config\n");
+			goto exit;
+		}
+	}
+
+	retval = count;
+
+exit:
+	if (reflash_hcd->fw_entry) {
+		release_firmware(reflash_hcd->fw_entry);
+		reflash_hcd->fw_entry = NULL;
+	}
+
+	reflash_hcd->image = NULL;
+	reflash_hcd->image_size = 0;
+	reflash_hcd->force_update = FORCE_REFLASH;
+
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	pm_relax(&tcm_hcd->pdev->dev);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_image_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = secure_memcpy(&reflash_hcd->image_buf[pos],
+			IMAGE_BUF_SIZE - pos,
+			buf,
+			count,
+			count);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy firmware image data\n");
+		reflash_hcd->image_size = 0;
+		goto exit;
+	}
+
+	reflash_hcd->image_size = pos + count;
+
+	retval = count;
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_lockdown_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	unsigned int readlen;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_read_data(CUSTOM_OTP, true, NULL);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read lockdown data\n");
+		goto exit;
+	}
+
+	reflash_show_data();
+
+exit:
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_lockdown_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	pm_stay_awake(&tcm_hcd->pdev->dev);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_update_custom_otp(buf, pos, count);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to update custom OTP data\n");
+		goto exit;
+	}
+
+	retval = count;
+
+exit:
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	pm_relax(&tcm_hcd->pdev->dev);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_lcm_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	unsigned int readlen;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_read_data(CUSTOM_LCM, true, NULL);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read LCM data\n");
+		goto exit;
+	}
+
+	reflash_show_data();
+
+exit:
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_lcm_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	pm_stay_awake(&tcm_hcd->pdev->dev);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_update_custom_lcm(buf, pos, count);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to update custom LCM data\n");
+		goto exit;
+	}
+
+	retval = count;
+
+exit:
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	pm_relax(&tcm_hcd->pdev->dev);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_oem_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	unsigned int readlen;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_read_data(CUSTOM_OEM, true, NULL);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read OEM data\n");
+		goto exit;
+	}
+
+	reflash_show_data();
+
+exit:
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t reflash_sysfs_oem_store(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	pm_stay_awake(&tcm_hcd->pdev->dev);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_update_custom_oem(buf, pos, count);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to update custom OEM data\n");
+		goto exit;
+	}
+
+	retval = count;
+
+exit:
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	pm_relax(&tcm_hcd->pdev->dev);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static int reflash_set_up_flash_access(void)
+{
+	int retval;
+	unsigned int temp;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	retval = tcm_hcd->identify(tcm_hcd, true);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do identification\n");
+		return retval;
+	}
+
+	if (tcm_hcd->id_info.mode == MODE_APPLICATION) {
+		retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to enter bootloader mode\n");
+			return retval;
+		}
+	}
+
+	temp = tcm_hcd->boot_info.write_block_size_words;
+	reflash_hcd->write_block_size = temp * 2;
+
+	temp = le2_to_uint(tcm_hcd->boot_info.erase_page_size_words);
+	reflash_hcd->page_size = temp * 2;
+
+	temp = le2_to_uint(tcm_hcd->boot_info.max_write_payload_size);
+	reflash_hcd->max_write_payload_size = temp;
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Write block size = %d\n",
+			reflash_hcd->write_block_size);
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Page size = %d\n",
+			reflash_hcd->page_size);
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Max write payload size = %d\n",
+			reflash_hcd->max_write_payload_size);
+
+	if (reflash_hcd->write_block_size > (tcm_hcd->wr_chunk_size - 5)) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Write size greater than available chunk space\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int reflash_parse_fw_image(void)
+{
+	unsigned int idx;
+	unsigned int addr;
+	unsigned int offset;
+	unsigned int length;
+	unsigned int checksum;
+	unsigned int flash_addr;
+	unsigned int magic_value;
+	unsigned int num_of_areas;
+	struct image_header *header;
+	struct image_info *image_info;
+	struct area_descriptor *descriptor;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+	const unsigned char *image;
+	const unsigned char *content;
+
+	image = reflash_hcd->image;
+	image_info = &reflash_hcd->image_info;
+	header = (struct image_header *)image;
+
+	reflash_hcd->disp_cfg_update = false;
+
+	magic_value = le4_to_uint(header->magic_value);
+	if (magic_value != IMAGE_FILE_MAGIC_VALUE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid image file magic value\n");
+		return -EINVAL;
+	}
+
+	memset(image_info, 0x00, sizeof(*image_info));
+
+	offset = sizeof(*header);
+	num_of_areas = le4_to_uint(header->num_of_areas);
+
+	for (idx = 0; idx < num_of_areas; idx++) {
+		addr = le4_to_uint(image + offset);
+		descriptor = (struct area_descriptor *)(image + addr);
+		offset += 4;
+
+		magic_value = le4_to_uint(descriptor->magic_value);
+		if (magic_value != FLASH_AREA_MAGIC_VALUE)
+			continue;
+
+		length = le4_to_uint(descriptor->length);
+		content = (unsigned char *)descriptor + sizeof(*descriptor);
+		flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2;
+		checksum = le4_to_uint(descriptor->checksum);
+
+		if (!memcmp((char *)descriptor->id_string,
+				BOOT_CONFIG_ID,
+				strlen(BOOT_CONFIG_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Boot config checksum error\n");
+				return -EINVAL;
+			}
+			image_info->boot_config.size = length;
+			image_info->boot_config.data = content;
+			image_info->boot_config.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Boot config size = %d\n",
+					length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Boot config flash address = 0x%08x\n",
+					flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				APP_CODE_ID,
+				strlen(APP_CODE_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"APP firmware checksum error\n");
+				return -EINVAL;
+			}
+			image_info->app_firmware.size = length;
+			image_info->app_firmware.data = content;
+			image_info->app_firmware.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application firmware size = %d\n",
+				length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application firmware flash address = 0x%08x\n",
+				flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				PROD_TEST_ID,
+				strlen(PROD_TEST_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Production test checksum error\n");
+				return -EINVAL;
+			}
+			image_info->prod_test_firmware.size = length;
+			image_info->prod_test_firmware.data = content;
+			image_info->prod_test_firmware.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Production test firmware size = %d\n",
+				length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Production test flash address = 0x%08x\n",
+				flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				APP_CONFIG_ID,
+				strlen(APP_CONFIG_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Application config checksum error\n");
+				return -EINVAL;
+			}
+			image_info->app_config.size = length;
+			image_info->app_config.data = content;
+			image_info->app_config.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application config size = %d\n",
+				length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application config flash address = 0x%08x\n",
+				flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				DISP_CONFIG_ID,
+				strlen(DISP_CONFIG_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Display config checksum error\n");
+				return -EINVAL;
+			}
+			reflash_hcd->disp_cfg_update = true;
+			image_info->disp_config.size = length;
+			image_info->disp_config.data = content;
+			image_info->disp_config.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Display config size = %d\n",
+				length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Display config flash address = 0x%08x\n",
+				flash_addr);
+		}
+	}
+
+	return 0;
+}
+
+static int reflash_get_fw_image(void)
+{
+	int retval;
+	const char *fw_name;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	if (bdata->fw_name)
+		fw_name = bdata->fw_name;
+	else
+		fw_name = FW_IMAGE_NAME;
+
+	if (reflash_hcd->image == NULL) {
+		retval = request_firmware(&reflash_hcd->fw_entry, fw_name,
+				tcm_hcd->pdev->dev.parent);
+		if (retval < 0) {
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Failed to request %s\n",
+					fw_name);
+			return retval;
+		}
+
+		LOGD(tcm_hcd->pdev->dev.parent,
+				"Firmware image size = %d\n",
+				(unsigned int)reflash_hcd->fw_entry->size);
+
+		reflash_hcd->image = reflash_hcd->fw_entry->data;
+		reflash_hcd->image_size = reflash_hcd->fw_entry->size;
+	}
+
+	retval = reflash_parse_fw_image();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to parse firmware image\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static enum update_area reflash_compare_id_info(void)
+{
+	enum update_area update_area;
+	unsigned int idx;
+	unsigned int image_fw_id;
+	unsigned int device_fw_id;
+	unsigned char *image_config_id;
+	unsigned char *device_config_id;
+	struct app_config_header *header;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+	const unsigned char *app_config_data;
+
+	update_area = NONE;
+
+	if (reflash_hcd->image_info.app_config.size < sizeof(*header)) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid application config in image file\n");
+		goto exit;
+	}
+
+	app_config_data = reflash_hcd->image_info.app_config.data;
+	header = (struct app_config_header *)app_config_data;
+
+	if (reflash_hcd->force_update) {
+		update_area = FIRMWARE_CONFIG;
+		goto exit;
+	}
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION) {
+		update_area = FIRMWARE_CONFIG;
+		goto exit;
+	}
+
+	image_fw_id = le4_to_uint(header->build_id);
+	device_fw_id = tcm_hcd->packrat_number;
+
+	if (image_fw_id > device_fw_id) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+			"Image firmware ID newer than device firmware ID\n");
+		update_area = FIRMWARE_CONFIG;
+		goto exit;
+	} else if (image_fw_id < device_fw_id) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+			"Image firmware ID older than device firmware ID\n");
+		update_area = NONE;
+		goto exit;
+	}
+
+	image_config_id = header->customer_config_id;
+	device_config_id = tcm_hcd->app_info.customer_config_id;
+
+	for (idx = 0; idx < 16; idx++) {
+		if (image_config_id[idx] > device_config_id[idx]) {
+			LOGN(tcm_hcd->pdev->dev.parent,
+				"Image config ID newer than device's ID\n");
+			update_area = CONFIG_ONLY;
+			goto exit;
+		} else if (image_config_id[idx] < device_config_id[idx]) {
+			LOGN(tcm_hcd->pdev->dev.parent,
+				"Image config ID older than device's ID\n");
+			update_area = NONE;
+			goto exit;
+		}
+	}
+
+	update_area = NONE;
+
+exit:
+	if (update_area == NONE)
+		LOGD(tcm_hcd->pdev->dev.parent, "No need to do reflash\n");
+	else
+		LOGD(tcm_hcd->pdev->dev.parent,
+				"Updating %s\n",
+				update_area == FIRMWARE_CONFIG ?
+				"firmware and config" :
+				"config only");
+
+	return update_area;
+}
+
+static int reflash_read_flash(unsigned int address, unsigned char *data,
+		unsigned int datalen)
+{
+	int retval;
+	unsigned int length_words;
+	unsigned int flash_addr_words;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	LOCK_BUFFER(reflash_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&reflash_hcd->out,
+			6);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for reflash_hcd->out.buf\n");
+		UNLOCK_BUFFER(reflash_hcd->out);
+		return retval;
+	}
+
+	length_words = datalen / 2;
+	flash_addr_words = address / 2;
+
+	reflash_hcd->out.buf[0] = (unsigned char)flash_addr_words;
+	reflash_hcd->out.buf[1] = (unsigned char)(flash_addr_words >> 8);
+	reflash_hcd->out.buf[2] = (unsigned char)(flash_addr_words >> 16);
+	reflash_hcd->out.buf[3] = (unsigned char)(flash_addr_words >> 24);
+	reflash_hcd->out.buf[4] = (unsigned char)length_words;
+	reflash_hcd->out.buf[5] = (unsigned char)(length_words >> 8);
+
+	LOCK_BUFFER(reflash_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_READ_FLASH,
+			reflash_hcd->out.buf,
+			6,
+			&reflash_hcd->resp.buf,
+			&reflash_hcd->resp.buf_size,
+			&reflash_hcd->resp.data_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_READ_FLASH));
+		UNLOCK_BUFFER(reflash_hcd->resp);
+		UNLOCK_BUFFER(reflash_hcd->out);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(reflash_hcd->out);
+
+	if (reflash_hcd->resp.data_length != datalen) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read requested length\n");
+		UNLOCK_BUFFER(reflash_hcd->resp);
+		return -EIO;
+	}
+
+	retval = secure_memcpy(data,
+			datalen,
+			reflash_hcd->resp.buf,
+			reflash_hcd->resp.buf_size,
+			datalen);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy read data\n");
+		UNLOCK_BUFFER(reflash_hcd->resp);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(reflash_hcd->resp);
+
+	return 0;
+}
+
+static int reflash_read_data(enum flash_area area, bool run_app_firmware,
+		struct syna_tcm_buffer *output)
+{
+	int retval;
+	unsigned int temp;
+	unsigned int addr;
+	unsigned int length;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_boot_info *boot_info;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	switch (area) {
+	case CUSTOM_LCM:
+	case CUSTOM_OEM:
+	case PPDT:
+		retval = tcm_hcd->get_data_location(tcm_hcd,
+				area,
+				&addr,
+				&length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get data location\n");
+			return retval;
+		}
+		break;
+	default:
+		break;
+	}
+
+	retval = reflash_set_up_flash_access();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up flash access\n");
+		return retval;
+	}
+
+	app_info = &tcm_hcd->app_info;
+	boot_info = &tcm_hcd->boot_info;
+
+	switch (area) {
+	case BOOT_CONFIG:
+		temp = le2_to_uint(boot_info->boot_config_start_block);
+		addr = temp * reflash_hcd->write_block_size;
+		length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS;
+		break;
+	case APP_CONFIG:
+		temp = le2_to_uint(app_info->app_config_start_write_block);
+		addr = temp * reflash_hcd->write_block_size;
+		length = le2_to_uint(app_info->app_config_size);
+		break;
+	case DISP_CONFIG:
+		temp = le4_to_uint(boot_info->display_config_start_block);
+		addr = temp * reflash_hcd->write_block_size;
+		temp = le2_to_uint(boot_info->display_config_length_blocks);
+		length = temp * reflash_hcd->write_block_size;
+		break;
+	case CUSTOM_OTP:
+		temp = le2_to_uint(boot_info->custom_otp_start_block);
+		addr = temp * reflash_hcd->write_block_size;
+		temp = le2_to_uint(boot_info->custom_otp_length_blocks);
+		length = temp * reflash_hcd->write_block_size;
+		break;
+	case CUSTOM_LCM:
+	case CUSTOM_OEM:
+	case PPDT:
+		addr *= reflash_hcd->write_block_size;
+		length *= reflash_hcd->write_block_size;
+		break;
+	default:
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid data area\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	if (addr == 0 || length == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Data area unavailable\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	LOCK_BUFFER(reflash_hcd->read);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&reflash_hcd->read,
+			length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for read.buf\n");
+		UNLOCK_BUFFER(reflash_hcd->read);
+		goto run_app_firmware;
+	}
+
+	retval = reflash_read_flash(addr, reflash_hcd->read.buf, length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read from flash\n");
+		UNLOCK_BUFFER(reflash_hcd->read);
+		goto run_app_firmware;
+	}
+
+	reflash_hcd->read.data_length = length;
+
+	if (output != NULL) {
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				output,
+				length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for output->buf\n");
+			UNLOCK_BUFFER(reflash_hcd->read);
+			goto run_app_firmware;
+		}
+
+		retval = secure_memcpy(output->buf,
+				output->buf_size,
+				reflash_hcd->read.buf,
+				reflash_hcd->read.buf_size,
+				length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy read data\n");
+			UNLOCK_BUFFER(reflash_hcd->read);
+			goto run_app_firmware;
+		}
+
+		output->data_length = length;
+	}
+
+	UNLOCK_BUFFER(reflash_hcd->read);
+
+	retval = 0;
+
+run_app_firmware:
+	if (!run_app_firmware)
+		goto exit;
+
+	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run application firmware\n");
+	}
+
+exit:
+	return retval;
+}
+
+static int reflash_check_boot_config(void)
+{
+	unsigned int temp;
+	unsigned int image_addr;
+	unsigned int device_addr;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	if (reflash_hcd->image_info.boot_config.size < BOOT_CONFIG_SIZE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No valid boot config in image file\n");
+		return -EINVAL;
+	}
+
+	image_addr = reflash_hcd->image_info.boot_config.flash_addr;
+
+	temp = le2_to_uint(tcm_hcd->boot_info.boot_config_start_block);
+	device_addr = temp * reflash_hcd->write_block_size;
+
+	if (image_addr != device_addr) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Flash address mismatch\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int reflash_check_app_config(void)
+{
+	unsigned int temp;
+	unsigned int image_addr;
+	unsigned int image_size;
+	unsigned int device_addr;
+	unsigned int device_size;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	if (reflash_hcd->image_info.app_config.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No application config in image file\n");
+		return -EINVAL;
+	}
+
+	image_addr = reflash_hcd->image_info.app_config.flash_addr;
+	image_size = reflash_hcd->image_info.app_config.size;
+
+	temp = le2_to_uint(tcm_hcd->app_info.app_config_start_write_block);
+	device_addr = temp * reflash_hcd->write_block_size;
+	device_size = le2_to_uint(tcm_hcd->app_info.app_config_size);
+
+	if (device_addr == 0 && device_size == 0)
+		return 0;
+
+	if (image_addr != device_addr) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Flash address mismatch\n");
+		return -EINVAL;
+	}
+
+	if (image_size != device_size) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Config size mismatch\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int reflash_check_disp_config(void)
+{
+	unsigned int temp;
+	unsigned int image_addr;
+	unsigned int image_size;
+	unsigned int device_addr;
+	unsigned int device_size;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	if (reflash_hcd->image_info.disp_config.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No display config in image file\n");
+		return -EINVAL;
+	}
+
+	image_addr = reflash_hcd->image_info.disp_config.flash_addr;
+	image_size = reflash_hcd->image_info.disp_config.size;
+
+	temp = le4_to_uint(tcm_hcd->boot_info.display_config_start_block);
+	device_addr = temp * reflash_hcd->write_block_size;
+
+	temp = le2_to_uint(tcm_hcd->boot_info.display_config_length_blocks);
+	device_size = temp * reflash_hcd->write_block_size;
+
+	if (image_addr != device_addr) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Flash address mismatch\n");
+		return -EINVAL;
+	}
+
+	if (image_size != device_size) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Config size mismatch\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int reflash_check_prod_test_firmware(void)
+{
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	if (reflash_hcd->image_info.prod_test_firmware.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No production test firmware in image file\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int reflash_check_app_firmware(void)
+{
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	if (reflash_hcd->image_info.app_firmware.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No application firmware in image file\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int reflash_write_flash(unsigned int address, const unsigned char *data,
+		unsigned int datalen)
+{
+	int retval;
+	unsigned int offset;
+	unsigned int w_length;
+	unsigned int xfer_length;
+	unsigned int remaining_length;
+	unsigned int flash_address;
+	unsigned int block_address;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	w_length = tcm_hcd->wr_chunk_size - 5;
+
+	w_length = w_length - (w_length % reflash_hcd->write_block_size);
+
+	w_length = MIN(w_length, reflash_hcd->max_write_payload_size);
+
+	offset = 0;
+
+	remaining_length = datalen;
+
+	LOCK_BUFFER(reflash_hcd->out);
+	LOCK_BUFFER(reflash_hcd->resp);
+
+	while (remaining_length) {
+		if (remaining_length > w_length)
+			xfer_length = w_length;
+		else
+			xfer_length = remaining_length;
+
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				&reflash_hcd->out,
+				xfer_length + 2);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for out.buf\n");
+			UNLOCK_BUFFER(reflash_hcd->resp);
+			UNLOCK_BUFFER(reflash_hcd->out);
+			return retval;
+		}
+
+		flash_address = address + offset;
+		block_address = flash_address / reflash_hcd->write_block_size;
+		reflash_hcd->out.buf[0] = (unsigned char)block_address;
+		reflash_hcd->out.buf[1] = (unsigned char)(block_address >> 8);
+
+		retval = secure_memcpy(&reflash_hcd->out.buf[2],
+				reflash_hcd->out.buf_size - 2,
+				&data[offset],
+				datalen - offset,
+				xfer_length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy write data\n");
+			UNLOCK_BUFFER(reflash_hcd->resp);
+			UNLOCK_BUFFER(reflash_hcd->out);
+			return retval;
+		}
+
+		retval = tcm_hcd->write_message(tcm_hcd,
+				CMD_WRITE_FLASH,
+				reflash_hcd->out.buf,
+				xfer_length + 2,
+				&reflash_hcd->resp.buf,
+				&reflash_hcd->resp.buf_size,
+				&reflash_hcd->resp.data_length,
+				NULL,
+				WRITE_FLASH_DELAY_MS);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to write command %s\n",
+					STR(CMD_WRITE_FLASH));
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Flash address = 0x%08x\n",
+					flash_address);
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Data length = %d\n",
+					xfer_length);
+			UNLOCK_BUFFER(reflash_hcd->resp);
+			UNLOCK_BUFFER(reflash_hcd->out);
+			return retval;
+		}
+
+		offset += xfer_length;
+		remaining_length -= xfer_length;
+	}
+
+	UNLOCK_BUFFER(reflash_hcd->resp);
+	UNLOCK_BUFFER(reflash_hcd->out);
+
+	return 0;
+}
+
+reflash_write(app_config)
+
+reflash_write(disp_config)
+
+reflash_write(prod_test_firmware)
+
+reflash_write(app_firmware)
+
+static int reflash_erase_flash(unsigned int page_start, unsigned int page_count)
+{
+	int retval;
+	unsigned char out_buf[2];
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	out_buf[0] = (unsigned char)page_start;
+	out_buf[1] = (unsigned char)page_count;
+
+	LOCK_BUFFER(reflash_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_ERASE_FLASH,
+			out_buf,
+			sizeof(out_buf),
+			&reflash_hcd->resp.buf,
+			&reflash_hcd->resp.buf_size,
+			&reflash_hcd->resp.data_length,
+			NULL,
+			ERASE_FLASH_DELAY_MS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_ERASE_FLASH));
+		UNLOCK_BUFFER(reflash_hcd->resp);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(reflash_hcd->resp);
+
+	return 0;
+}
+
+reflash_erase(app_config)
+
+reflash_erase(disp_config)
+
+reflash_erase(prod_test_firmware)
+
+reflash_erase(app_firmware)
+
+static int reflash_update_custom_otp(const unsigned char *data,
+		unsigned int offset, unsigned int datalen)
+{
+	int retval;
+	unsigned int temp;
+	unsigned int addr;
+	unsigned int length;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	retval = reflash_set_up_flash_access();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up flash access\n");
+		return retval;
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_start_block);
+	addr = temp * reflash_hcd->write_block_size;
+
+	temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_length_blocks);
+	length = temp * reflash_hcd->write_block_size;
+
+	if (addr == 0 || length == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Data area unavailable\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	if (datalen + offset > length) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid data length\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	retval = reflash_write_flash(addr + offset,
+			data,
+			datalen);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write to flash\n");
+		goto run_app_firmware;
+	}
+
+	retval = 0;
+
+run_app_firmware:
+	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run application firmware\n");
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	return retval;
+}
+
+static int reflash_update_custom_lcm(const unsigned char *data,
+		unsigned int offset, unsigned int datalen)
+{
+	int retval;
+	unsigned int addr;
+	unsigned int length;
+	unsigned int page_start;
+	unsigned int page_count;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	retval = tcm_hcd->get_data_location(tcm_hcd,
+			CUSTOM_LCM,
+			&addr,
+			&length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get data location\n");
+		return retval;
+	}
+
+	retval = reflash_set_up_flash_access();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up flash access\n");
+		return retval;
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	addr *= reflash_hcd->write_block_size;
+	length *= reflash_hcd->write_block_size;
+
+	if (addr == 0 || length == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Data area unavailable\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	if (datalen + offset > length) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid data length\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	if (offset == 0) {
+		page_start = addr / reflash_hcd->page_size;
+
+		page_count = ceil_div(length, reflash_hcd->page_size);
+
+		retval = reflash_erase_flash(page_start, page_count);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to erase flash pages\n");
+			goto run_app_firmware;
+		}
+	}
+
+	retval = reflash_write_flash(addr + offset,
+			data,
+			datalen);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write to flash\n");
+		goto run_app_firmware;
+	}
+
+	retval = 0;
+
+run_app_firmware:
+	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run application firmware\n");
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	return retval;
+}
+
+static int reflash_update_custom_oem(const unsigned char *data,
+		unsigned int offset, unsigned int datalen)
+{
+	int retval;
+	unsigned int addr;
+	unsigned int length;
+	unsigned int page_start;
+	unsigned int page_count;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	retval = tcm_hcd->get_data_location(tcm_hcd,
+			CUSTOM_OEM,
+			&addr,
+			&length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get data location\n");
+		return retval;
+	}
+
+	retval = reflash_set_up_flash_access();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up flash access\n");
+		return retval;
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	addr *= reflash_hcd->write_block_size;
+	length *= reflash_hcd->write_block_size;
+
+	if (addr == 0 || length == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Data area unavailable\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	if (datalen + offset > length) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid data length\n");
+		retval = -EINVAL;
+		goto run_app_firmware;
+	}
+
+	if (offset == 0) {
+		page_start = addr / reflash_hcd->page_size;
+
+		page_count = ceil_div(length, reflash_hcd->page_size);
+
+		retval = reflash_erase_flash(page_start, page_count);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to erase flash pages\n");
+			goto run_app_firmware;
+		}
+	}
+
+	retval = reflash_write_flash(addr + offset,
+			data,
+			datalen);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write to flash\n");
+		goto run_app_firmware;
+	}
+
+	retval = 0;
+
+run_app_firmware:
+	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run application firmware\n");
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	return retval;
+}
+
+static int reflash_update_boot_config(bool lock)
+{
+	int retval;
+	unsigned char slot_used;
+	unsigned int idx;
+	unsigned int addr = 0;
+	struct boot_config *data;
+	struct boot_config *last_slot;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	retval = reflash_set_up_flash_access();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up flash access\n");
+		return retval;
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	retval = reflash_check_boot_config();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed boot_config partition check\n");
+		goto reset;
+	}
+
+	retval = reflash_read_data(BOOT_CONFIG, false, NULL);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read boot config\n");
+		goto reset;
+	}
+
+	LOCK_BUFFER(reflash_hcd->read);
+
+	data = (struct boot_config *)reflash_hcd->read.buf;
+	last_slot = data + (BOOT_CONFIG_SLOTS - 1);
+	slot_used = tcm_hcd->id_info.mode == MODE_TDDI_BOOTLOADER ? 0 : 1;
+
+	if (last_slot->used == slot_used) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Boot config already locked down\n");
+		UNLOCK_BUFFER(reflash_hcd->read);
+		goto reset;
+	}
+
+	if (lock) {
+		idx = BOOT_CONFIG_SLOTS - 1;
+	} else {
+		for (idx = 0; idx < BOOT_CONFIG_SLOTS; idx++) {
+			if (data->used == slot_used) {
+				data++;
+				continue;
+			} else {
+				break;
+			}
+		}
+	}
+
+	UNLOCK_BUFFER(reflash_hcd->read);
+
+	if (idx == BOOT_CONFIG_SLOTS) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No free boot config slot available\n");
+		goto reset;
+	}
+
+	addr += idx * BOOT_CONFIG_SIZE;
+
+	retval = reflash_write_flash(addr,
+			reflash_hcd->image_info.boot_config.data,
+			BOOT_CONFIG_SIZE);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write to flash\n");
+		goto reset;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Slot %d updated with new boot config\n",
+			idx);
+
+	retval = 0;
+
+reset:
+	if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do reset\n");
+	}
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	return retval;
+}
+
+reflash_update(app_config)
+
+reflash_update(disp_config)
+
+reflash_update(prod_test_firmware)
+
+reflash_update(app_firmware)
+
+static int reflash_do_reflash(void)
+{
+	int retval;
+	enum update_area update_area;
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+	retval = reflash_get_fw_image();
+	if (retval < 0) {
+		LOGD(tcm_hcd->pdev->dev.parent,
+				"Failed to get firmware image\n");
+		goto exit;
+	}
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Start of reflash\n");
+
+	atomic_set(&tcm_hcd->firmware_flashing, 1);
+
+	update_area = reflash_compare_id_info();
+
+	switch (update_area) {
+	case FIRMWARE_CONFIG:
+		retval = reflash_update_app_firmware(false);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to reflash application firmware\n");
+			goto exit;
+		}
+		memset(&tcm_hcd->app_info, 0x00, sizeof(tcm_hcd->app_info));
+		if (tcm_hcd->features.dual_firmware) {
+			retval = reflash_update_prod_test_firmware(false);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to reflash production test\n");
+				goto exit;
+			}
+		}
+	case CONFIG_ONLY:
+		if (reflash_hcd->disp_cfg_update) {
+			retval = reflash_update_disp_config(false);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to reflash display config\n");
+				goto exit;
+			}
+		}
+		retval = reflash_update_app_config(true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to reflash application config\n");
+			goto exit;
+		}
+		break;
+	case NONE:
+	default:
+		break;
+	}
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"End of reflash\n");
+
+	retval = 0;
+
+exit:
+	if (reflash_hcd->fw_entry) {
+		release_firmware(reflash_hcd->fw_entry);
+		reflash_hcd->fw_entry = NULL;
+		reflash_hcd->image = NULL;
+		reflash_hcd->image_size = 0;
+	}
+
+	atomic_set(&tcm_hcd->firmware_flashing, 0);
+	wake_up_interruptible(&tcm_hcd->reflash_wq);
+	return retval;
+}
+
+#ifdef STARTUP_REFLASH
+static void reflash_startup_work(struct work_struct *work)
+{
+	int retval;
+#if defined(CONFIG_DRM) || defined(CONFIG_FB)
+	unsigned int timeout;
+#endif
+	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
+
+#if defined(CONFIG_DRM) || defined(CONFIG_FB)
+	timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS;
+
+	while (tcm_hcd->fb_ready != FB_READY_COUNT - 1) {
+		if (timeout == 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Timed out waiting for FB ready\n");
+			return;
+		}
+		msleep(FB_READY_WAIT_MS);
+		timeout--;
+	}
+#endif
+
+	pm_stay_awake(&tcm_hcd->pdev->dev);
+
+	mutex_lock(&reflash_hcd->reflash_mutex);
+
+	retval = reflash_do_reflash();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do reflash\n");
+	}
+
+	mutex_unlock(&reflash_hcd->reflash_mutex);
+
+	pm_relax(&tcm_hcd->pdev->dev);
+}
+#endif
+
+static int reflash_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval = 0;
+	int idx;
+
+	reflash_hcd = kzalloc(sizeof(*reflash_hcd), GFP_KERNEL);
+	if (!reflash_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for reflash_hcd\n");
+		return -ENOMEM;
+	}
+
+	reflash_hcd->image_buf = kzalloc(IMAGE_BUF_SIZE, GFP_KERNEL);
+	if (!reflash_hcd->image_buf) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for image_buf\n");
+		goto err_allocate_memory;
+	}
+
+	reflash_hcd->tcm_hcd = tcm_hcd;
+
+	reflash_hcd->force_update = FORCE_REFLASH;
+
+	mutex_init(&reflash_hcd->reflash_mutex);
+
+	INIT_BUFFER(reflash_hcd->out, false);
+	INIT_BUFFER(reflash_hcd->resp, false);
+	INIT_BUFFER(reflash_hcd->read, false);
+
+#ifdef STARTUP_REFLASH
+	reflash_hcd->workqueue =
+			create_singlethread_workqueue("syna_tcm_reflash");
+	INIT_WORK(&reflash_hcd->work, reflash_startup_work);
+	queue_work(reflash_hcd->workqueue, &reflash_hcd->work);
+#endif
+
+	if (!ENABLE_SYSFS_INTERFACE)
+		return 0;
+
+	reflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
+			tcm_hcd->sysfs_dir);
+	if (!reflash_hcd->sysfs_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs directory\n");
+		retval = -EINVAL;
+		goto err_sysfs_create_dir;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+		retval = sysfs_create_file(reflash_hcd->sysfs_dir,
+				&(*attrs[idx]).attr);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to create sysfs file\n");
+			goto err_sysfs_create_file;
+		}
+	}
+
+	retval = sysfs_create_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs bin file\n");
+		goto err_sysfs_create_bin_file;
+	}
+
+	reflash_hcd->custom_dir = kobject_create_and_add(CUSTOM_DIR_NAME,
+			reflash_hcd->sysfs_dir);
+	if (!reflash_hcd->custom_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create custom sysfs directory\n");
+		retval = -EINVAL;
+		goto err_custom_sysfs_create_dir;
+	}
+
+	for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) {
+		retval = sysfs_create_bin_file(reflash_hcd->custom_dir,
+				&bin_attrs[idx]);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to create sysfs bin file\n");
+			goto err_custom_sysfs_create_bin_file;
+		}
+	}
+
+	tcm_hcd->read_flash_data = reflash_read_data;
+
+	return 0;
+
+err_custom_sysfs_create_bin_file:
+	for (idx--; idx > 0; idx--)
+		sysfs_remove_bin_file(reflash_hcd->custom_dir, &bin_attrs[idx]);
+
+	kobject_put(reflash_hcd->custom_dir);
+
+	idx = ARRAY_SIZE(attrs);
+
+err_custom_sysfs_create_dir:
+	sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]);
+
+err_sysfs_create_bin_file:
+err_sysfs_create_file:
+	for (idx--; idx >= 0; idx--)
+		sysfs_remove_file(reflash_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(reflash_hcd->sysfs_dir);
+
+err_sysfs_create_dir:
+err_allocate_memory:
+	kfree(reflash_hcd->image_buf);
+
+	RELEASE_BUFFER(reflash_hcd->read);
+	RELEASE_BUFFER(reflash_hcd->resp);
+	RELEASE_BUFFER(reflash_hcd->out);
+
+	kfree(reflash_hcd);
+	reflash_hcd = NULL;
+
+	return retval;
+}
+
+static int reflash_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	int idx;
+
+	if (!reflash_hcd)
+		goto exit;
+
+	tcm_hcd->read_flash_data = NULL;
+
+	if (ENABLE_SYSFS_INTERFACE) {
+		for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) {
+			sysfs_remove_bin_file(reflash_hcd->custom_dir,
+					&bin_attrs[idx]);
+		}
+
+		kobject_put(reflash_hcd->custom_dir);
+
+		sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]);
+
+		for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+			sysfs_remove_file(reflash_hcd->sysfs_dir,
+					&(*attrs[idx]).attr);
+		}
+
+		kobject_put(reflash_hcd->sysfs_dir);
+	}
+
+#ifdef STARTUP_REFLASH
+	cancel_work_sync(&reflash_hcd->work);
+	flush_workqueue(reflash_hcd->workqueue);
+	destroy_workqueue(reflash_hcd->workqueue);
+#endif
+
+	kfree(reflash_hcd->image_buf);
+
+	RELEASE_BUFFER(reflash_hcd->read);
+	RELEASE_BUFFER(reflash_hcd->resp);
+	RELEASE_BUFFER(reflash_hcd->out);
+
+	kfree(reflash_hcd);
+	reflash_hcd = NULL;
+
+exit:
+	complete(&reflash_remove_complete);
+
+	return 0;
+}
+
+static int reflash_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!reflash_hcd) {
+		retval = reflash_init(tcm_hcd);
+		return retval;
+	}
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb reflash_module = {
+	.type = TCM_REFLASH,
+	.init = reflash_init,
+	.remove = reflash_remove,
+	.syncbox = NULL,
+	.asyncbox = NULL,
+	.reset = reflash_reset,
+	.suspend = NULL,
+	.resume = NULL,
+	.early_suspend = NULL,
+};
+
+static int __init reflash_module_init(void)
+{
+	return syna_tcm_add_module(&reflash_module, true);
+}
+
+static void __exit reflash_module_exit(void)
+{
+	syna_tcm_add_module(&reflash_module, false);
+
+	wait_for_completion(&reflash_remove_complete);
+}
+
+module_init(reflash_module_init);
+module_exit(reflash_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Reflash Module");
+MODULE_LICENSE("GPL v2");

+ 670 - 0
synaptics_tcm/synaptics_tcm_spi.c

@@ -0,0 +1,670 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/spi/spi.h>
+#include <linux/of_gpio.h>
+#include "synaptics_tcm_core.h"
+
+static unsigned char *buf;
+
+static unsigned int buf_size;
+
+static struct spi_transfer *xfer;
+
+static struct syna_tcm_bus_io bus_io;
+
+static struct syna_tcm_hw_interface hw_if;
+
+static struct platform_device *syna_tcm_spi_device;
+
+#ifdef CONFIG_OF
+static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata)
+{
+	int retval;
+	u32 value;
+	struct property *prop;
+	struct device_node *np = dev->of_node;
+	const char *name;
+
+	prop = of_find_property(np, "synaptics,irq-gpio", NULL);
+	if (prop && prop->length) {
+		bdata->irq_gpio = of_get_named_gpio_flags(np,
+				"synaptics,irq-gpio", 0,
+				(enum of_gpio_flags *)&bdata->irq_flags);
+	} else {
+		bdata->irq_gpio = -1;
+	}
+
+	retval = of_property_read_u32(np, "synaptics,irq-on-state", &value);
+	if (retval < 0)
+		bdata->irq_on_state = 0;
+	else
+		bdata->irq_on_state = value;
+
+	retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name);
+	if (retval < 0)
+		bdata->pwr_reg_name = NULL;
+	else
+		bdata->pwr_reg_name = name;
+
+	retval = of_property_read_string(np, "synaptics,bus-reg-name", &name);
+	if (retval < 0)
+		bdata->bus_reg_name = NULL;
+	else
+		bdata->bus_reg_name = name;
+
+	prop = of_find_property(np, "synaptics,power-gpio", NULL);
+	if (prop && prop->length) {
+		bdata->power_gpio = of_get_named_gpio_flags(np,
+				"synaptics,power-gpio", 0, NULL);
+	} else {
+		bdata->power_gpio = -1;
+	}
+
+	prop = of_find_property(np, "synaptics,power-on-state", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,power-on-state",
+				&value);
+		if (retval < 0) {
+			LOGE(dev,
+				"Failed to read synaptics,power-on-state\n");
+			return retval;
+		}
+		bdata->power_on_state = value;
+	} else {
+		bdata->power_on_state = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,power-delay-ms", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,power-delay-ms",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Failed to read synaptics,power-delay-ms\n");
+			return retval;
+		}
+		bdata->power_delay_ms = value;
+	} else {
+		bdata->power_delay_ms = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,reset-gpio", NULL);
+	if (prop && prop->length) {
+		bdata->reset_gpio = of_get_named_gpio_flags(np,
+				"synaptics,reset-gpio", 0, NULL);
+	} else {
+		bdata->reset_gpio = -1;
+	}
+
+	prop = of_find_property(np, "synaptics,reset-on-state", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,reset-on-state",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Failed to read synaptics,reset-on-state\n");
+			return retval;
+		}
+		bdata->reset_on_state = value;
+	} else {
+		bdata->reset_on_state = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,reset-active-ms", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,reset-active-ms",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Failed to read synaptics,reset-active-ms\n");
+			return retval;
+		}
+		bdata->reset_active_ms = value;
+	} else {
+		bdata->reset_active_ms = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,reset-delay-ms", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,reset-delay-ms",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Unable to read synaptics,reset-delay-ms\n");
+			return retval;
+		}
+		bdata->reset_delay_ms = value;
+	} else {
+		bdata->reset_delay_ms = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,x-flip", NULL);
+	bdata->x_flip = prop > 0 ? true : false;
+
+	prop = of_find_property(np, "synaptics,y-flip", NULL);
+	bdata->y_flip = prop > 0 ? true : false;
+
+	prop = of_find_property(np, "synaptics,swap-axes", NULL);
+	bdata->swap_axes = prop > 0 ? true : false;
+
+	prop = of_find_property(np, "synaptics,byte-delay-us", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,byte-delay-us",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Unable to read synaptics,byte-delay-us\n");
+			return retval;
+		}
+		bdata->byte_delay_us = value;
+	} else {
+		bdata->byte_delay_us = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,block-delay-us", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,block-delay-us",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Unable to read synaptics,block-delay-us\n");
+			return retval;
+		}
+		bdata->block_delay_us = value;
+	} else {
+		bdata->block_delay_us = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,spi-mode", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,spi-mode",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Unable to read synaptics,spi-mode\n");
+			return retval;
+		}
+		bdata->spi_mode = value;
+
+	} else {
+		bdata->spi_mode = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,ubl-max-freq", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,ubl-max-freq",
+				&value);
+		if (retval < 0) {
+			LOGE(dev, "Unable to read synaptics,ubl-max-freq\n");
+			return retval;
+		}
+		bdata->ubl_max_freq = value;
+	} else {
+		bdata->ubl_max_freq = 0;
+	}
+
+	prop = of_find_property(np, "synaptics,ubl-byte-delay-us", NULL);
+	if (prop && prop->length) {
+		retval = of_property_read_u32(np, "synaptics,ubl-byte-delay-us",
+				&value);
+		if (retval < 0) {
+			LOGE(dev,
+				"Unable to read synaptics,ubl-byte-delay-us\n");
+			return retval;
+		}
+		bdata->ubl_byte_delay_us = value;
+	} else {
+		bdata->ubl_byte_delay_us = 0;
+	}
+
+	return 0;
+}
+#endif
+
+static int syna_tcm_spi_alloc_mem(struct syna_tcm_hcd *tcm_hcd,
+		unsigned int count, unsigned int size)
+{
+	static unsigned int xfer_count;
+	struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent);
+
+	if (count > xfer_count) {
+		kfree(xfer);
+		xfer = kcalloc(count, sizeof(*xfer), GFP_KERNEL);
+		if (!xfer) {
+			LOGE(&spi->dev,
+					"Failed to allocate memory for xfer\n");
+			xfer_count = 0;
+			return -ENOMEM;
+		}
+		xfer_count = count;
+	} else {
+		memset(xfer, 0, count * sizeof(*xfer));
+	}
+
+	if (size > buf_size) {
+		if (buf_size)
+			kfree(buf);
+		buf = kmalloc(size, GFP_KERNEL);
+		if (!buf) {
+			LOGE(&spi->dev,
+					"Failed to allocate memory for buf\n");
+			buf_size = 0;
+			return -ENOMEM;
+		}
+		buf_size = size;
+	}
+
+	return 0;
+}
+
+static int syna_tcm_spi_rmi_read(struct syna_tcm_hcd *tcm_hcd,
+		unsigned short addr, unsigned char *data, unsigned int length)
+{
+	int retval;
+	unsigned int idx;
+	unsigned int mode;
+	unsigned int byte_count;
+	struct spi_message msg;
+	struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	spi_message_init(&msg);
+
+	byte_count = length + 2;
+
+	if (bdata->ubl_byte_delay_us == 0)
+		retval = syna_tcm_spi_alloc_mem(tcm_hcd, 2, byte_count);
+	else
+		retval = syna_tcm_spi_alloc_mem(tcm_hcd, byte_count, 3);
+	if (retval < 0) {
+		LOGE(&spi->dev,
+				"Failed to allocate memory\n");
+		goto exit;
+	}
+
+	buf[0] = (unsigned char)(addr >> 8) | 0x80;
+	buf[1] = (unsigned char)addr;
+
+	if (bdata->ubl_byte_delay_us == 0) {
+		xfer[0].len = 2;
+		xfer[0].tx_buf = buf;
+		xfer[0].speed_hz = bdata->ubl_max_freq;
+		spi_message_add_tail(&xfer[0], &msg);
+		memset(&buf[2], 0xff, length);
+		xfer[1].len = length;
+		xfer[1].tx_buf = &buf[2];
+		xfer[1].rx_buf = data;
+		if (bdata->block_delay_us)
+			xfer[1].delay_usecs = bdata->block_delay_us;
+		xfer[1].speed_hz = bdata->ubl_max_freq;
+		spi_message_add_tail(&xfer[1], &msg);
+	} else {
+		buf[2] = 0xff;
+		for (idx = 0; idx < byte_count; idx++) {
+			xfer[idx].len = 1;
+			if (idx < 2) {
+				xfer[idx].tx_buf = &buf[idx];
+			} else {
+				xfer[idx].tx_buf = &buf[2];
+				xfer[idx].rx_buf = &data[idx - 2];
+			}
+			xfer[idx].delay_usecs = bdata->ubl_byte_delay_us;
+			if (bdata->block_delay_us && (idx == byte_count - 1))
+				xfer[idx].delay_usecs = bdata->block_delay_us;
+			xfer[idx].speed_hz = bdata->ubl_max_freq;
+			spi_message_add_tail(&xfer[idx], &msg);
+		}
+	}
+
+	mode = spi->mode;
+	spi->mode = SPI_MODE_3;
+
+	retval = spi_sync(spi, &msg);
+	if (retval == 0) {
+		retval = length;
+	} else {
+		LOGE(&spi->dev,
+				"Failed to complete SPI transfer, error = %d\n",
+				retval);
+	}
+
+	spi->mode = mode;
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_spi_rmi_write(struct syna_tcm_hcd *tcm_hcd,
+		unsigned short addr, unsigned char *data, unsigned int length)
+{
+	int retval;
+	unsigned int mode;
+	unsigned int byte_count;
+	struct spi_message msg;
+	struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	spi_message_init(&msg);
+
+	byte_count = length + 2;
+
+	retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, byte_count);
+	if (retval < 0) {
+		LOGE(&spi->dev,
+				"Failed to allocate memory\n");
+		goto exit;
+	}
+
+	buf[0] = (unsigned char)(addr >> 8) & ~0x80;
+	buf[1] = (unsigned char)addr;
+	retval = secure_memcpy(&buf[2],
+			buf_size - 2,
+			data,
+			length,
+			length);
+	if (retval < 0) {
+		LOGE(&spi->dev,
+				"Failed to copy write data\n");
+		goto exit;
+	}
+
+	xfer[0].len = byte_count;
+	xfer[0].tx_buf = buf;
+	if (bdata->block_delay_us)
+		xfer[0].delay_usecs = bdata->block_delay_us;
+	spi_message_add_tail(&xfer[0], &msg);
+
+	mode = spi->mode;
+	spi->mode = SPI_MODE_3;
+
+	retval = spi_sync(spi, &msg);
+	if (retval == 0) {
+		retval = length;
+	} else {
+		LOGE(&spi->dev,
+				"Failed to complete SPI transfer, error = %d\n",
+				retval);
+	}
+
+	spi->mode = mode;
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_spi_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data,
+		unsigned int length)
+{
+	int retval;
+	unsigned int idx;
+	struct spi_message msg;
+	struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	spi_message_init(&msg);
+
+	if (bdata->byte_delay_us == 0)
+		retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, length);
+	else
+		retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory\n");
+		goto exit;
+	}
+
+	if (bdata->byte_delay_us == 0) {
+		memset(buf, 0xff, length);
+		xfer[0].len = length;
+		xfer[0].tx_buf = buf;
+		xfer[0].rx_buf = data;
+		if (bdata->block_delay_us)
+			xfer[0].delay_usecs = bdata->block_delay_us;
+		spi_message_add_tail(&xfer[0], &msg);
+	} else {
+		buf[0] = 0xff;
+		for (idx = 0; idx < length; idx++) {
+			xfer[idx].len = 1;
+			xfer[idx].tx_buf = buf;
+			xfer[idx].rx_buf = &data[idx];
+			xfer[idx].delay_usecs = bdata->byte_delay_us;
+			if (bdata->block_delay_us && (idx == length - 1))
+				xfer[idx].delay_usecs = bdata->block_delay_us;
+			spi_message_add_tail(&xfer[idx], &msg);
+		}
+	}
+
+	retval = spi_sync(spi, &msg);
+	if (retval == 0) {
+		retval = length;
+	} else {
+		LOGE(&spi->dev,
+				"Failed to complete SPI transfer, error = %d\n",
+				retval);
+	}
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_spi_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data,
+		unsigned int length)
+{
+	int retval;
+	unsigned int idx;
+	struct spi_message msg;
+	struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent);
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	mutex_lock(&tcm_hcd->io_ctrl_mutex);
+
+	spi_message_init(&msg);
+
+	if (bdata->byte_delay_us == 0)
+		retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, 0);
+	else
+		retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 0);
+	if (retval < 0) {
+		LOGE(&spi->dev,
+				"Failed to allocate memory\n");
+		goto exit;
+	}
+
+	if (bdata->byte_delay_us == 0) {
+		xfer[0].len = length;
+		xfer[0].tx_buf = data;
+		if (bdata->block_delay_us)
+			xfer[0].delay_usecs = bdata->block_delay_us;
+		spi_message_add_tail(&xfer[0], &msg);
+	} else {
+		for (idx = 0; idx < length; idx++) {
+			xfer[idx].len = 1;
+			xfer[idx].tx_buf = &data[idx];
+			xfer[idx].delay_usecs = bdata->byte_delay_us;
+			if (bdata->block_delay_us && (idx == length - 1))
+				xfer[idx].delay_usecs = bdata->block_delay_us;
+			spi_message_add_tail(&xfer[idx], &msg);
+		}
+	}
+
+	retval = spi_sync(spi, &msg);
+	if (retval == 0) {
+		retval = length;
+	} else {
+		LOGE(&spi->dev,
+				"Failed to complete SPI transfer, error = %d\n",
+				retval);
+	}
+
+exit:
+	mutex_unlock(&tcm_hcd->io_ctrl_mutex);
+
+	return retval;
+}
+
+static int syna_tcm_spi_probe(struct spi_device *spi)
+{
+	int retval;
+
+	if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
+		LOGE(&spi->dev,
+				"Full duplex not supported by host\n");
+		return -EIO;
+	}
+
+	syna_tcm_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0);
+	if (!syna_tcm_spi_device) {
+		LOGE(&spi->dev,
+				"Failed to allocate platform device\n");
+		return -ENOMEM;
+	}
+
+#ifdef CONFIG_OF
+	hw_if.bdata = devm_kzalloc(&spi->dev, sizeof(*hw_if.bdata), GFP_KERNEL);
+	if (!hw_if.bdata) {
+		LOGE(&spi->dev,
+				"Failed to allocate memory for board data\n");
+		return -ENOMEM;
+	}
+	parse_dt(&spi->dev, hw_if.bdata);
+#else
+	hw_if.bdata = spi->dev.platform_data;
+#endif
+
+	switch (hw_if.bdata->spi_mode) {
+	case 0:
+		spi->mode = SPI_MODE_0;
+		break;
+	case 1:
+		spi->mode = SPI_MODE_1;
+		break;
+	case 2:
+		spi->mode = SPI_MODE_2;
+		break;
+	case 3:
+		spi->mode = SPI_MODE_3;
+		break;
+	}
+
+	bus_io.type = BUS_SPI;
+	bus_io.read = syna_tcm_spi_read;
+	bus_io.write = syna_tcm_spi_write;
+	bus_io.rmi_read = syna_tcm_spi_rmi_read;
+	bus_io.rmi_write = syna_tcm_spi_rmi_write;
+
+	hw_if.bus_io = &bus_io;
+
+	spi->bits_per_word = 8;
+
+	retval = spi_setup(spi);
+	if (retval < 0) {
+		LOGE(&spi->dev,
+				"Failed to set up SPI protocol driver\n");
+		return retval;
+	}
+
+	syna_tcm_spi_device->dev.parent = &spi->dev;
+	syna_tcm_spi_device->dev.platform_data = &hw_if;
+
+	retval = platform_device_add(syna_tcm_spi_device);
+	if (retval < 0) {
+		LOGE(&spi->dev,
+				"Failed to add platform device\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int syna_tcm_spi_remove(struct spi_device *spi)
+{
+	syna_tcm_spi_device->dev.platform_data = NULL;
+
+	platform_device_unregister(syna_tcm_spi_device);
+
+	return 0;
+}
+
+static const struct spi_device_id syna_tcm_id_table[] = {
+	{SPI_MODULE_NAME, 0},
+	{},
+};
+MODULE_DEVICE_TABLE(spi, syna_tcm_id_table);
+
+#ifdef CONFIG_OF
+static const struct of_device_id syna_tcm_of_match_table[] = {
+	{
+		.compatible = "synaptics,tcm-spi",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table);
+#else
+#define syna_tcm_of_match_table NULL
+#endif
+
+static struct spi_driver syna_tcm_spi_driver = {
+	.driver = {
+		.name = SPI_MODULE_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = syna_tcm_of_match_table,
+	},
+	.probe = syna_tcm_spi_probe,
+	.remove = syna_tcm_spi_remove,
+	.id_table = syna_tcm_id_table,
+};
+
+int syna_tcm_bus_init_spi(void)
+{
+	return spi_register_driver(&syna_tcm_spi_driver);
+}
+EXPORT_SYMBOL(syna_tcm_bus_init_spi);
+
+void syna_tcm_bus_exit_spi(void)
+{
+	kfree(buf);
+
+	kfree(xfer);
+
+	spi_unregister_driver(&syna_tcm_spi_driver);
+}
+EXPORT_SYMBOL(syna_tcm_bus_exit_spi);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM SPI Bus Module");
+MODULE_LICENSE("GPL v2");

+ 1938 - 0
synaptics_tcm/synaptics_tcm_testing.c

@@ -0,0 +1,1938 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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 "synaptics_tcm_core.h"
+#include "synaptics_tcm_testing.h"
+
+#define SYSFS_DIR_NAME "testing"
+
+#define REPORT_TIMEOUT_MS 500
+
+#define testing_sysfs_show(t_name) \
+static ssize_t testing_sysfs_##t_name##_show(struct device *dev, \
+		struct device_attribute *attr, char *buf) \
+{ \
+	int retval; \
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; \
+\
+	mutex_lock(&tcm_hcd->extif_mutex); \
+\
+	retval = testing_##t_name(); \
+	if (retval < 0) { \
+		LOGE(tcm_hcd->pdev->dev.parent, \
+				"Failed to do "#t_name" test\n"); \
+		goto exit; \
+	} \
+\
+	retval = snprintf(buf, PAGE_SIZE, \
+			"%s\n", \
+			testing_hcd->result ? "Passed" : "Failed"); \
+\
+exit: \
+	mutex_unlock(&tcm_hcd->extif_mutex); \
+\
+	return retval; \
+}
+
+enum test_code {
+	TEST_TRX_TRX_SHORTS = 0,
+	TEST_TRX_SENSOR_OPENS = 1,
+	TEST_TRX_GROUND_SHORTS = 2,
+	TEST_DYNAMIC_RANGE = 7,
+	TEST_OPEN_SHORT_DETECTOR = 8,
+	TEST_NOISE = 10,
+	TEST_PT11 = 11,
+	TEST_PT12 = 12,
+	TEST_PT13 = 13,
+	TEST_DYNAMIC_RANGE_DOZE = 14,
+	TEST_NOISE_DOZE = 15,
+};
+
+struct testing_hcd {
+	bool result;
+	unsigned char report_type;
+	unsigned int report_index;
+	unsigned int num_of_reports;
+	struct kobject *sysfs_dir;
+	struct syna_tcm_buffer out;
+	struct syna_tcm_buffer resp;
+	struct syna_tcm_buffer report;
+	struct syna_tcm_buffer process;
+	struct syna_tcm_buffer output;
+	struct syna_tcm_hcd *tcm_hcd;
+	int (*collect_reports)(enum report_type report_type,
+			unsigned int num_of_reports);
+};
+
+DECLARE_COMPLETION(report_complete);
+
+DECLARE_COMPLETION(testing_remove_complete);
+
+static struct testing_hcd *testing_hcd;
+
+static int testing_dynamic_range(void);
+
+static int testing_dynamic_range_lpwg(void);
+
+static int testing_dynamic_range_doze(void);
+
+static int testing_noise(void);
+
+static int testing_noise_lpwg(void);
+
+static int testing_noise_doze(void);
+
+static int testing_open_short_detector(void);
+
+static int testing_pt11(void);
+
+static int testing_pt12(void);
+
+static int testing_pt13(void);
+
+static int testing_reset_open(void);
+
+static int testing_lockdown(void);
+
+static int testing_trx(enum test_code test_code);
+
+SHOW_PROTOTYPE(testing, dynamic_range);
+SHOW_PROTOTYPE(testing, dynamic_range_lpwg);
+SHOW_PROTOTYPE(testing, dynamic_range_doze);
+SHOW_PROTOTYPE(testing, noise);
+SHOW_PROTOTYPE(testing, noise_lpwg);
+SHOW_PROTOTYPE(testing, noise_doze);
+SHOW_PROTOTYPE(testing, open_short_detector);
+SHOW_PROTOTYPE(testing, pt11);
+SHOW_PROTOTYPE(testing, pt12);
+SHOW_PROTOTYPE(testing, pt13);
+SHOW_PROTOTYPE(testing, reset_open);
+SHOW_PROTOTYPE(testing, lockdown);
+SHOW_PROTOTYPE(testing, trx_trx_shorts);
+SHOW_PROTOTYPE(testing, trx_sensor_opens);
+SHOW_PROTOTYPE(testing, trx_ground_shorts);
+SHOW_PROTOTYPE(testing, size);
+
+static struct device_attribute *attrs[] = {
+	ATTRIFY(dynamic_range),
+	ATTRIFY(dynamic_range_lpwg),
+	ATTRIFY(dynamic_range_doze),
+	ATTRIFY(noise),
+	ATTRIFY(noise_lpwg),
+	ATTRIFY(noise_doze),
+	ATTRIFY(open_short_detector),
+	ATTRIFY(pt11),
+	ATTRIFY(pt12),
+	ATTRIFY(pt13),
+	ATTRIFY(reset_open),
+	ATTRIFY(lockdown),
+	ATTRIFY(trx_trx_shorts),
+	ATTRIFY(trx_sensor_opens),
+	ATTRIFY(trx_ground_shorts),
+	ATTRIFY(size),
+};
+
+static ssize_t testing_sysfs_data_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count);
+
+static struct bin_attribute bin_attr = {
+	.attr = {
+		.name = "data",
+		.mode = 0444,
+	},
+	.size = 0,
+	.read = testing_sysfs_data_show,
+};
+
+testing_sysfs_show(dynamic_range)
+
+testing_sysfs_show(dynamic_range_lpwg)
+
+testing_sysfs_show(dynamic_range_doze)
+
+testing_sysfs_show(noise)
+
+testing_sysfs_show(noise_lpwg)
+
+testing_sysfs_show(noise_doze)
+
+testing_sysfs_show(open_short_detector)
+
+testing_sysfs_show(pt11)
+
+testing_sysfs_show(pt12)
+
+testing_sysfs_show(pt13)
+
+testing_sysfs_show(reset_open)
+
+testing_sysfs_show(lockdown)
+
+static ssize_t testing_sysfs_trx_trx_shorts_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = testing_trx(TEST_TRX_TRX_SHORTS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do TRX-TRX shorts test\n");
+		goto exit;
+	}
+
+	retval = snprintf(buf, PAGE_SIZE,
+			"%s\n",
+			testing_hcd->result ? "Passed" : "Failed");
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t testing_sysfs_trx_sensor_opens_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = testing_trx(TEST_TRX_SENSOR_OPENS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do TRX-sensor opens test\n");
+		goto exit;
+	}
+
+	retval = snprintf(buf, PAGE_SIZE,
+			"%s\n",
+			testing_hcd->result ? "Passed" : "Failed");
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t testing_sysfs_trx_ground_shorts_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	retval = testing_trx(TEST_TRX_GROUND_SHORTS);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do TRX-ground shorts test\n");
+		goto exit;
+	}
+
+	retval = snprintf(buf, PAGE_SIZE,
+			"%s\n",
+			testing_hcd->result ? "Passed" : "Failed");
+
+exit:
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t testing_sysfs_size_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	LOCK_BUFFER(testing_hcd->output);
+
+	retval = snprintf(buf, PAGE_SIZE,
+			"%u\n",
+			testing_hcd->output.data_length);
+
+	UNLOCK_BUFFER(testing_hcd->output);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static ssize_t testing_sysfs_data_show(struct file *data_file,
+		struct kobject *kobj, struct bin_attribute *attributes,
+		char *buf, loff_t pos, size_t count)
+{
+	int retval;
+	unsigned int readlen;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	mutex_lock(&tcm_hcd->extif_mutex);
+
+	LOCK_BUFFER(testing_hcd->output);
+
+	readlen = MIN(count, testing_hcd->output.data_length - pos);
+
+	retval = secure_memcpy(buf,
+			count,
+			&testing_hcd->output.buf[pos],
+			testing_hcd->output.buf_size - pos,
+			readlen);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to copy report data\n");
+	} else {
+		retval = readlen;
+	}
+
+	UNLOCK_BUFFER(testing_hcd->output);
+
+	mutex_unlock(&tcm_hcd->extif_mutex);
+
+	return retval;
+}
+
+static int testing_run_prod_test_item(enum test_code test_code)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	if (tcm_hcd->features.dual_firmware &&
+			tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) {
+		retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_PRODUCTION_TEST);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to run production test firmware\n");
+			return retval;
+		}
+	} else if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Application firmware not running\n");
+		return -ENODEV;
+	}
+
+	LOCK_BUFFER(testing_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->out,
+			1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for testing_hcd->out.buf\n");
+		UNLOCK_BUFFER(testing_hcd->out);
+		return retval;
+	}
+
+	testing_hcd->out.buf[0] = test_code;
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_PRODUCTION_TEST,
+			testing_hcd->out.buf,
+			1,
+			&testing_hcd->resp.buf,
+			&testing_hcd->resp.buf_size,
+			&testing_hcd->resp.data_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_PRODUCTION_TEST));
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->out);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+	UNLOCK_BUFFER(testing_hcd->out);
+
+	return 0;
+}
+
+static int testing_collect_reports(enum report_type report_type,
+		unsigned int num_of_reports)
+{
+	int retval;
+	bool completed;
+	unsigned int timeout;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	testing_hcd->report_index = 0;
+	testing_hcd->report_type = report_type;
+	testing_hcd->num_of_reports = num_of_reports;
+
+	reinit_completion(&report_complete);
+
+	LOCK_BUFFER(testing_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->out,
+			1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for testing_hcd->out.buf\n");
+		UNLOCK_BUFFER(testing_hcd->out);
+		goto exit;
+	}
+
+	testing_hcd->out.buf[0] = testing_hcd->report_type;
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_ENABLE_REPORT,
+			testing_hcd->out.buf,
+			1,
+			&testing_hcd->resp.buf,
+			&testing_hcd->resp.buf_size,
+			&testing_hcd->resp.data_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_ENABLE_REPORT));
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->out);
+		goto exit;
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+	UNLOCK_BUFFER(testing_hcd->out);
+
+	completed = false;
+	timeout = REPORT_TIMEOUT_MS * num_of_reports;
+
+	retval = wait_for_completion_timeout(&report_complete,
+			msecs_to_jiffies(timeout));
+	if (retval == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Timed out waiting for report collection\n");
+	} else {
+		completed = true;
+	}
+
+	LOCK_BUFFER(testing_hcd->out);
+
+	testing_hcd->out.buf[0] = testing_hcd->report_type;
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_DISABLE_REPORT,
+			testing_hcd->out.buf,
+			1,
+			&testing_hcd->resp.buf,
+			&testing_hcd->resp.buf_size,
+			&testing_hcd->resp.data_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_DISABLE_REPORT));
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->out);
+		goto exit;
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+	UNLOCK_BUFFER(testing_hcd->out);
+
+	if (completed)
+		retval = 0;
+	else
+		retval = -EIO;
+
+exit:
+	testing_hcd->report_type = 0;
+
+	return retval;
+}
+
+static void testing_get_frame_size_words(unsigned int *size, bool image_only)
+{
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int hybrid;
+	unsigned int buttons;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+	hybrid = le2_to_uint(app_info->has_hybrid_data);
+	buttons = le2_to_uint(app_info->num_of_buttons);
+
+	*size = rows * cols;
+
+	if (!image_only) {
+		if (hybrid)
+			*size += rows + cols;
+		*size += buttons;
+	}
+}
+
+static void testing_doze_frame_output(unsigned int rows, unsigned int cols)
+{
+	int retval;
+	unsigned int data_size;
+	unsigned int header_size;
+	unsigned int output_size;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	header_size = 2;
+
+	data_size = rows * cols;
+
+	if (le2_to_uint(app_info->num_of_buttons))
+		data_size++;
+
+	output_size = header_size + data_size * 2;
+
+	LOCK_BUFFER(testing_hcd->output);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->output,
+			output_size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for testing_hcd->output.buf\n");
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	testing_hcd->output.buf[0] = rows;
+	testing_hcd->output.buf[1] = cols;
+
+	output_size = header_size;
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = secure_memcpy(testing_hcd->output.buf + header_size,
+			testing_hcd->output.buf_size - header_size,
+			testing_hcd->resp.buf,
+			testing_hcd->resp.buf_size,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy test data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	output_size += testing_hcd->resp.data_length;
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_hcd->output.data_length = output_size;
+
+	UNLOCK_BUFFER(testing_hcd->output);
+}
+
+static void testing_standard_frame_output(bool image_only)
+{
+	int retval;
+	unsigned int data_size;
+	unsigned int header_size;
+	unsigned int output_size;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	testing_get_frame_size_words(&data_size, image_only);
+
+	header_size = sizeof(app_info->num_of_buttons) +
+			sizeof(app_info->num_of_image_rows) +
+			sizeof(app_info->num_of_image_cols) +
+			sizeof(app_info->has_hybrid_data);
+
+	output_size = header_size + data_size * 2;
+
+	LOCK_BUFFER(testing_hcd->output);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->output,
+			output_size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for testing_hcd->output.buf\n");
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	retval = secure_memcpy(testing_hcd->output.buf,
+			testing_hcd->output.buf_size,
+			&app_info->num_of_buttons[0],
+			header_size,
+			header_size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy header data\n");
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	output_size = header_size;
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = secure_memcpy(testing_hcd->output.buf + header_size,
+			testing_hcd->output.buf_size - header_size,
+			testing_hcd->resp.buf,
+			testing_hcd->resp.buf_size,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy test data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	output_size += testing_hcd->resp.data_length;
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_hcd->output.data_length = output_size;
+
+	UNLOCK_BUFFER(testing_hcd->output);
+}
+
+static int testing_dynamic_range_doze(void)
+{
+	int retval;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int data;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int data_size;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE_DOZE);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	data_size = testing_hcd->resp.data_length / 2;
+
+	if (le2_to_uint(app_info->num_of_buttons))
+		data_size--;
+
+	if (data_size % cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid max number of rows per burst\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	rows = data_size / cols;
+
+	limits_rows = ARRAY_SIZE(drt_hi_limits);
+	limits_cols = ARRAY_SIZE(drt_hi_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(drt_lo_limits);
+	limits_cols = ARRAY_SIZE(drt_lo_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = le2_to_uint(&buf[idx * 2]);
+			if (data > drt_hi_limits[row][col] ||
+					data < drt_lo_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_doze_frame_output(rows, cols);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static int testing_dynamic_range_lpwg(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd,
+			DC_IN_WAKEUP_GESTURE_MODE,
+			1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable wakeup gesture mode\n");
+		return retval;
+	}
+
+	retval = testing_dynamic_range();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do dynamic range test\n");
+		return retval;
+	}
+
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd,
+			DC_IN_WAKEUP_GESTURE_MODE,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to disable wakeup gesture mode\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int testing_dynamic_range(void)
+{
+	int retval;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int data;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	unsigned int frame_size_words;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	testing_get_frame_size_words(&frame_size_words, false);
+
+	retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	if (frame_size_words != testing_hcd->resp.data_length / 2) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Frame size mismatch\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(drt_hi_limits);
+	limits_cols = ARRAY_SIZE(drt_hi_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(drt_lo_limits);
+	limits_cols = ARRAY_SIZE(drt_lo_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = le2_to_uint(&buf[idx * 2]);
+			if (data > drt_hi_limits[row][col] ||
+					data < drt_lo_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_standard_frame_output(false);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static int testing_noise_doze(void)
+{
+	int retval;
+	short data;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int data_size;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	retval = testing_run_prod_test_item(TEST_NOISE_DOZE);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	data_size = testing_hcd->resp.data_length / 2;
+
+	if (le2_to_uint(app_info->num_of_buttons))
+		data_size--;
+
+	if (data_size % cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid max number of rows per burst\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	rows = data_size / cols;
+
+	limits_rows = ARRAY_SIZE(noise_limits);
+	limits_cols = ARRAY_SIZE(noise_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = (short)le2_to_uint(&buf[idx * 2]);
+			if (data > noise_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_doze_frame_output(rows, cols);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static int testing_noise_lpwg(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd,
+			DC_IN_WAKEUP_GESTURE_MODE,
+			1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable wakeup gesture mode\n");
+		return retval;
+	}
+
+	retval = testing_noise();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do noise test\n");
+		return retval;
+	}
+
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd,
+			DC_IN_WAKEUP_GESTURE_MODE,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to disable wakeup gesture mode\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int testing_noise(void)
+{
+	int retval;
+	short data;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	unsigned int frame_size_words;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	testing_get_frame_size_words(&frame_size_words, false);
+
+	retval = testing_run_prod_test_item(TEST_NOISE);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	if (frame_size_words != testing_hcd->resp.data_length / 2) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Frame size mismatch\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(noise_limits);
+	limits_cols = ARRAY_SIZE(noise_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = (short)le2_to_uint(&buf[idx * 2]);
+			if (data > noise_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_standard_frame_output(false);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static void testing_open_short_detector_output(void)
+{
+	int retval;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int data_size;
+	unsigned int header_size;
+	unsigned int output_size;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+	data_size = (rows * cols + 7) / 8;
+
+	header_size = sizeof(app_info->num_of_buttons) +
+			sizeof(app_info->num_of_image_rows) +
+			sizeof(app_info->num_of_image_cols) +
+			sizeof(app_info->has_hybrid_data);
+
+	output_size = header_size + data_size * 2;
+
+	LOCK_BUFFER(testing_hcd->output);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->output,
+			output_size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for output.buf\n");
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	retval = secure_memcpy(testing_hcd->output.buf,
+			testing_hcd->output.buf_size,
+			&app_info->num_of_buttons[0],
+			header_size,
+			header_size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy header data\n");
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	output_size = header_size;
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = secure_memcpy(testing_hcd->output.buf + header_size,
+			testing_hcd->output.buf_size - header_size,
+			testing_hcd->resp.buf,
+			testing_hcd->resp.buf_size,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy test data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	output_size += testing_hcd->resp.data_length;
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_hcd->output.data_length = output_size;
+
+	UNLOCK_BUFFER(testing_hcd->output);
+}
+
+static int testing_open_short_detector(void)
+{
+	int retval;
+	unsigned int bit;
+	unsigned int byte;
+	unsigned int row;
+	unsigned int col;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int data_size;
+	unsigned char *data;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+	data_size = (rows * cols + 7) / 8;
+
+	retval = testing_run_prod_test_item(TEST_OPEN_SHORT_DETECTOR);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	if (data_size * 2 != testing_hcd->resp.data_length) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Data size mismatch\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	testing_hcd->result = true;
+
+	bit = 0;
+	byte = 0;
+	data = &testing_hcd->resp.buf[0];
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			if (data[byte] & (1 << bit)) {
+				testing_hcd->result = false;
+				break;
+			}
+			if (bit++ > 7) {
+				bit = 0;
+				byte++;
+			}
+		}
+	}
+
+	if (testing_hcd->result == true) {
+		bit = 0;
+		byte = 0;
+		data = &testing_hcd->resp.buf[data_size];
+		for (row = 0; row < rows; row++) {
+			for (col = 0; col < cols; col++) {
+				if (data[byte] & (1 << bit)) {
+					testing_hcd->result = false;
+					break;
+				}
+				if (bit++ > 7) {
+					bit = 0;
+					byte++;
+				}
+			}
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_open_short_detector_output();
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to do reset\n");
+	}
+
+	return retval;
+}
+
+static int testing_pt11(void)
+{
+	int retval;
+	short data;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	unsigned int image_size_words;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	testing_get_frame_size_words(&image_size_words, true);
+
+	retval = testing_run_prod_test_item(TEST_PT11);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	if (image_size_words != testing_hcd->resp.data_length / 2) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Image size mismatch\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(pt11_hi_limits);
+	limits_cols = ARRAY_SIZE(pt11_hi_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(pt11_lo_limits);
+	limits_cols = ARRAY_SIZE(pt11_lo_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = (short)le2_to_uint(&buf[idx * 2]);
+			if (data > pt11_hi_limits[row][col] ||
+					data < pt11_lo_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_standard_frame_output(true);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static int testing_pt12(void)
+{
+	int retval;
+	short data;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	unsigned int image_size_words;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	testing_get_frame_size_words(&image_size_words, true);
+
+	retval = testing_run_prod_test_item(TEST_PT12);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	if (image_size_words != testing_hcd->resp.data_length / 2) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Image size mismatch\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(pt12_limits);
+	limits_cols = ARRAY_SIZE(pt12_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = (short)le2_to_uint(&buf[idx * 2]);
+			if (data < pt12_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_standard_frame_output(true);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static int testing_pt13(void)
+{
+	int retval;
+	short data;
+	unsigned char *buf;
+	unsigned int idx;
+	unsigned int row;
+	unsigned int col;
+	unsigned int rows;
+	unsigned int cols;
+	unsigned int limits_rows;
+	unsigned int limits_cols;
+	unsigned int image_size_words;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	app_info = &tcm_hcd->app_info;
+
+	rows = le2_to_uint(app_info->num_of_image_rows);
+	cols = le2_to_uint(app_info->num_of_image_cols);
+
+	testing_get_frame_size_words(&image_size_words, true);
+
+	retval = testing_run_prod_test_item(TEST_PT13);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	if (image_size_words != testing_hcd->resp.data_length / 2) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Image size mismatch\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	limits_rows = ARRAY_SIZE(pt13_limits);
+	limits_cols = ARRAY_SIZE(pt13_limits[0]);
+
+	if (rows > limits_rows || cols > limits_cols) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		retval = -EINVAL;
+		goto exit;
+	}
+
+	idx = 0;
+	buf = testing_hcd->resp.buf;
+	testing_hcd->result = true;
+
+	for (row = 0; row < rows; row++) {
+		for (col = 0; col < cols; col++) {
+			data = (short)le2_to_uint(&buf[idx * 2]);
+			if (data < pt13_limits[row][col]) {
+				testing_hcd->result = false;
+				break;
+			}
+			idx++;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_standard_frame_output(true);
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static int testing_reset_open(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	if (bdata->reset_gpio < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Hardware reset unavailable\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&tcm_hcd->reset_mutex);
+
+	tcm_hcd->update_watchdog(tcm_hcd, false);
+
+	gpio_set_value(bdata->reset_gpio, bdata->reset_on_state);
+	msleep(bdata->reset_active_ms);
+	gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state);
+	msleep(bdata->reset_delay_ms);
+
+	tcm_hcd->update_watchdog(tcm_hcd, true);
+
+	mutex_unlock(&tcm_hcd->reset_mutex);
+
+	if (tcm_hcd->id_info.mode == MODE_APPLICATION) {
+		retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to enter bootloader mode\n");
+			return retval;
+		}
+	} else {
+		retval = tcm_hcd->identify(tcm_hcd, false);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do identification\n");
+			goto run_app_firmware;
+		}
+	}
+
+	if (tcm_hcd->boot_info.last_reset_reason == reset_open_limit)
+		testing_hcd->result = true;
+	else
+		testing_hcd->result = false;
+
+	retval = 0;
+
+run_app_firmware:
+	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run application firmware\n");
+	}
+
+	return retval;
+}
+
+static void testing_lockdown_output(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	LOCK_BUFFER(testing_hcd->output);
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->output,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for output.buf\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	retval = secure_memcpy(testing_hcd->output.buf,
+			testing_hcd->output.buf_size,
+			testing_hcd->resp.buf,
+			testing_hcd->resp.buf_size,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy test data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	testing_hcd->output.data_length = testing_hcd->resp.data_length;
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+	UNLOCK_BUFFER(testing_hcd->output);
+}
+
+static int testing_lockdown(void)
+{
+	int retval;
+	unsigned int idx;
+	unsigned int lockdown_size;
+	unsigned int limits_size;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	if (tcm_hcd->read_flash_data == NULL) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Unable to read from flash\n");
+		return -EINVAL;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = tcm_hcd->read_flash_data(CUSTOM_OTP, true, &testing_hcd->resp);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read lockdown data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		return retval;
+	}
+
+	lockdown_size = testing_hcd->resp.data_length;
+
+	limits_size = sizeof(lockdown_limits) / sizeof(*lockdown_limits);
+
+	if (lockdown_size != limits_size) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Mismatching limits data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		return -EINVAL;
+	}
+
+	testing_hcd->result = true;
+
+	for (idx = 0; idx < lockdown_size; idx++) {
+		if (testing_hcd->resp.buf[idx] != lockdown_limits[idx]) {
+			testing_hcd->result = false;
+			break;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_lockdown_output();
+
+	return 0;
+}
+
+static void testing_trx_output(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	LOCK_BUFFER(testing_hcd->output);
+	LOCK_BUFFER(testing_hcd->resp);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&testing_hcd->output,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for output.buf\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	retval = secure_memcpy(testing_hcd->output.buf,
+			testing_hcd->output.buf_size,
+			testing_hcd->resp.buf,
+			testing_hcd->resp.buf_size,
+			testing_hcd->resp.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy test data\n");
+		UNLOCK_BUFFER(testing_hcd->resp);
+		UNLOCK_BUFFER(testing_hcd->output);
+		return;
+	}
+
+	testing_hcd->output.data_length = testing_hcd->resp.data_length;
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+	UNLOCK_BUFFER(testing_hcd->output);
+}
+
+static int testing_trx(enum test_code test_code)
+{
+	int retval;
+	unsigned char pass_vector;
+	unsigned int idx;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	switch (test_code) {
+	case TEST_TRX_TRX_SHORTS:
+	case TEST_TRX_GROUND_SHORTS:
+		pass_vector = 0xff;
+		break;
+	case TEST_TRX_SENSOR_OPENS:
+		pass_vector = 0x00;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	retval = testing_run_prod_test_item(test_code);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to run test\n");
+		goto exit;
+	}
+
+	LOCK_BUFFER(testing_hcd->resp);
+
+	testing_hcd->result = true;
+
+	for (idx = 0; idx < testing_hcd->resp.data_length; idx++) {
+		if (testing_hcd->resp.buf[idx] != pass_vector) {
+			testing_hcd->result = false;
+			break;
+		}
+	}
+
+	UNLOCK_BUFFER(testing_hcd->resp);
+
+	testing_trx_output();
+
+	retval = 0;
+
+exit:
+	if (tcm_hcd->features.dual_firmware) {
+		if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do reset\n");
+		}
+	}
+
+	return retval;
+}
+
+static void testing_report(void)
+{
+	int retval;
+	unsigned int offset;
+	unsigned int report_size;
+	struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd;
+
+	report_size = tcm_hcd->report.buffer.data_length;
+
+	LOCK_BUFFER(testing_hcd->report);
+
+	if (testing_hcd->report_index == 0) {
+		retval = syna_tcm_alloc_mem(tcm_hcd,
+				&testing_hcd->report,
+				report_size * testing_hcd->num_of_reports);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for report.buf\n");
+			UNLOCK_BUFFER(testing_hcd->report);
+			return;
+		}
+	}
+
+	if (testing_hcd->report_index < testing_hcd->num_of_reports) {
+		offset = report_size * testing_hcd->report_index;
+
+		retval = secure_memcpy(testing_hcd->report.buf + offset,
+				testing_hcd->report.buf_size - offset,
+				tcm_hcd->report.buffer.buf,
+				tcm_hcd->report.buffer.buf_size,
+				tcm_hcd->report.buffer.data_length);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy report data\n");
+			UNLOCK_BUFFER(testing_hcd->report);
+			return;
+		}
+
+		testing_hcd->report_index++;
+		testing_hcd->report.data_length += report_size;
+	}
+
+	UNLOCK_BUFFER(testing_hcd->report);
+
+	if (testing_hcd->report_index == testing_hcd->num_of_reports)
+		complete(&report_complete);
+}
+
+static int testing_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	int idx;
+
+	testing_hcd = kzalloc(sizeof(*testing_hcd), GFP_KERNEL);
+	if (!testing_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for testing_hcd\n");
+		return -ENOMEM;
+	}
+
+	testing_hcd->tcm_hcd = tcm_hcd;
+
+	testing_hcd->collect_reports = testing_collect_reports;
+
+	INIT_BUFFER(testing_hcd->out, false);
+	INIT_BUFFER(testing_hcd->resp, false);
+	INIT_BUFFER(testing_hcd->report, false);
+	INIT_BUFFER(testing_hcd->process, false);
+	INIT_BUFFER(testing_hcd->output, false);
+
+	testing_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
+			tcm_hcd->sysfs_dir);
+	if (!testing_hcd->sysfs_dir) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs directory\n");
+		retval = -EINVAL;
+		goto err_sysfs_create_dir;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
+		retval = sysfs_create_file(testing_hcd->sysfs_dir,
+				&(*attrs[idx]).attr);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to create sysfs file\n");
+			goto err_sysfs_create_file;
+		}
+	}
+
+	retval = sysfs_create_bin_file(testing_hcd->sysfs_dir, &bin_attr);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to create sysfs bin file\n");
+		goto err_sysfs_create_bin_file;
+	}
+
+	return 0;
+
+err_sysfs_create_bin_file:
+err_sysfs_create_file:
+	for (idx--; idx >= 0; idx--)
+		sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(testing_hcd->sysfs_dir);
+
+err_sysfs_create_dir:
+	RELEASE_BUFFER(testing_hcd->output);
+	RELEASE_BUFFER(testing_hcd->process);
+	RELEASE_BUFFER(testing_hcd->report);
+	RELEASE_BUFFER(testing_hcd->resp);
+	RELEASE_BUFFER(testing_hcd->out);
+
+	kfree(testing_hcd);
+	testing_hcd = NULL;
+
+	return retval;
+}
+
+static int testing_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	int idx;
+
+	if (!testing_hcd)
+		goto exit;
+
+	sysfs_remove_bin_file(testing_hcd->sysfs_dir, &bin_attr);
+
+	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++)
+		sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr);
+
+	kobject_put(testing_hcd->sysfs_dir);
+
+	RELEASE_BUFFER(testing_hcd->output);
+	RELEASE_BUFFER(testing_hcd->process);
+	RELEASE_BUFFER(testing_hcd->report);
+	RELEASE_BUFFER(testing_hcd->resp);
+	RELEASE_BUFFER(testing_hcd->out);
+
+	kfree(testing_hcd);
+	testing_hcd = NULL;
+
+exit:
+	complete(&testing_remove_complete);
+
+	return 0;
+}
+
+static int testing_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!testing_hcd) {
+		retval = testing_init(tcm_hcd);
+		return retval;
+	}
+
+	return 0;
+}
+
+static int testing_syncbox(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!testing_hcd)
+		return 0;
+
+	if (tcm_hcd->report.id == testing_hcd->report_type)
+		testing_report();
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb testing_module = {
+	.type = TCM_TESTING,
+	.init = testing_init,
+	.remove = testing_remove,
+	.syncbox = testing_syncbox,
+	.asyncbox = NULL,
+	.reset = testing_reset,
+	.suspend = NULL,
+	.resume = NULL,
+	.early_suspend = NULL,
+};
+
+static int __init testing_module_init(void)
+{
+	return syna_tcm_add_module(&testing_module, true);
+}
+
+static void __exit testing_module_exit(void)
+{
+	syna_tcm_add_module(&testing_module, false);
+
+	wait_for_completion(&testing_remove_complete);
+}
+
+module_init(testing_module_init);
+module_exit(testing_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Testing Module");
+MODULE_LICENSE("GPL v2");

+ 85 - 0
synaptics_tcm/synaptics_tcm_testing.h

@@ -0,0 +1,85 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
+ * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
+ * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
+ * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
+ * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
+ * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
+ * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
+ * DOLLARS.
+ */
+
+#ifndef _SYNAPTICS_TCM_TESTING_H_
+#define _SYNAPTICS_TCM_TESTING_H_
+
+static const unsigned short drt_hi_limits[32][32] = {
+	{0},
+};
+
+static const unsigned short drt_lo_limits[32][32] = {
+	{0,},
+};
+
+static const unsigned short noise_limits[32][32] = {
+	{0,},
+};
+
+static const short pt11_hi_limits[32][32] = {
+	{0,},
+};
+
+static const short pt11_lo_limits[32][32] = {
+	{0,},
+};
+
+static const short pt12_limits[32][32] = {
+	{0,},
+};
+
+static const short pt13_limits[32][32] = {
+	{0,},
+};
+
+static const unsigned char lockdown_limits[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+static const unsigned char reset_open_limit = 0x13;
+
+#endif

+ 1267 - 0
synaptics_tcm/synaptics_tcm_touch.c

@@ -0,0 +1,1267 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/input/mt.h>
+#include <linux/interrupt.h>
+#include "synaptics_tcm_core.h"
+
+#define TYPE_B_PROTOCOL
+
+#define USE_DEFAULT_TOUCH_REPORT_CONFIG
+
+#define TOUCH_REPORT_CONFIG_SIZE 128
+
+enum touch_status {
+	LIFT = 0,
+	FINGER = 1,
+	GLOVED_FINGER = 2,
+	NOP = -1,
+};
+
+enum touch_report_code {
+	TOUCH_END = 0,
+	TOUCH_FOREACH_ACTIVE_OBJECT,
+	TOUCH_FOREACH_OBJECT,
+	TOUCH_FOREACH_END,
+	TOUCH_PAD_TO_NEXT_BYTE,
+	TOUCH_TIMESTAMP,
+	TOUCH_OBJECT_N_INDEX,
+	TOUCH_OBJECT_N_CLASSIFICATION,
+	TOUCH_OBJECT_N_X_POSITION,
+	TOUCH_OBJECT_N_Y_POSITION,
+	TOUCH_OBJECT_N_Z,
+	TOUCH_OBJECT_N_X_WIDTH,
+	TOUCH_OBJECT_N_Y_WIDTH,
+	TOUCH_OBJECT_N_TX_POSITION_TIXELS,
+	TOUCH_OBJECT_N_RX_POSITION_TIXELS,
+	TOUCH_0D_BUTTONS_STATE,
+	TOUCH_GESTURE_DOUBLE_TAP,
+	TOUCH_FRAME_RATE,
+	TOUCH_POWER_IM,
+	TOUCH_CID_IM,
+	TOUCH_RAIL_IM,
+	TOUCH_CID_VARIANCE_IM,
+	TOUCH_NSM_FREQUENCY,
+	TOUCH_NSM_STATE,
+	TOUCH_NUM_OF_ACTIVE_OBJECTS,
+	TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME,
+	TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80,
+	TOUCH_TUNING_SMALL_OBJECT_PARAMS,
+	TOUCH_TUNING_0D_BUTTONS_VARIANCE,
+};
+
+struct object_data {
+	unsigned char status;
+	unsigned int x_pos;
+	unsigned int y_pos;
+	unsigned int x_width;
+	unsigned int y_width;
+	unsigned int z;
+	unsigned int tx_pos;
+	unsigned int rx_pos;
+};
+
+struct input_params {
+	unsigned int max_x;
+	unsigned int max_y;
+	unsigned int max_objects;
+};
+
+struct touch_data {
+	struct object_data *object_data;
+	unsigned int timestamp;
+	unsigned int buttons_state;
+	unsigned int gesture_double_tap;
+	unsigned int frame_rate;
+	unsigned int power_im;
+	unsigned int cid_im;
+	unsigned int rail_im;
+	unsigned int cid_variance_im;
+	unsigned int nsm_frequency;
+	unsigned int nsm_state;
+	unsigned int num_of_active_objects;
+	unsigned int num_of_cpu_cycles;
+};
+
+struct touch_hcd {
+	bool irq_wake;
+	bool report_touch;
+	bool suspend_touch;
+	unsigned char *prev_status;
+	unsigned int max_x;
+	unsigned int max_y;
+	unsigned int max_objects;
+	struct mutex report_mutex;
+	struct input_dev *input_dev;
+	struct touch_data touch_data;
+	struct input_params input_params;
+	struct syna_tcm_buffer out;
+	struct syna_tcm_buffer resp;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+DECLARE_COMPLETION(touch_remove_complete);
+
+static struct touch_hcd *touch_hcd;
+
+/**
+ * touch_free_objects() - Free all touch objects
+ *
+ * Report finger lift events to the input subsystem for all touch objects.
+ */
+static void touch_free_objects(void)
+{
+#ifdef TYPE_B_PROTOCOL
+	unsigned int idx;
+#endif
+
+	if (touch_hcd->input_dev == NULL)
+		return;
+
+	mutex_lock(&touch_hcd->report_mutex);
+
+#ifdef TYPE_B_PROTOCOL
+	for (idx = 0; idx < touch_hcd->max_objects; idx++) {
+		input_mt_slot(touch_hcd->input_dev, idx);
+		input_mt_report_slot_state(touch_hcd->input_dev,
+				MT_TOOL_FINGER, 0);
+	}
+#endif
+	input_report_key(touch_hcd->input_dev,
+			BTN_TOUCH, 0);
+	input_report_key(touch_hcd->input_dev,
+			BTN_TOOL_FINGER, 0);
+#ifndef TYPE_B_PROTOCOL
+	input_mt_sync(touch_hcd->input_dev);
+#endif
+	input_sync(touch_hcd->input_dev);
+
+	mutex_unlock(&touch_hcd->report_mutex);
+}
+
+/**
+ * touch_get_report_data() - Retrieve data from touch report
+ *
+ * Retrieve data from the touch report based on the bit offset and bit length
+ * information from the touch report configuration.
+ */
+static int touch_get_report_data(unsigned int offset,
+		unsigned int bits, unsigned int *data)
+{
+	unsigned char mask;
+	unsigned char byte_data;
+	unsigned int output_data;
+	unsigned int bit_offset;
+	unsigned int byte_offset;
+	unsigned int data_bits;
+	unsigned int available_bits;
+	unsigned int remaining_bits;
+	unsigned char *touch_report;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+
+	if (bits == 0 || bits > 32) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid number of bits\n");
+		return -EINVAL;
+	}
+
+	if (offset + bits > tcm_hcd->report.buffer.data_length * 8) {
+		*data = 0;
+		return 0;
+	}
+
+	touch_report = tcm_hcd->report.buffer.buf;
+
+	output_data = 0;
+	remaining_bits = bits;
+
+	bit_offset = offset % 8;
+	byte_offset = offset / 8;
+
+	while (remaining_bits) {
+		byte_data = touch_report[byte_offset];
+		byte_data >>= bit_offset;
+
+		available_bits = 8 - bit_offset;
+		data_bits = MIN(available_bits, remaining_bits);
+		mask = 0xff >> (8 - data_bits);
+
+		byte_data &= mask;
+
+		output_data |= byte_data << (bits - remaining_bits);
+
+		bit_offset = 0;
+		byte_offset += 1;
+		remaining_bits -= data_bits;
+	}
+
+	*data = output_data;
+
+	return 0;
+}
+
+/**
+ * touch_parse_report() - Parse touch report
+ *
+ * Traverse through the touch report configuration and parse the touch report
+ * generated by the device accordingly to retrieve the touch data.
+ */
+static int touch_parse_report(void)
+{
+	int retval;
+	bool active_only;
+	bool num_of_active_objects;
+	unsigned char code;
+	unsigned int size;
+	unsigned int idx;
+	unsigned int obj;
+	unsigned int next;
+	unsigned int data;
+	unsigned int bits;
+	unsigned int offset;
+	unsigned int objects;
+	unsigned int active_objects;
+	unsigned int report_size;
+	unsigned int config_size;
+	unsigned char *config_data;
+	struct touch_data *touch_data;
+	struct object_data *object_data;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+	static unsigned int end_of_foreach;
+
+	touch_data = &touch_hcd->touch_data;
+	object_data = touch_hcd->touch_data.object_data;
+
+	config_data = tcm_hcd->config.buf;
+	config_size = tcm_hcd->config.data_length;
+
+	report_size = tcm_hcd->report.buffer.data_length;
+
+	size = sizeof(*object_data) * touch_hcd->max_objects;
+	memset(touch_hcd->touch_data.object_data, 0x00, size);
+
+	num_of_active_objects = false;
+
+	idx = 0;
+	offset = 0;
+	objects = 0;
+	while (idx < config_size) {
+		code = config_data[idx++];
+		switch (code) {
+		case TOUCH_END:
+			goto exit;
+		case TOUCH_FOREACH_ACTIVE_OBJECT:
+			obj = 0;
+			next = idx;
+			active_only = true;
+			break;
+		case TOUCH_FOREACH_OBJECT:
+			obj = 0;
+			next = idx;
+			active_only = false;
+			break;
+		case TOUCH_FOREACH_END:
+			end_of_foreach = idx;
+			if (active_only) {
+				if (num_of_active_objects) {
+					objects++;
+					if (objects < active_objects)
+						idx = next;
+				} else if (offset < report_size * 8) {
+					idx = next;
+				}
+			} else {
+				obj++;
+				if (obj < touch_hcd->max_objects)
+					idx = next;
+			}
+			break;
+		case TOUCH_PAD_TO_NEXT_BYTE:
+			offset = ceil_div(offset, 8) * 8;
+			break;
+		case TOUCH_TIMESTAMP:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get timestamp\n");
+				return retval;
+			}
+			touch_data->timestamp = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_INDEX:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &obj);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get object index\n");
+				return retval;
+			}
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_CLASSIFICATION:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get classification data\n");
+				return retval;
+			}
+			object_data[obj].status = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_X_POSITION:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get object x position\n");
+				return retval;
+			}
+			object_data[obj].x_pos = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_Y_POSITION:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get object y position\n");
+				return retval;
+			}
+			object_data[obj].y_pos = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_Z:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get object z\n");
+				return retval;
+			}
+			object_data[obj].z = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_X_WIDTH:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get object x width\n");
+				return retval;
+			}
+			object_data[obj].x_width = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_Y_WIDTH:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get object y width\n");
+				return retval;
+			}
+			object_data[obj].y_width = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_TX_POSITION_TIXELS:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get object tx position\n");
+				return retval;
+			}
+			object_data[obj].tx_pos = data;
+			offset += bits;
+			break;
+		case TOUCH_OBJECT_N_RX_POSITION_TIXELS:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get object rx position\n");
+				return retval;
+			}
+			object_data[obj].rx_pos = data;
+			offset += bits;
+			break;
+		case TOUCH_0D_BUTTONS_STATE:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get 0D buttons state\n");
+				return retval;
+			}
+			touch_data->buttons_state = data;
+			offset += bits;
+			break;
+		case TOUCH_GESTURE_DOUBLE_TAP:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get gesture double tap\n");
+				return retval;
+			}
+			touch_data->gesture_double_tap = data;
+			offset += bits;
+			break;
+		case TOUCH_FRAME_RATE:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get frame rate\n");
+				return retval;
+			}
+			touch_data->frame_rate = data;
+			offset += bits;
+			break;
+		case TOUCH_POWER_IM:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get power IM\n");
+				return retval;
+			}
+			touch_data->power_im = data;
+			offset += bits;
+			break;
+		case TOUCH_CID_IM:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get CID IM\n");
+				return retval;
+			}
+			touch_data->cid_im = data;
+			offset += bits;
+			break;
+		case TOUCH_RAIL_IM:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get rail IM\n");
+				return retval;
+			}
+			touch_data->rail_im = data;
+			offset += bits;
+			break;
+		case TOUCH_CID_VARIANCE_IM:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get CID variance IM\n");
+				return retval;
+			}
+			touch_data->cid_variance_im = data;
+			offset += bits;
+			break;
+		case TOUCH_NSM_FREQUENCY:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get NSM frequency\n");
+				return retval;
+			}
+			touch_data->nsm_frequency = data;
+			offset += bits;
+			break;
+		case TOUCH_NSM_STATE:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Failed to get NSM state\n");
+				return retval;
+			}
+			touch_data->nsm_state = data;
+			offset += bits;
+			break;
+		case TOUCH_NUM_OF_ACTIVE_OBJECTS:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get number of objects\n");
+				return retval;
+			}
+			active_objects = data;
+			num_of_active_objects = true;
+			touch_data->num_of_active_objects = data;
+			offset += bits;
+			if (touch_data->num_of_active_objects == 0)
+				idx = end_of_foreach;
+			break;
+		case TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME:
+			bits = config_data[idx++];
+			retval = touch_get_report_data(offset, bits, &data);
+			if (retval < 0) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to get number of CPU cycles\n");
+				return retval;
+			}
+			touch_data->num_of_cpu_cycles = data;
+			offset += bits;
+			break;
+		case TOUCH_TUNING_GAUSSIAN_WIDTHS:
+			bits = config_data[idx++];
+			offset += bits;
+			break;
+		case TOUCH_TUNING_SMALL_OBJECT_PARAMS:
+			bits = config_data[idx++];
+			offset += bits;
+			break;
+		case TOUCH_TUNING_0D_BUTTONS_VARIANCE:
+			bits = config_data[idx++];
+			offset += bits;
+			break;
+		}
+	}
+
+exit:
+	return 0;
+}
+
+/**
+ * touch_report() - Report touch events
+ *
+ * Retrieve data from the touch report generated by the device and report touch
+ * events to the input subsystem.
+ */
+static void touch_report(void)
+{
+	int retval;
+	unsigned int idx;
+	unsigned int x;
+	unsigned int y;
+	unsigned int temp;
+	unsigned int status;
+	unsigned int touch_count;
+	struct touch_data *touch_data;
+	struct object_data *object_data;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	if (!touch_hcd->report_touch)
+		return;
+
+	if (touch_hcd->input_dev == NULL)
+		return;
+
+	mutex_lock(&touch_hcd->report_mutex);
+
+	retval = touch_parse_report();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to parse touch report\n");
+		goto exit;
+	}
+
+	touch_data = &touch_hcd->touch_data;
+	object_data = touch_hcd->touch_data.object_data;
+
+#ifdef WAKEUP_GESTURE
+	if (touch_data->gesture_double_tap && tcm_hcd->in_suspend) {
+		input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 1);
+		input_sync(touch_hcd->input_dev);
+		input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 0);
+		input_sync(touch_hcd->input_dev);
+	}
+#endif
+
+	if (tcm_hcd->in_suspend)
+		goto exit;
+
+	touch_count = 0;
+
+	for (idx = 0; idx < touch_hcd->max_objects; idx++) {
+		if (touch_hcd->prev_status[idx] == LIFT &&
+				object_data[idx].status == LIFT)
+			status = NOP;
+		else
+			status = object_data[idx].status;
+
+		switch (status) {
+		case LIFT:
+#ifdef TYPE_B_PROTOCOL
+			input_mt_slot(touch_hcd->input_dev, idx);
+			input_mt_report_slot_state(touch_hcd->input_dev,
+					MT_TOOL_FINGER, 0);
+#endif
+			break;
+		case FINGER:
+		case GLOVED_FINGER:
+			x = object_data[idx].x_pos;
+			y = object_data[idx].y_pos;
+			if (bdata->swap_axes) {
+				temp = x;
+				x = y;
+				y = temp;
+			}
+			if (bdata->x_flip)
+				x = touch_hcd->input_params.max_x - x;
+			if (bdata->y_flip)
+				y = touch_hcd->input_params.max_y - y;
+#ifdef TYPE_B_PROTOCOL
+			input_mt_slot(touch_hcd->input_dev, idx);
+			input_mt_report_slot_state(touch_hcd->input_dev,
+					MT_TOOL_FINGER, 1);
+#endif
+			input_report_key(touch_hcd->input_dev,
+					BTN_TOUCH, 1);
+			input_report_key(touch_hcd->input_dev,
+					BTN_TOOL_FINGER, 1);
+			input_report_abs(touch_hcd->input_dev,
+					ABS_MT_POSITION_X, x);
+			input_report_abs(touch_hcd->input_dev,
+					ABS_MT_POSITION_Y, y);
+#ifndef TYPE_B_PROTOCOL
+			input_mt_sync(touch_hcd->input_dev);
+#endif
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Finger %d: x = %d, y = %d\n",
+					idx, x, y);
+			touch_count++;
+			break;
+		default:
+			break;
+		}
+
+		touch_hcd->prev_status[idx] = object_data[idx].status;
+	}
+
+	if (touch_count == 0) {
+		input_report_key(touch_hcd->input_dev,
+				BTN_TOUCH, 0);
+		input_report_key(touch_hcd->input_dev,
+				BTN_TOOL_FINGER, 0);
+#ifndef TYPE_B_PROTOCOL
+		input_mt_sync(touch_hcd->input_dev);
+#endif
+	}
+
+	input_sync(touch_hcd->input_dev);
+
+exit:
+	mutex_unlock(&touch_hcd->report_mutex);
+}
+
+/**
+ * touch_set_input_params() - Set input parameters
+ *
+ * Set the input parameters of the input device based on the information
+ * retrieved from the application information packet. In addition, set up an
+ * array for tracking the status of touch objects.
+ */
+static int touch_set_input_params(void)
+{
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+
+	input_set_abs_params(touch_hcd->input_dev,
+			ABS_MT_POSITION_X, 0, touch_hcd->max_x, 0, 0);
+	input_set_abs_params(touch_hcd->input_dev,
+			ABS_MT_POSITION_Y, 0, touch_hcd->max_y, 0, 0);
+
+	input_mt_init_slots(touch_hcd->input_dev, touch_hcd->max_objects,
+			INPUT_MT_DIRECT);
+
+	touch_hcd->input_params.max_x = touch_hcd->max_x;
+	touch_hcd->input_params.max_y = touch_hcd->max_y;
+	touch_hcd->input_params.max_objects = touch_hcd->max_objects;
+
+	if (touch_hcd->max_objects == 0)
+		return 0;
+
+	kfree(touch_hcd->prev_status);
+	touch_hcd->prev_status = kzalloc(touch_hcd->max_objects, GFP_KERNEL);
+	if (!touch_hcd->prev_status) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for prev_status\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * touch_get_input_params() - Get input parameters
+ *
+ * Retrieve the input parameters to register with the input subsystem for
+ * the input device from the application information packet. In addition,
+ * the touch report configuration is retrieved and stored.
+ */
+static int touch_get_input_params(void)
+{
+	int retval;
+	unsigned int temp;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	app_info = &tcm_hcd->app_info;
+	touch_hcd->max_x = le2_to_uint(app_info->max_x);
+	touch_hcd->max_y = le2_to_uint(app_info->max_y);
+	touch_hcd->max_objects = le2_to_uint(app_info->max_objects);
+
+	if (bdata->swap_axes) {
+		temp = touch_hcd->max_x;
+		touch_hcd->max_x = touch_hcd->max_y;
+		touch_hcd->max_y = temp;
+	}
+
+	LOCK_BUFFER(tcm_hcd->config);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_GET_TOUCH_REPORT_CONFIG,
+			NULL,
+			0,
+			&tcm_hcd->config.buf,
+			&tcm_hcd->config.buf_size,
+			&tcm_hcd->config.data_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_GET_TOUCH_REPORT_CONFIG));
+		UNLOCK_BUFFER(tcm_hcd->config);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(tcm_hcd->config);
+
+	return 0;
+}
+
+/**
+ * touch_set_input_dev() - Set up input device
+ *
+ * Allocate an input device, configure the input device based on the particular
+ * input events to be reported, and register the input device with the input
+ * subsystem.
+ */
+static int touch_set_input_dev(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+
+	touch_hcd->input_dev = input_allocate_device();
+	if (touch_hcd->input_dev == NULL) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate input device\n");
+		return -ENODEV;
+	}
+
+	touch_hcd->input_dev->name = TOUCH_INPUT_NAME;
+	touch_hcd->input_dev->phys = TOUCH_INPUT_PHYS_PATH;
+	touch_hcd->input_dev->id.product = SYNAPTICS_TCM_ID_PRODUCT;
+	touch_hcd->input_dev->id.version = SYNAPTICS_TCM_ID_VERSION;
+	touch_hcd->input_dev->dev.parent = tcm_hcd->pdev->dev.parent;
+	input_set_drvdata(touch_hcd->input_dev, tcm_hcd);
+
+	set_bit(EV_SYN, touch_hcd->input_dev->evbit);
+	set_bit(EV_KEY, touch_hcd->input_dev->evbit);
+	set_bit(EV_ABS, touch_hcd->input_dev->evbit);
+	set_bit(BTN_TOUCH, touch_hcd->input_dev->keybit);
+	set_bit(BTN_TOOL_FINGER, touch_hcd->input_dev->keybit);
+#ifdef INPUT_PROP_DIRECT
+	set_bit(INPUT_PROP_DIRECT, touch_hcd->input_dev->propbit);
+#endif
+
+#ifdef WAKEUP_GESTURE
+	set_bit(KEY_WAKEUP, touch_hcd->input_dev->keybit);
+	input_set_capability(touch_hcd->input_dev, EV_KEY, KEY_WAKEUP);
+#endif
+
+	retval = touch_set_input_params();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set input parameters\n");
+		input_free_device(touch_hcd->input_dev);
+		touch_hcd->input_dev = NULL;
+		return retval;
+	}
+
+	retval = input_register_device(touch_hcd->input_dev);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to register input device\n");
+		input_free_device(touch_hcd->input_dev);
+		touch_hcd->input_dev = NULL;
+		return retval;
+	}
+
+	return 0;
+}
+
+/**
+ * touch_set_report_config() - Set touch report configuration
+ *
+ * Send the SET_TOUCH_REPORT_CONFIG command to configure the format and content
+ * of the touch report.
+ */
+static int touch_set_report_config(void)
+{
+	int retval;
+	unsigned int idx;
+	unsigned int length;
+	struct syna_tcm_app_info *app_info;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+
+	if (!bdata->extend_report)
+		return 0;
+
+	app_info = &tcm_hcd->app_info;
+	length = le2_to_uint(app_info->max_touch_report_config_size);
+
+	if (length < TOUCH_REPORT_CONFIG_SIZE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid maximum touch report config size\n");
+		return -EINVAL;
+	}
+
+	LOCK_BUFFER(touch_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&touch_hcd->out,
+			length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for touch_hcd->out.buf\n");
+		UNLOCK_BUFFER(touch_hcd->out);
+		return retval;
+	}
+
+	idx = 0;
+#ifdef WAKEUP_GESTURE
+	touch_hcd->out.buf[idx++] = TOUCH_GESTURE_DOUBLE_TAP;
+	touch_hcd->out.buf[idx++] = 8;
+#endif
+	touch_hcd->out.buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT;
+	touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_INDEX;
+	touch_hcd->out.buf[idx++] = 4;
+	touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION;
+	touch_hcd->out.buf[idx++] = 4;
+	touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_X_POSITION;
+	touch_hcd->out.buf[idx++] = 12;
+	touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_Y_POSITION;
+	touch_hcd->out.buf[idx++] = 12;
+	touch_hcd->out.buf[idx++] = TOUCH_FOREACH_END;
+	touch_hcd->out.buf[idx++] = TOUCH_END;
+
+	LOCK_BUFFER(touch_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_SET_TOUCH_REPORT_CONFIG,
+			touch_hcd->out.buf,
+			length,
+			&touch_hcd->resp.buf,
+			&touch_hcd->resp.buf_size,
+			&touch_hcd->resp.data_length,
+			NULL,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_SET_TOUCH_REPORT_CONFIG));
+		UNLOCK_BUFFER(touch_hcd->resp);
+		UNLOCK_BUFFER(touch_hcd->out);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(touch_hcd->resp);
+	UNLOCK_BUFFER(touch_hcd->out);
+
+	return 0;
+}
+
+/**
+ * touch_check_input_params() - Check input parameters
+ *
+ * Check if any of the input parameters registered with the input subsystem for
+ * the input device has changed.
+ */
+static int touch_check_input_params(void)
+{
+	unsigned int size;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+
+	if (touch_hcd->max_x == 0 && touch_hcd->max_y == 0)
+		return 0;
+
+	if (touch_hcd->input_params.max_objects != touch_hcd->max_objects) {
+		kfree(touch_hcd->touch_data.object_data);
+		size = sizeof(*touch_hcd->touch_data.object_data);
+		size *= touch_hcd->max_objects;
+		touch_hcd->touch_data.object_data = kzalloc(size, GFP_KERNEL);
+		if (!touch_hcd->touch_data.object_data) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for object_data\n");
+			return -ENOMEM;
+		}
+		return 1;
+	}
+
+	if (touch_hcd->input_params.max_x != touch_hcd->max_x)
+		return 1;
+
+	if (touch_hcd->input_params.max_y != touch_hcd->max_y)
+		return 1;
+
+	return 0;
+}
+
+/**
+ * touch_set_input_reporting() - Configure touch report and set up new input
+ * device if necessary
+ *
+ * After a device reset event, configure the touch report and set up a new input
+ * device if any of the input parameters has changed after the device reset.
+ */
+static int touch_set_input_reporting(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd;
+
+	if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
+			tcm_hcd->app_status != APP_STATUS_OK) {
+		LOGN(tcm_hcd->pdev->dev.parent,
+				"Application firmware not running\n");
+		return 0;
+	}
+
+	touch_hcd->report_touch = false;
+
+	touch_free_objects();
+
+	mutex_lock(&touch_hcd->report_mutex);
+
+	retval = touch_set_report_config();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set report config\n");
+		goto exit;
+	}
+
+	retval = touch_get_input_params();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get input parameters\n");
+		goto exit;
+	}
+
+	retval = touch_check_input_params();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to check input parameters\n");
+		goto exit;
+	} else if (retval == 0) {
+		LOGD(tcm_hcd->pdev->dev.parent,
+				"Input parameters unchanged\n");
+		goto exit;
+	}
+
+	if (touch_hcd->input_dev != NULL) {
+		input_unregister_device(touch_hcd->input_dev);
+		touch_hcd->input_dev = NULL;
+	}
+
+	retval = touch_set_input_dev();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up input device\n");
+		goto exit;
+	}
+
+exit:
+	mutex_unlock(&touch_hcd->report_mutex);
+
+	touch_hcd->report_touch = retval < 0 ? false : true;
+
+	return retval;
+}
+
+static int touch_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	touch_hcd = kzalloc(sizeof(*touch_hcd), GFP_KERNEL);
+	if (!touch_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to allocate memory for touch_hcd\n");
+		return -ENOMEM;
+	}
+
+	touch_hcd->tcm_hcd = tcm_hcd;
+
+	mutex_init(&touch_hcd->report_mutex);
+
+	INIT_BUFFER(touch_hcd->out, false);
+	INIT_BUFFER(touch_hcd->resp, false);
+
+	retval = touch_set_input_reporting();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to set up input reporting\n");
+		goto err_set_input_reporting;
+	}
+
+	tcm_hcd->report_touch = touch_report;
+
+	return 0;
+
+err_set_input_reporting:
+	kfree(touch_hcd->touch_data.object_data);
+	kfree(touch_hcd->prev_status);
+
+	RELEASE_BUFFER(touch_hcd->resp);
+	RELEASE_BUFFER(touch_hcd->out);
+
+	kfree(touch_hcd);
+	touch_hcd = NULL;
+
+	return retval;
+}
+
+static int touch_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!touch_hcd)
+		goto exit;
+
+	tcm_hcd->report_touch = NULL;
+
+	if (touch_hcd->input_dev)
+		input_unregister_device(touch_hcd->input_dev);
+
+	kfree(touch_hcd->touch_data.object_data);
+	kfree(touch_hcd->prev_status);
+
+	RELEASE_BUFFER(touch_hcd->resp);
+	RELEASE_BUFFER(touch_hcd->out);
+
+	kfree(touch_hcd);
+	touch_hcd = NULL;
+
+exit:
+	complete(&touch_remove_complete);
+
+	return 0;
+}
+
+static int touch_syncbox(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!touch_hcd)
+		return 0;
+
+	switch (tcm_hcd->report.id) {
+	case REPORT_IDENTIFY:
+		touch_free_objects();
+		break;
+	case REPORT_TOUCH:
+		if (!touch_hcd->suspend_touch)
+			touch_report();
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int touch_asyncbox(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!touch_hcd)
+		return 0;
+
+	switch (tcm_hcd->async_report_id) {
+	case REPORT_IDENTIFY:
+		if (tcm_hcd->id_info.mode != MODE_APPLICATION)
+			break;
+		retval = tcm_hcd->identify(tcm_hcd, false);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to do identification\n");
+			return retval;
+		}
+		retval = touch_set_input_reporting();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to set up input reporting\n");
+			return retval;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int touch_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!touch_hcd) {
+		retval = touch_init(tcm_hcd);
+		return retval;
+	}
+
+	if (tcm_hcd->id_info.mode == MODE_APPLICATION) {
+		retval = touch_set_input_reporting();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to set up input reporting\n");
+			return retval;
+		}
+	}
+
+	return 0;
+}
+
+static int touch_early_suspend(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!touch_hcd)
+		return 0;
+
+#ifdef WAKEUP_GESTURE
+	touch_hcd->suspend_touch = false;
+#else
+	touch_hcd->suspend_touch = true;
+#endif
+
+	touch_free_objects();
+
+	return 0;
+}
+
+static int touch_suspend(struct syna_tcm_hcd *tcm_hcd)
+{
+#ifdef WAKEUP_GESTURE
+	int retval;
+#endif
+
+	if (!touch_hcd)
+		return 0;
+
+	touch_hcd->suspend_touch = true;
+
+	touch_free_objects();
+
+#ifdef WAKEUP_GESTURE
+	if (!touch_hcd->irq_wake) {
+		enable_irq_wake(tcm_hcd->irq);
+		touch_hcd->irq_wake = true;
+	}
+
+	touch_hcd->suspend_touch = false;
+
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd,
+			DC_IN_WAKEUP_GESTURE_MODE,
+			1);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to enable wakeup gesture mode\n");
+		return retval;
+	}
+#endif
+
+	return 0;
+}
+
+static int touch_resume(struct syna_tcm_hcd *tcm_hcd)
+{
+#ifdef WAKEUP_GESTURE
+	int retval;
+#endif
+
+	if (!touch_hcd)
+		return 0;
+
+	touch_hcd->suspend_touch = false;
+
+#ifdef WAKEUP_GESTURE
+	if (touch_hcd->irq_wake) {
+		disable_irq_wake(tcm_hcd->irq);
+		touch_hcd->irq_wake = false;
+	}
+
+	retval = tcm_hcd->set_dynamic_config(tcm_hcd,
+			DC_IN_WAKEUP_GESTURE_MODE,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to disable wakeup gesture mode\n");
+		return retval;
+	}
+#endif
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb touch_module = {
+	.type = TCM_TOUCH,
+	.init = touch_init,
+	.remove = touch_remove,
+	.syncbox = touch_syncbox,
+	.asyncbox = touch_asyncbox,
+	.reset = touch_reset,
+	.suspend = touch_suspend,
+	.resume = touch_resume,
+	.early_suspend = touch_early_suspend,
+};
+
+ int touch_module_init(void)
+{
+	return syna_tcm_add_module(&touch_module, true);
+}
+EXPORT_SYMBOL(touch_module_init);
+ void touch_module_exit(void)
+{
+	syna_tcm_add_module(&touch_module, false);
+
+	wait_for_completion(&touch_remove_complete);
+}
+EXPORT_SYMBOL(touch_module_exit);
+

+ 1012 - 0
synaptics_tcm/synaptics_tcm_zeroflash.c

@@ -0,0 +1,1012 @@
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
+ *
+ * Copyright (C) 2017-2019 Scott Lin <[email protected]>
+ *
+ * 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/crc32.h>
+#include <linux/firmware.h>
+#include "synaptics_tcm_core.h"
+
+#define FW_IMAGE_NAME "synaptics/hdl_firmware.img"
+
+#define BOOT_CONFIG_ID "BOOT_CONFIG"
+
+#define F35_APP_CODE_ID "F35_APP_CODE"
+
+#define APP_CONFIG_ID "APP_CONFIG"
+
+#define DISP_CONFIG_ID "DISPLAY"
+
+#define IMAGE_FILE_MAGIC_VALUE 0x4818472b
+
+#define FLASH_AREA_MAGIC_VALUE 0x7c05e516
+
+#define PDT_START_ADDR 0x00e9
+
+#define PDT_END_ADDR 0x00ee
+
+#define UBL_FN_NUMBER 0x35
+
+#define F35_CTRL3_OFFSET 18
+
+#define F35_CTRL7_OFFSET 22
+
+#define F35_WRITE_FW_TO_PMEM_COMMAND 4
+
+#define RESET_TO_HDL_DELAY_MS 12
+
+#define DOWNLOAD_RETRY_COUNT 10
+
+enum f35_error_code {
+	SUCCESS = 0,
+	UNKNOWN_FLASH_PRESENT,
+	MAGIC_NUMBER_NOT_PRESENT,
+	INVALID_BLOCK_NUMBER,
+	BLOCK_NOT_ERASED,
+	NO_FLASH_PRESENT,
+	CHECKSUM_FAILURE,
+	WRITE_FAILURE,
+	INVALID_COMMAND,
+	IN_DEBUG_MODE,
+	INVALID_HEADER,
+	REQUESTING_FIRMWARE,
+	INVALID_CONFIGURATION,
+	DISABLE_BLOCK_PROTECT_FAILURE,
+};
+
+enum config_download {
+	HDL_INVALID = 0,
+	HDL_TOUCH_CONFIG,
+	HDL_DISPLAY_CONFIG,
+	HDL_DISPLAY_CONFIG_TO_RAM,
+};
+
+struct area_descriptor {
+	unsigned char magic_value[4];
+	unsigned char id_string[16];
+	unsigned char flags[4];
+	unsigned char flash_addr_words[4];
+	unsigned char length[4];
+	unsigned char checksum[4];
+};
+
+struct block_data {
+	const unsigned char *data;
+	unsigned int size;
+	unsigned int flash_addr;
+};
+
+struct image_info {
+	unsigned int packrat_number;
+	struct block_data boot_config;
+	struct block_data app_firmware;
+	struct block_data app_config;
+	struct block_data disp_config;
+};
+
+struct image_header {
+	unsigned char magic_value[4];
+	unsigned char num_of_areas[4];
+};
+
+struct rmi_f35_query {
+	unsigned char version:4;
+	unsigned char has_debug_mode:1;
+	unsigned char has_data5:1;
+	unsigned char has_query1:1;
+	unsigned char has_query2:1;
+	unsigned char chunk_size;
+	unsigned char has_ctrl7:1;
+	unsigned char has_host_download:1;
+	unsigned char has_spi_master:1;
+	unsigned char advanced_recovery_mode:1;
+	unsigned char reserved:4;
+} __packed;
+
+struct rmi_f35_data {
+	unsigned char error_code:5;
+	unsigned char recovery_mode_forced:1;
+	unsigned char nvm_programmed:1;
+	unsigned char in_recovery:1;
+} __packed;
+
+struct rmi_pdt_entry {
+	unsigned char query_base_addr;
+	unsigned char command_base_addr;
+	unsigned char control_base_addr;
+	unsigned char data_base_addr;
+	unsigned char intr_src_count:3;
+	unsigned char reserved_1:2;
+	unsigned char fn_version:2;
+	unsigned char reserved_2:1;
+	unsigned char fn_number;
+} __packed;
+
+struct rmi_addr {
+	unsigned short query_base;
+	unsigned short command_base;
+	unsigned short control_base;
+	unsigned short data_base;
+};
+
+struct firmware_status {
+	unsigned short invalid_static_config:1;
+	unsigned short need_disp_config:1;
+	unsigned short need_app_config:1;
+	unsigned short hdl_version:4;
+	unsigned short reserved:9;
+} __packed;
+
+struct zeroflash_hcd {
+	bool has_hdl;
+	bool f35_ready;
+	const unsigned char *image;
+	unsigned char *buf;
+	const struct firmware *fw_entry;
+	struct work_struct config_work;
+	struct work_struct firmware_work;
+	struct workqueue_struct *workqueue;
+	struct rmi_addr f35_addr;
+	struct image_info image_info;
+	struct firmware_status fw_status;
+	struct syna_tcm_buffer out;
+	struct syna_tcm_buffer resp;
+	struct syna_tcm_hcd *tcm_hcd;
+};
+
+DECLARE_COMPLETION(zeroflash_remove_complete);
+
+static struct zeroflash_hcd *zeroflash_hcd;
+
+static int zeroflash_check_uboot(void)
+{
+	int retval;
+	unsigned char fn_number;
+	struct rmi_f35_query query;
+	struct rmi_pdt_entry p_entry;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+
+	retval = syna_tcm_rmi_read(tcm_hcd,
+			PDT_END_ADDR,
+			&fn_number,
+			sizeof(fn_number));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read RMI function number\n");
+		return retval;
+	}
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Found F$%02x\n",
+			fn_number);
+
+	if (fn_number != UBL_FN_NUMBER) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to find F$35\n");
+		return -ENODEV;
+	}
+
+	if (zeroflash_hcd->f35_ready)
+		return 0;
+
+	retval = syna_tcm_rmi_read(tcm_hcd,
+			PDT_START_ADDR,
+			(unsigned char *)&p_entry,
+			sizeof(p_entry));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read PDT entry\n");
+		return retval;
+	}
+
+	zeroflash_hcd->f35_addr.query_base = p_entry.query_base_addr;
+	zeroflash_hcd->f35_addr.command_base = p_entry.command_base_addr;
+	zeroflash_hcd->f35_addr.control_base = p_entry.control_base_addr;
+	zeroflash_hcd->f35_addr.data_base = p_entry.data_base_addr;
+
+	retval = syna_tcm_rmi_read(tcm_hcd,
+			zeroflash_hcd->f35_addr.query_base,
+			(unsigned char *)&query,
+			sizeof(query));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read F$35 query\n");
+		return retval;
+	}
+
+	zeroflash_hcd->f35_ready = true;
+
+	if (query.has_query2 && query.has_ctrl7 && query.has_host_download) {
+		zeroflash_hcd->has_hdl = true;
+	} else {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Host download not supported\n");
+		zeroflash_hcd->has_hdl = false;
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int zeroflash_parse_fw_image(void)
+{
+	unsigned int idx;
+	unsigned int addr;
+	unsigned int offset;
+	unsigned int length;
+	unsigned int checksum;
+	unsigned int flash_addr;
+	unsigned int magic_value;
+	unsigned int num_of_areas;
+	struct image_header *header;
+	struct image_info *image_info;
+	struct area_descriptor *descriptor;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+	const unsigned char *image;
+	const unsigned char *content;
+
+	image = zeroflash_hcd->image;
+	image_info = &zeroflash_hcd->image_info;
+	header = (struct image_header *)image;
+
+	magic_value = le4_to_uint(header->magic_value);
+	if (magic_value != IMAGE_FILE_MAGIC_VALUE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid image file magic value\n");
+		return -EINVAL;
+	}
+
+	memset(image_info, 0x00, sizeof(*image_info));
+
+	offset = sizeof(*header);
+	num_of_areas = le4_to_uint(header->num_of_areas);
+
+	for (idx = 0; idx < num_of_areas; idx++) {
+		addr = le4_to_uint(image + offset);
+		descriptor = (struct area_descriptor *)(image + addr);
+		offset += 4;
+
+		magic_value = le4_to_uint(descriptor->magic_value);
+		if (magic_value != FLASH_AREA_MAGIC_VALUE)
+			continue;
+
+		length = le4_to_uint(descriptor->length);
+		content = (unsigned char *)descriptor + sizeof(*descriptor);
+		flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2;
+		checksum = le4_to_uint(descriptor->checksum);
+
+		if (!memcmp((char *)descriptor->id_string,
+				BOOT_CONFIG_ID,
+				strlen(BOOT_CONFIG_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+						"Boot config checksum error\n");
+				return -EINVAL;
+			}
+			image_info->boot_config.size = length;
+			image_info->boot_config.data = content;
+			image_info->boot_config.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Boot config size = %d\n",
+					length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Boot config flash address = 0x%08x\n",
+					flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				F35_APP_CODE_ID,
+				strlen(F35_APP_CODE_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"APP firmware checksum error\n");
+				return -EINVAL;
+			}
+			image_info->app_firmware.size = length;
+			image_info->app_firmware.data = content;
+			image_info->app_firmware.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Application firmware size = %d\n",
+					length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application firmware flash address = 0x%08x\n",
+				flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				APP_CONFIG_ID,
+				strlen(APP_CONFIG_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Application config checksum error\n");
+				return -EINVAL;
+			}
+			image_info->app_config.size = length;
+			image_info->app_config.data = content;
+			image_info->app_config.flash_addr = flash_addr;
+			image_info->packrat_number = le4_to_uint(&content[14]);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application config size = %d\n",
+				length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Application config flash address = 0x%08x\n",
+				flash_addr);
+		} else if (!memcmp((char *)descriptor->id_string,
+				DISP_CONFIG_ID,
+				strlen(DISP_CONFIG_ID))) {
+			if (checksum != (crc32(~0, content, length) ^ ~0)) {
+				LOGE(tcm_hcd->pdev->dev.parent,
+					"Display config checksum error\n");
+				return -EINVAL;
+			}
+			image_info->disp_config.size = length;
+			image_info->disp_config.data = content;
+			image_info->disp_config.flash_addr = flash_addr;
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Display config size = %d\n",
+				length);
+			LOGD(tcm_hcd->pdev->dev.parent,
+				"Display config flash address = 0x%08x\n",
+				flash_addr);
+		}
+	}
+
+	return 0;
+}
+
+static int zeroflash_get_fw_image(void)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+
+	if (zeroflash_hcd->fw_entry != NULL)
+		return 0;
+
+	do {
+		retval = request_firmware(&zeroflash_hcd->fw_entry,
+				FW_IMAGE_NAME,
+				tcm_hcd->pdev->dev.parent);
+		if (retval < 0) {
+			LOGD(tcm_hcd->pdev->dev.parent,
+					"Failed to request %s\n",
+					FW_IMAGE_NAME);
+			msleep(100);
+		} else {
+			break;
+		}
+	} while (1);
+
+	LOGD(tcm_hcd->pdev->dev.parent,
+			"Firmware image size = %d\n",
+			(unsigned int)zeroflash_hcd->fw_entry->size);
+
+	zeroflash_hcd->image = zeroflash_hcd->fw_entry->data;
+
+	retval = zeroflash_parse_fw_image();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to parse firmware image\n");
+		release_firmware(zeroflash_hcd->fw_entry);
+		zeroflash_hcd->fw_entry = NULL;
+		zeroflash_hcd->image = NULL;
+		return retval;
+	}
+
+	return 0;
+}
+
+static void zeroflash_download_config(void)
+{
+	struct firmware_status *fw_status;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+
+	fw_status = &zeroflash_hcd->fw_status;
+
+	if (!fw_status->need_app_config && !fw_status->need_disp_config) {
+		if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) {
+			atomic_set(&tcm_hcd->helper.task,
+					HELP_SEND_RESET_NOTIFICATION);
+			queue_work(tcm_hcd->helper.workqueue,
+					&tcm_hcd->helper.work);
+		}
+		atomic_set(&tcm_hcd->host_downloading, 0);
+		return;
+	}
+
+	queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->config_work);
+}
+
+static void zeroflash_download_firmware(void)
+{
+	queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->firmware_work);
+}
+
+static int zeroflash_download_disp_config(void)
+{
+	int retval;
+	unsigned char response_code;
+	struct image_info *image_info;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+	static unsigned int retry_count;
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Downloading display config\n");
+
+	image_info = &zeroflash_hcd->image_info;
+
+	if (image_info->disp_config.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No display config in image file\n");
+		return -EINVAL;
+	}
+
+	LOCK_BUFFER(zeroflash_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&zeroflash_hcd->out,
+			image_info->disp_config.size + 2);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for out.buf\n");
+		goto unlock_out;
+	}
+
+	switch (zeroflash_hcd->fw_status.hdl_version) {
+	case 0:
+		zeroflash_hcd->out.buf[0] = 1;
+		break;
+	case 1:
+		zeroflash_hcd->out.buf[0] = 2;
+		break;
+	default:
+		retval = -EINVAL;
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid HDL version (%d)\n",
+				zeroflash_hcd->fw_status.hdl_version);
+		goto unlock_out;
+	}
+
+	zeroflash_hcd->out.buf[1] = HDL_DISPLAY_CONFIG;
+
+	retval = secure_memcpy(&zeroflash_hcd->out.buf[2],
+			zeroflash_hcd->out.buf_size - 2,
+			image_info->disp_config.data,
+			image_info->disp_config.size,
+			image_info->disp_config.size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy display config data\n");
+		goto unlock_out;
+	}
+
+	zeroflash_hcd->out.data_length = image_info->disp_config.size + 2;
+
+	LOCK_BUFFER(zeroflash_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_DOWNLOAD_CONFIG,
+			zeroflash_hcd->out.buf,
+			zeroflash_hcd->out.data_length,
+			&zeroflash_hcd->resp.buf,
+			&zeroflash_hcd->resp.buf_size,
+			&zeroflash_hcd->resp.data_length,
+			&response_code,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_DOWNLOAD_CONFIG));
+		if (response_code != STATUS_ERROR)
+			goto unlock_resp;
+		retry_count++;
+		if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT)
+			goto unlock_resp;
+	} else {
+		retry_count = 0;
+	}
+
+	retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status,
+			sizeof(zeroflash_hcd->fw_status),
+			zeroflash_hcd->resp.buf,
+			zeroflash_hcd->resp.buf_size,
+			sizeof(zeroflash_hcd->fw_status));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy firmware status\n");
+		goto unlock_resp;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Display config downloaded\n");
+
+	retval = 0;
+
+unlock_resp:
+	UNLOCK_BUFFER(zeroflash_hcd->resp);
+
+unlock_out:
+	UNLOCK_BUFFER(zeroflash_hcd->out);
+
+	return retval;
+}
+
+static int zeroflash_download_app_config(void)
+{
+	int retval;
+	unsigned char padding;
+	unsigned char response_code;
+	struct image_info *image_info;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+	static unsigned int retry_count;
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Downloading application config\n");
+
+	image_info = &zeroflash_hcd->image_info;
+
+	if (image_info->app_config.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No application config in image file\n");
+		return -EINVAL;
+	}
+
+	padding = image_info->app_config.size % 8;
+	if (padding)
+		padding = 8 - padding;
+
+	LOCK_BUFFER(zeroflash_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&zeroflash_hcd->out,
+			image_info->app_config.size + 2 + padding);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for out.buf\n");
+		goto unlock_out;
+	}
+
+	switch (zeroflash_hcd->fw_status.hdl_version) {
+	case 0:
+		zeroflash_hcd->out.buf[0] = 1;
+		break;
+	case 1:
+		zeroflash_hcd->out.buf[0] = 2;
+		break;
+	default:
+		retval = -EINVAL;
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Invalid HDL version (%d)\n",
+				zeroflash_hcd->fw_status.hdl_version);
+		goto unlock_out;
+	}
+
+	zeroflash_hcd->out.buf[1] = HDL_TOUCH_CONFIG;
+
+	retval = secure_memcpy(&zeroflash_hcd->out.buf[2],
+			zeroflash_hcd->out.buf_size - 2,
+			image_info->app_config.data,
+			image_info->app_config.size,
+			image_info->app_config.size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy application config data\n");
+		goto unlock_out;
+	}
+
+	zeroflash_hcd->out.data_length = image_info->app_config.size + 2;
+	zeroflash_hcd->out.data_length += padding;
+
+	LOCK_BUFFER(zeroflash_hcd->resp);
+
+	retval = tcm_hcd->write_message(tcm_hcd,
+			CMD_DOWNLOAD_CONFIG,
+			zeroflash_hcd->out.buf,
+			zeroflash_hcd->out.data_length,
+			&zeroflash_hcd->resp.buf,
+			&zeroflash_hcd->resp.buf_size,
+			&zeroflash_hcd->resp.data_length,
+			&response_code,
+			0);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write command %s\n",
+				STR(CMD_DOWNLOAD_CONFIG));
+		if (response_code != STATUS_ERROR)
+			goto unlock_resp;
+		retry_count++;
+		if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT)
+			goto unlock_resp;
+	} else {
+		retry_count = 0;
+	}
+
+	retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status,
+			sizeof(zeroflash_hcd->fw_status),
+			zeroflash_hcd->resp.buf,
+			zeroflash_hcd->resp.buf_size,
+			sizeof(zeroflash_hcd->fw_status));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy firmware status\n");
+		goto unlock_resp;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Application config downloaded\n");
+
+	retval = 0;
+
+unlock_resp:
+	UNLOCK_BUFFER(zeroflash_hcd->resp);
+
+unlock_out:
+	UNLOCK_BUFFER(zeroflash_hcd->out);
+
+	return retval;
+}
+
+static void zeroflash_download_config_work(struct work_struct *work)
+{
+	int retval;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+
+	retval = zeroflash_get_fw_image();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get firmware image\n");
+		return;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Start of config download\n");
+
+	if (zeroflash_hcd->fw_status.need_app_config) {
+		retval = zeroflash_download_app_config();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to download application config\n");
+			return;
+		}
+		goto exit;
+	}
+
+	if (zeroflash_hcd->fw_status.need_disp_config) {
+		retval = zeroflash_download_disp_config();
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to download display config\n");
+			return;
+		}
+		goto exit;
+	}
+
+exit:
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"End of config download\n");
+
+	zeroflash_download_config();
+}
+
+static int zeroflash_download_app_fw(void)
+{
+	int retval;
+	unsigned char command;
+	struct image_info *image_info;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+#if RESET_TO_HDL_DELAY_MS
+	const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
+#endif
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Downloading application firmware\n");
+
+	image_info = &zeroflash_hcd->image_info;
+
+	if (image_info->app_firmware.size == 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"No application firmware in image file\n");
+		return -EINVAL;
+	}
+
+	LOCK_BUFFER(zeroflash_hcd->out);
+
+	retval = syna_tcm_alloc_mem(tcm_hcd,
+			&zeroflash_hcd->out,
+			image_info->app_firmware.size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for out.buf\n");
+		UNLOCK_BUFFER(zeroflash_hcd->out);
+		return retval;
+	}
+
+	retval = secure_memcpy(zeroflash_hcd->out.buf,
+			zeroflash_hcd->out.buf_size,
+			image_info->app_firmware.data,
+			image_info->app_firmware.size,
+			image_info->app_firmware.size);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to copy application firmware data\n");
+		UNLOCK_BUFFER(zeroflash_hcd->out);
+		return retval;
+	}
+
+	zeroflash_hcd->out.data_length = image_info->app_firmware.size;
+
+	command = F35_WRITE_FW_TO_PMEM_COMMAND;
+
+#if RESET_TO_HDL_DELAY_MS
+	gpio_set_value(bdata->reset_gpio, bdata->reset_on_state);
+	msleep(bdata->reset_active_ms);
+	gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state);
+	msleep(RESET_TO_HDL_DELAY_MS);
+#endif
+
+	retval = syna_tcm_rmi_write(tcm_hcd,
+			zeroflash_hcd->f35_addr.control_base + F35_CTRL3_OFFSET,
+			&command,
+			sizeof(command));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write F$35 command\n");
+		UNLOCK_BUFFER(zeroflash_hcd->out);
+		return retval;
+	}
+
+	retval = syna_tcm_rmi_write(tcm_hcd,
+			zeroflash_hcd->f35_addr.control_base + F35_CTRL7_OFFSET,
+			zeroflash_hcd->out.buf,
+			zeroflash_hcd->out.data_length);
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to write application firmware data\n");
+		UNLOCK_BUFFER(zeroflash_hcd->out);
+		return retval;
+	}
+
+	UNLOCK_BUFFER(zeroflash_hcd->out);
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Application firmware downloaded\n");
+
+	return 0;
+}
+
+static void zeroflash_download_firmware_work(struct work_struct *work)
+{
+	int retval;
+	struct rmi_f35_data data;
+	struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd;
+	static unsigned int retry_count;
+
+	retval = zeroflash_check_uboot();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Microbootloader support unavailable\n");
+		goto exit;
+	}
+
+	atomic_set(&tcm_hcd->host_downloading, 1);
+
+	retval = syna_tcm_rmi_read(tcm_hcd,
+			zeroflash_hcd->f35_addr.data_base,
+			(unsigned char *)&data,
+			sizeof(data));
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to read F$35 data\n");
+		goto exit;
+	}
+
+	if (data.error_code != REQUESTING_FIRMWARE) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Microbootloader error code = 0x%02x\n",
+				data.error_code);
+		if (data.error_code != CHECKSUM_FAILURE) {
+			retval = -EIO;
+			goto exit;
+		} else {
+			retry_count++;
+		}
+	} else {
+		retry_count = 0;
+	}
+
+	retval = zeroflash_get_fw_image();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to get firmware image\n");
+		goto exit;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"Start of firmware download\n");
+
+	retval = zeroflash_download_app_fw();
+	if (retval < 0) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+				"Failed to download application firmware\n");
+		goto exit;
+	}
+
+	LOGN(tcm_hcd->pdev->dev.parent,
+			"End of firmware download\n");
+
+exit:
+	if (retval < 0)
+		retry_count++;
+
+	if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) {
+		retval = tcm_hcd->enable_irq(tcm_hcd, false, true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to disable interrupt\n");
+		}
+	} else {
+		retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to enable interrupt\n");
+		}
+	}
+}
+
+static int zeroflash_init(struct syna_tcm_hcd *tcm_hcd)
+{
+	zeroflash_hcd = kzalloc(sizeof(*zeroflash_hcd), GFP_KERNEL);
+	if (!zeroflash_hcd) {
+		LOGE(tcm_hcd->pdev->dev.parent,
+			"Failed to allocate memory for zeroflash_hcd\n");
+		return -ENOMEM;
+	}
+
+	zeroflash_hcd->tcm_hcd = tcm_hcd;
+
+	INIT_BUFFER(zeroflash_hcd->out, false);
+	INIT_BUFFER(zeroflash_hcd->resp, false);
+
+	zeroflash_hcd->workqueue =
+			create_singlethread_workqueue("syna_tcm_zeroflash");
+	INIT_WORK(&zeroflash_hcd->config_work,
+			zeroflash_download_config_work);
+	INIT_WORK(&zeroflash_hcd->firmware_work,
+			zeroflash_download_firmware_work);
+
+	if (tcm_hcd->init_okay == false &&
+			tcm_hcd->hw_if->bus_io->type == BUS_SPI)
+		zeroflash_download_firmware();
+
+	return 0;
+}
+
+static int zeroflash_remove(struct syna_tcm_hcd *tcm_hcd)
+{
+	if (!zeroflash_hcd)
+		goto exit;
+
+	if (zeroflash_hcd->fw_entry)
+		release_firmware(zeroflash_hcd->fw_entry);
+
+	cancel_work_sync(&zeroflash_hcd->config_work);
+	cancel_work_sync(&zeroflash_hcd->firmware_work);
+	flush_workqueue(zeroflash_hcd->workqueue);
+	destroy_workqueue(zeroflash_hcd->workqueue);
+
+	RELEASE_BUFFER(zeroflash_hcd->resp);
+	RELEASE_BUFFER(zeroflash_hcd->out);
+
+	kfree(zeroflash_hcd);
+	zeroflash_hcd = NULL;
+
+exit:
+	complete(&zeroflash_remove_complete);
+
+	return 0;
+}
+
+static int zeroflash_syncbox(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+	unsigned char *fw_status;
+
+	if (!zeroflash_hcd)
+		return 0;
+
+	switch (tcm_hcd->report.id) {
+	case REPORT_STATUS:
+		fw_status = (unsigned char *)&zeroflash_hcd->fw_status;
+		retval = secure_memcpy(fw_status,
+				sizeof(zeroflash_hcd->fw_status),
+				tcm_hcd->report.buffer.buf,
+				tcm_hcd->report.buffer.buf_size,
+				sizeof(zeroflash_hcd->fw_status));
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to copy firmware status\n");
+			return retval;
+		}
+		zeroflash_download_config();
+		break;
+	case REPORT_HDL:
+		retval = tcm_hcd->enable_irq(tcm_hcd, false, true);
+		if (retval < 0) {
+			LOGE(tcm_hcd->pdev->dev.parent,
+					"Failed to disable interrupt\n");
+			return retval;
+		}
+		zeroflash_download_firmware();
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int zeroflash_reset(struct syna_tcm_hcd *tcm_hcd)
+{
+	int retval;
+
+	if (!zeroflash_hcd) {
+		retval = zeroflash_init(tcm_hcd);
+		return retval;
+	}
+
+	return 0;
+}
+
+static struct syna_tcm_module_cb zeroflash_module = {
+	.type = TCM_ZEROFLASH,
+	.init = zeroflash_init,
+	.remove = zeroflash_remove,
+	.syncbox = zeroflash_syncbox,
+	.asyncbox = NULL,
+	.reset = zeroflash_reset,
+	.suspend = NULL,
+	.resume = NULL,
+	.early_suspend = NULL,
+};
+
+static int __init zeroflash_module_init(void)
+{
+	return syna_tcm_add_module(&zeroflash_module, true);
+}
+
+static void __exit zeroflash_module_exit(void)
+{
+	syna_tcm_add_module(&zeroflash_module, false);
+
+	wait_for_completion(&zeroflash_remove_complete);
+}
+
+module_init(zeroflash_module_init);
+module_exit(zeroflash_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Zeroflash Module");
+MODULE_LICENSE("GPL v2");

+ 2 - 1
touch_driver_board.mk

@@ -10,7 +10,8 @@ ifeq ($(TOUCH_DLKM_ENABLE),  true)
 		ifeq ($(call is-board-platform-in-list,$(TARGET_BOARD_PLATFORM)),true)
 			BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \
 				$(KERNEL_MODULES_OUT)/goodix_ts.ko \
-				$(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko
+				$(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko \
+				$(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko
 		endif
 	endif
 endif

+ 2 - 1
touch_driver_product.mk

@@ -8,5 +8,6 @@ endif
 ifeq ($(TOUCH_DLKM_ENABLE),  true)
 	PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \
 		$(KERNEL_MODULES_OUT)/goodix_ts.ko \
-		$(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko
+		$(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko \
+		$(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko
 endif