summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordavidycchen <davidycchen@google.com>2021-08-17 14:00:30 +0800
committerdavidycchen <davidycchen@google.com>2021-09-08 15:12:37 +0800
commitb709f4d97bb33d74ff5a14f8ff8c617b5f605c67 (patch)
tree1a431604d32160798b1b9791e81e9d9e172d13dc
parentdcf50a13512c25d57d413b1ce99f1770450e3d5f (diff)
downloadsynaptics_touch-b709f4d97bb33d74ff5a14f8ff8c617b5f605c67.tar.gz
synaptics: add initial driver
Initial driver provided by Synaptics. Bug: 198228556 Signed-off-by: davidycchen <davidycchen@google.com> Change-Id: I471eda97a2b2e06a4dd9328e69c71d5f8e8fdc93
-rw-r--r--Kbuild14
-rw-r--r--Kconfig60
-rw-r--r--Makefile26
-rw-r--r--syna_tcm2.c1745
-rw-r--r--syna_tcm2.h416
-rw-r--r--syna_tcm2_platform.h306
-rw-r--r--syna_tcm2_platform_i2c.c898
-rw-r--r--syna_tcm2_platform_spi.c1058
-rw-r--r--syna_tcm2_runtime.h753
-rw-r--r--syna_tcm2_sysfs.c2207
-rw-r--r--syna_tcm2_testing.c660
-rw-r--r--syna_tcm2_testing.h66
-rw-r--r--syna_tcm2_testing_limits.h234
-rw-r--r--tcm/synaptics_touchcom_core_dev.h1096
-rw-r--r--tcm/synaptics_touchcom_core_v1.c1078
-rw-r--r--tcm/synaptics_touchcom_core_v2.c1445
-rw-r--r--tcm/synaptics_touchcom_func_base.c1678
-rw-r--r--tcm/synaptics_touchcom_func_base.h418
-rw-r--r--tcm/synaptics_touchcom_func_base_flash.h569
-rw-r--r--tcm/synaptics_touchcom_func_reflash.c2074
-rw-r--r--tcm/synaptics_touchcom_func_reflash.h145
-rw-r--r--tcm/synaptics_touchcom_func_romboot.c1586
-rw-r--r--tcm/synaptics_touchcom_func_romboot.h155
-rw-r--r--tcm/synaptics_touchcom_func_touch.c877
-rw-r--r--tcm/synaptics_touchcom_func_touch.h235
25 files changed, 19799 insertions, 0 deletions
diff --git a/Kbuild b/Kbuild
new file mode 100644
index 0000000..93b07c0
--- /dev/null
+++ b/Kbuild
@@ -0,0 +1,14 @@
+TCM_CORE=tcm/
+
+obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM2) = syna_touch.o
+syna_touch-objs += \
+ syna_tcm2.o \
+ $(TCM_CORE)synaptics_touchcom_core_v1.o \
+ $(TCM_CORE)synaptics_touchcom_core_v2.o \
+ $(TCM_CORE)synaptics_touchcom_func_base.o \
+ $(TCM_CORE)synaptics_touchcom_func_touch.o \
+ $(TCM_CORE)synaptics_touchcom_func_reflash.o \
+ $(TCM_CORE)synaptics_touchcom_func_romboot.o \
+ syna_tcm2_platform_spi.o \
+ syna_tcm2_sysfs.o \
+ syna_tcm2_testing.o
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..9eb1bd1
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,60 @@
+#
+# Synaptics TCM touchscreen driver configuration
+#
+menuconfig TOUCHSCREEN_SYNA_TCM2
+ tristate "Synaptics TCM touchscreen"
+ default y
+ help
+ Say Y here if you have a Synaptics TCM touchscreen connected
+ to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here.
+
+if TOUCHSCREEN_SYNA_TCM2
+
+choice
+ default TOUCHSCREEN_SYNA_TCM2_I2C
+ prompt "Synaptics TCM bus module"
+config TOUCHSCREEN_SYNA_TCM2_I2C
+ bool "I2C"
+ depends on I2C
+config TOUCHSCREEN_SYNA_TCM2_SPI
+ bool "SPI"
+ depends on SPI_MASTER
+endchoice
+
+config TOUCHSCREEN_SYNA_TCM2_SYSFS
+ bool "Synaptics TCM device sysfs module"
+ depends on TOUCHSCREEN_SYNA_TCM2
+ help
+ Say Y here to enable support for the sysfs interface.
+
+ If unsure, say N.
+
+config TOUCHSCREEN_SYNA_TCM2_REFLASH
+ bool "Synaptics TCM reflash module"
+ depends on TOUCHSCREEN_SYNA_TCM2
+ help
+ Say Y here to enable support for the reflashing.
+
+ If unsure, say N.
+
+config TOUCHSCREEN_SYNA_TCM2_TESTING
+ bool "Synaptics TCM testing module"
+ depends on TOUCHSCREEN_SYNA_TCM2
+ help
+ Say Y here to enable support for the production test.
+
+ If unsure, say N.
+
+config TOUCHSCREEN_SYNA_TCM2_ROMBOOT
+ bool "Synaptics TCM rom-bootloader module"
+ depends on TOUCHSCREEN_SYNA_TCM2
+ help
+ Say Y here to enable support for the rom-boot access.
+
+ If unsure, say N.
+
+endif
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0ebd555
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_SYNA_TCM2=m
+# CONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING is not set
+# CONFIG_TOUCHSCREEN_SYNA_TCM2_ROMBOOT is not set
+EXTRA_CFLAGS += -DDYNAMIC_DEBUG_MODULE
+EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_TBN
+EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_HEATMAP
+EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_OFFLOAD
+EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_SYNA_TCM2_REFLASH
+EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS
+EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/display
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/common
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/common/include
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/synaptics
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/synaptics/tcm
+EXTRA_SYMBOLS += $(OUT_DIR)/../google-modules/touch/common/Module.symvers
+
+modules modules_install clean:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) \
+ $(KBUILD_OPTIONS) \
+ EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
+ KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)" \
+ $(@)
diff --git a/syna_tcm2.c b/syna_tcm2.c
new file mode 100644
index 0000000..4527a0a
--- /dev/null
+++ b/syna_tcm2.c
@@ -0,0 +1,1745 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2.c
+ *
+ * This file implements the Synaptics device driver running under Linux kernel
+ * input device subsystem, and also communicate with Synaptics touch controller
+ * through TouchComm command-response protocol.
+ */
+
+#include "syna_tcm2.h"
+#include "syna_tcm2_platform.h"
+#include "synaptics_touchcom_core_dev.h"
+#include "synaptics_touchcom_func_base.h"
+#include "synaptics_touchcom_func_touch.h"
+#ifdef STARTUP_REFLASH
+#include "synaptics_touchcom_func_reflash.h"
+#endif
+#ifdef MULTICHIP_DUT_REFLASH
+#include "synaptics_touchcom_func_romboot.h"
+#endif
+
+/**
+ * @section: USE_CUSTOM_TOUCH_REPORT_CONFIG
+ * Open if willing to set up the format of touch report.
+ * The custom_touch_format[] array can be used to describe the
+ * customized report format.
+ */
+#ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG
+static unsigned char custom_touch_format[] = {
+ /* entity code */ /* bits */
+#ifdef ENABLE_WAKEUP_GESTURE
+ TOUCH_REPORT_GESTURE_ID, 8,
+#endif
+ TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS, 8,
+ TOUCH_REPORT_FOREACH_ACTIVE_OBJECT,
+ TOUCH_REPORT_OBJECT_N_INDEX, 8,
+ TOUCH_REPORT_OBJECT_N_CLASSIFICATION, 8,
+ TOUCH_REPORT_OBJECT_N_X_POSITION, 16,
+ TOUCH_REPORT_OBJECT_N_Y_POSITION, 16,
+ TOUCH_REPORT_FOREACH_END,
+ TOUCH_REPORT_END
+};
+#endif
+
+/**
+ * @section: STARTUP_REFLASH_DELAY_TIME_MS
+ * The delayed time to start fw update during the startup time.
+ * This configuration depends on STARTUP_REFLASH.
+ */
+#ifdef STARTUP_REFLASH
+#define STARTUP_REFLASH_DELAY_TIME_MS (200)
+
+#define FW_IMAGE_NAME "synaptics/firmware.img"
+#endif
+
+/**
+ * @section: RESET_ON_RESUME_DELAY_MS
+ * The delayed time to issue a reset on resume state.
+ * This configuration depends on RESET_ON_RESUME.
+ */
+#ifdef RESET_ON_RESUME
+#define RESET_ON_RESUME_DELAY_MS (100)
+#endif
+
+
+/**
+ * @section: POWER_ALIVE_AT_SUSPEND
+ * indicate that the power is still alive even at
+ * system suspend.
+ * otherwise, there is no power supplied when system
+ * is going to suspend stage.
+ */
+#define POWER_ALIVE_AT_SUSPEND
+
+/**
+ * @section: global variables for an active drm panel
+ * in order to register display notifier
+ */
+#ifdef USE_DRM_PANEL_NOTIFIER
+ struct drm_panel *active_panel;
+#endif
+
+
+
+/**
+ * syna_dev_enable_lowpwr_gesture()
+ *
+ * Enable or disable the low power gesture mode.
+ * Furthermore, set up the wake-up irq.
+ *
+ * @param
+ * [ in] tcm: tcm driver handle
+ * [ in] en: '1' to enable low power gesture mode; '0' to disable
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_enable_lowpwr_gesture(struct syna_tcm *tcm, bool en)
+{
+ int retval = 0;
+ struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn;
+
+ if (!tcm->lpwg_enabled)
+ return 0;
+
+ if (attn->irq_id == 0)
+ return 0;
+
+ if (en) {
+ if (!tcm->irq_wake) {
+ enable_irq_wake(attn->irq_id);
+ tcm->irq_wake = true;
+ }
+
+ /* enable wakeup gesture mode
+ *
+ * the wakeup gesture control may result from by finger event;
+ * therefore, it's better to use ATTN driven mode here
+ */
+ retval = syna_tcm_set_dynamic_config(tcm->tcm_dev,
+ DC_ENABLE_WAKEUP_GESTURE_MODE,
+ 1,
+ RESP_IN_ATTN);
+ if (retval < 0) {
+ LOGE("Fail to enable wakeup gesture via DC command\n");
+ return retval;
+ }
+ } else {
+ if (tcm->irq_wake) {
+ disable_irq_wake(attn->irq_id);
+ tcm->irq_wake = false;
+ }
+
+ /* disable wakeup gesture mode
+ *
+ * the wakeup gesture control may result from by finger event;
+ * therefore, it's better to use ATTN driven mode here
+ */
+ retval = syna_tcm_set_dynamic_config(tcm->tcm_dev,
+ DC_ENABLE_WAKEUP_GESTURE_MODE,
+ 0,
+ RESP_IN_ATTN);
+ if (retval < 0) {
+ LOGE("Fail to disable wakeup gesture via DC command\n");
+ return retval;
+ }
+ }
+
+ return retval;
+}
+
+#ifdef ENABLE_CUSTOM_TOUCH_ENTITY
+/**
+ * @section: Callback function used for custom entity parsing in touch report
+ *
+ * Allow to parse the custom "newly" entity in the touch report.
+ * Please note that this function will be invoked in ISR
+ *
+ * @param
+ * [ in] code: the code of current touch entity
+ * [ in] config: the report configuration stored
+ * [in/out] config_offset: offset of current position in report config,
+ * and then return the updated position.
+ * [ in] report: touch report given
+ * [in/out] report_offset: offset of current position in touch report,
+ * the updated position should be returned.
+ * [ in] report_size: size of given touch report
+ * [ in] callback_data: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_dev_parse_custom_touch_report(const unsigned char code,
+ const unsigned char *config, unsigned int *config_offset,
+ const unsigned char *report, unsigned int *report_offset,
+ unsigned int report_size, void *callback_data)
+{
+ /**
+ * sample code to demonstrate how to parse the custom entity
+ * from the report, additional modifications must be beeded.
+ *
+ * struct syna_tcm *tcm = (struct syna_tcm *)callback_data;
+ * unsigned int data;
+ * unsigned int bits;
+ *
+ * switch (code) {
+ * case CUSTOM_ENTITY_CODE:
+ * bits = config[(*config_offset)++];
+ * syna_tcm_get_touch_data(report, report_size,
+ * *report_offset, bits, &data);
+ *
+ * *report_offset += bits;
+ * return bits;
+ * default:
+ * LOGW("Unknown touch config code (idx:%d 0x%02x)\n",
+ * *config_offset, code);
+ * return (-EINVAL);
+ * }
+ *
+ */
+
+ return (-EINVAL);
+}
+#endif
+
+/**
+ * syna_tcm_free_input_events()
+ *
+ * Clear all relevant touched events.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * none.
+ */
+static void syna_dev_free_input_events(struct syna_tcm *tcm)
+{
+ struct input_dev *input_dev = tcm->input_dev;
+#ifdef TYPE_B_PROTOCOL
+ unsigned int idx;
+#endif
+
+ if (input_dev == NULL)
+ return;
+
+ syna_pal_mutex_lock(&tcm->tp_event_mutex);
+
+#ifdef TYPE_B_PROTOCOL
+ for (idx = 0; idx < MAX_NUM_OBJECTS; idx++) {
+ input_mt_slot(input_dev, idx);
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0);
+ }
+#endif
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_report_key(input_dev, BTN_TOOL_FINGER, 0);
+#ifndef TYPE_B_PROTOCOL
+ input_mt_sync(input_dev);
+#endif
+ input_sync(input_dev);
+
+ syna_pal_mutex_unlock(&tcm->tp_event_mutex);
+
+}
+
+/**
+ * syna_dev_report_input_events()
+ *
+ * Report touched events to the input subsystem.
+ *
+ * After syna_tcm_get_event_data() function and the touched data is ready,
+ * this function can be called to report an input event.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * none.
+ */
+static void syna_dev_report_input_events(struct syna_tcm *tcm)
+{
+ unsigned int idx;
+ unsigned int x;
+ unsigned int y;
+ int wx;
+ int wy;
+ unsigned int status;
+ unsigned int touch_count;
+ struct input_dev *input_dev = tcm->input_dev;
+ unsigned int max_objects = tcm->tcm_dev->max_objects;
+ struct tcm_touch_data_blob *touch_data;
+ struct tcm_objects_data_blob *object_data;
+
+ if (input_dev == NULL)
+ return;
+
+ syna_pal_mutex_lock(&tcm->tp_event_mutex);
+
+ touch_data = &tcm->tp_data;
+ object_data = &tcm->tp_data.object_data[0];
+
+#ifdef ENABLE_WAKEUP_GESTURE
+ if ((tcm->pwr_state == LOW_PWR) && tcm->irq_wake) {
+ if (touch_data->gesture_id) {
+ LOGD("Gesture detected, id:%d\n",
+ touch_data->gesture_id);
+
+ input_report_key(input_dev, KEY_WAKEUP, 1);
+ input_sync(input_dev);
+ input_report_key(input_dev, KEY_WAKEUP, 0);
+ input_sync(input_dev);
+ }
+ }
+#endif
+
+ if (tcm->pwr_state == LOW_PWR)
+ goto exit;
+
+ touch_count = 0;
+
+ for (idx = 0; idx < max_objects; idx++) {
+ if (tcm->prev_obj_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(input_dev, idx);
+ input_mt_report_slot_state(input_dev,
+ MT_TOOL_FINGER, 0);
+#endif
+ break;
+ case FINGER:
+ case GLOVED_OBJECT:
+ x = object_data[idx].x_pos;
+ y = object_data[idx].y_pos;
+ wx = object_data[idx].x_width;
+ wy = object_data[idx].y_width;
+#ifdef REPORT_SWAP_XY
+ x = x ^ y;
+ y = x ^ y;
+ x = x ^ y;
+#endif
+#ifdef REPORT_FLIP_X
+ x = tcm->input_dev_params.max_x - x;
+#endif
+#ifdef REPORT_FLIP_Y
+ y = tcm->input_dev_params.max_y - y;
+#endif
+#ifdef TYPE_B_PROTOCOL
+ input_mt_slot(input_dev, idx);
+ input_mt_report_slot_state(input_dev,
+ MT_TOOL_FINGER, 1);
+#endif
+ input_report_key(input_dev, BTN_TOUCH, 1);
+ input_report_key(input_dev, BTN_TOOL_FINGER, 1);
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+#ifdef REPORT_TOUCH_WIDTH
+ input_report_abs(input_dev,
+ ABS_MT_TOUCH_MAJOR, MAX(wx, wy));
+ input_report_abs(input_dev,
+ ABS_MT_TOUCH_MINOR, MIN(wx, wy));
+#endif
+#ifndef TYPE_B_PROTOCOL
+ input_mt_sync(input_dev);
+#endif
+ LOGD("Finger %d: x = %d, y = %d\n", idx, x, y);
+ touch_count++;
+ break;
+ default:
+ break;
+ }
+
+ tcm->prev_obj_status[idx] = object_data[idx].status;
+ }
+
+ if (touch_count == 0) {
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_report_key(input_dev, BTN_TOOL_FINGER, 0);
+#ifndef TYPE_B_PROTOCOL
+ input_mt_sync(input_dev);
+#endif
+ }
+
+ input_sync(input_dev);
+
+exit:
+ syna_pal_mutex_unlock(&tcm->tp_event_mutex);
+
+}
+
+/**
+ * syna_dev_create_input_device()
+ *
+ * Allocate an input device and set up relevant parameters to the
+ * input subsystem.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_create_input_device(struct syna_tcm *tcm)
+{
+ int retval = 0;
+ struct tcm_dev *tcm_dev = tcm->tcm_dev;
+ struct input_dev *input_dev = NULL;
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ return -EINVAL;
+ }
+
+ input_dev = devm_input_allocate_device(dev);
+#else /* Legacy API */
+ input_dev = input_allocate_device();
+#endif
+ if (input_dev == NULL) {
+ LOGE("Fail to allocate input device\n");
+ return -ENODEV;
+ }
+
+ input_dev->name = TOUCH_INPUT_NAME;
+ input_dev->phys = TOUCH_INPUT_PHYS_PATH;
+ input_dev->id.product = SYNAPTICS_TCM_DRIVER_ID;
+ input_dev->id.version = SYNAPTICS_TCM_DRIVER_VERSION;
+ input_dev->dev.parent = tcm->pdev->dev.parent;
+ input_set_drvdata(input_dev, tcm);
+
+ set_bit(EV_SYN, input_dev->evbit);
+ set_bit(EV_KEY, input_dev->evbit);
+ set_bit(EV_ABS, input_dev->evbit);
+ set_bit(BTN_TOUCH, input_dev->keybit);
+ set_bit(BTN_TOOL_FINGER, input_dev->keybit);
+#ifdef INPUT_PROP_DIRECT
+ set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+#endif
+
+#ifdef ENABLE_WAKEUP_GESTURE
+ set_bit(KEY_WAKEUP, input_dev->keybit);
+ input_set_capability(input_dev, EV_KEY, KEY_WAKEUP);
+#endif
+
+ input_set_abs_params(input_dev,
+ ABS_MT_POSITION_X, 0, tcm_dev->max_x, 0, 0);
+ input_set_abs_params(input_dev,
+ ABS_MT_POSITION_Y, 0, tcm_dev->max_y, 0, 0);
+
+ input_mt_init_slots(input_dev, tcm_dev->max_objects,
+ INPUT_MT_DIRECT);
+
+#ifdef REPORT_TOUCH_WIDTH
+ input_set_abs_params(input_dev,
+ ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(input_dev,
+ ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+#endif
+
+ tcm->input_dev_params.max_x = tcm_dev->max_x;
+ tcm->input_dev_params.max_y = tcm_dev->max_y;
+ tcm->input_dev_params.max_objects = tcm_dev->max_objects;
+
+ retval = input_register_device(input_dev);
+ if (retval < 0) {
+ LOGE("Fail to register input device\n");
+ input_free_device(input_dev);
+ input_dev = NULL;
+ return retval;
+ }
+
+ tcm->input_dev = input_dev;
+
+ return 0;
+}
+
+/**
+ * syna_dev_release_input_device()
+ *
+ * Release an input device allocated previously.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * none.
+ */
+static void syna_dev_release_input_device(struct syna_tcm *tcm)
+{
+ if (!tcm->input_dev)
+ return;
+
+ input_unregister_device(tcm->input_dev);
+ input_free_device(tcm->input_dev);
+
+ tcm->input_dev = NULL;
+}
+
+/**
+ * syna_dev_check_input_params()
+ *
+ * Check if any of the input parameters registered to the input subsystem
+ * has changed.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * positive value to indicate mismatching parameters; otherwise, return 0.
+ */
+static int syna_dev_check_input_params(struct syna_tcm *tcm)
+{
+ struct tcm_dev *tcm_dev = tcm->tcm_dev;
+
+ if (tcm_dev->max_x == 0 && tcm_dev->max_y == 0)
+ return 0;
+
+ if (tcm->input_dev_params.max_x != tcm_dev->max_x)
+ return 1;
+
+ if (tcm->input_dev_params.max_y != tcm_dev->max_y)
+ return 1;
+
+ if (tcm->input_dev_params.max_objects != tcm_dev->max_objects)
+ return 1;
+
+ if (tcm_dev->max_objects > MAX_NUM_OBJECTS) {
+ LOGW("Out of max num objects defined, in app_info: %d\n",
+ tcm_dev->max_objects);
+ return 0;
+ }
+
+ LOGN("Input parameters unchanged\n");
+
+ return 0;
+}
+
+/**
+ * syna_dev_set_up_input_device()
+ *
+ * Set up input device to the input subsystem by confirming the supported
+ * parameters and creating the device.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_set_up_input_device(struct syna_tcm *tcm)
+{
+ int retval = 0;
+
+ if (IS_NOT_APP_FW_MODE(tcm->tcm_dev->dev_mode)) {
+ LOGN("Application firmware not running, current mode: %02x\n",
+ tcm->tcm_dev->dev_mode);
+ return 0;
+ }
+
+ syna_dev_free_input_events(tcm);
+
+ syna_pal_mutex_lock(&tcm->tp_event_mutex);
+
+ retval = syna_dev_check_input_params(tcm);
+ if (retval == 0)
+ goto exit;
+
+ if (tcm->input_dev != NULL)
+ syna_dev_release_input_device(tcm);
+
+ retval = syna_dev_create_input_device(tcm);
+ if (retval < 0) {
+ LOGE("Fail to create input device\n");
+ goto exit;
+ }
+
+exit:
+ syna_pal_mutex_unlock(&tcm->tp_event_mutex);
+
+ return retval;
+}
+
+/**
+ * syna_dev_isr()
+ *
+ * This is the function to be called when the interrupt is asserted.
+ * The purposes of this handler is to read events generated by device and
+ * retrieve all enqueued messages until ATTN is no longer asserted.
+ *
+ * @param
+ * [ in] irq: interrupt line
+ * [ in] data: private data being passed to the handler function
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static irqreturn_t syna_dev_isr(int irq, void *data)
+{
+ int retval;
+ unsigned char code = 0;
+ struct syna_tcm *tcm = data;
+ struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn;
+
+ if (unlikely(gpio_get_value(attn->irq_gpio) != attn->irq_on_state))
+ goto exit;
+
+ tcm->isr_pid = current->pid;
+
+#ifdef HAS_SYSFS_INTERFACE
+ if (tcm->is_attn_redirecting) {
+ syna_cdev_redirect_attn(tcm);
+ goto exit;
+ }
+#endif
+ /* retrieve the original report date generated by firmware */
+ retval = syna_tcm_get_event_data(tcm->tcm_dev,
+ &code,
+ &tcm->event_data);
+ if (retval < 0) {
+ LOGE("Fail to get event data\n");
+ goto exit;
+ }
+
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+ if (tcm->report_to_queue[code] == EFP_ENABLE) {
+ syna_tcm_buf_lock(&tcm->tcm_dev->external_buf);
+ syna_cdev_update_report_queue(tcm, code,
+ &tcm->tcm_dev->external_buf);
+ syna_tcm_buf_unlock(&tcm->tcm_dev->external_buf);
+#ifndef REPORT_CONCURRENTLY
+ goto exit;
+#endif
+ }
+#endif
+ /* report input event only when receiving a touch report */
+ if (code == REPORT_TOUCH) {
+ /* parse touch report once received */
+ retval = syna_tcm_parse_touch_report(tcm->tcm_dev,
+ tcm->event_data.buf,
+ tcm->event_data.data_length,
+ &tcm->tp_data);
+ if (retval < 0) {
+ LOGE("Fail to parse touch report\n");
+ goto exit;
+ }
+ /* forward the touch event to system */
+ syna_dev_report_input_events(tcm);
+ }
+
+exit:
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * syna_dev_request_irq()
+ *
+ * Allocate an interrupt line and register the ISR handler
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_request_irq(struct syna_tcm *tcm)
+{
+ int retval;
+ struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn;
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ retval = -EINVAL;
+ goto exit;
+ }
+#endif
+
+ if (attn->irq_gpio < 0) {
+ LOGE("Invalid IRQ GPIO\n");
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ attn->irq_id = gpio_to_irq(attn->irq_gpio);
+
+#ifdef DEV_MANAGED_API
+ retval = devm_request_threaded_irq(dev,
+ attn->irq_id,
+ NULL,
+ syna_dev_isr,
+ attn->irq_flags,
+ PLATFORM_DRIVER_NAME,
+ tcm);
+#else /* Legacy API */
+ retval = request_threaded_irq(attn->irq_id,
+ NULL,
+ syna_dev_isr,
+ attn->irq_flags,
+ PLATFORM_DRIVER_NAME,
+ tcm);
+#endif
+ if (retval < 0) {
+ LOGE("Fail to request threaded irq\n");
+ goto exit;
+ }
+
+ attn->irq_enabled = true;
+
+ LOGI("Interrupt handler registered\n");
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_dev_release_irq()
+ *
+ * Release an interrupt line allocated previously
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * none.
+ */
+static void syna_dev_release_irq(struct syna_tcm *tcm)
+{
+ struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn;
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ return;
+ }
+#endif
+
+ if (attn->irq_id <= 0)
+ return;
+
+ if (tcm->hw_if->ops_enable_irq)
+ tcm->hw_if->ops_enable_irq(tcm->hw_if, false);
+
+#ifdef DEV_MANAGED_API
+ devm_free_irq(dev, attn->irq_id, tcm);
+#else
+ free_irq(attn->irq_id, tcm);
+#endif
+
+ attn->irq_id = 0;
+ attn->irq_enabled = false;
+
+ LOGI("Interrupt handler released\n");
+}
+
+/**
+ * syna_dev_set_up_app_fw()
+ *
+ * Implement the essential steps for the initialization including the
+ * preparation of app info and the configuration of touch report.
+ *
+ * This function should be called whenever the device initially powers
+ * up, resets, or firmware update.
+ *
+ * @param
+ * [ in] tcm: tcm driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_set_up_app_fw(struct syna_tcm *tcm)
+{
+ int retval = 0;
+ struct tcm_dev *tcm_dev = tcm->tcm_dev;
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGN("Application firmware not running, current mode: %02x\n",
+ tcm_dev->dev_mode);
+ return -EINVAL;
+ }
+
+ /* collect app info containing most of sensor information */
+ retval = syna_tcm_get_app_info(tcm_dev, &tcm_dev->app_info);
+ if (retval < 0) {
+ LOGE("Fail to get application info\n");
+ return -EIO;
+ }
+
+ /* set up the format of touch report */
+#ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG
+ retval = syna_tcm_set_touch_report_config(tcm_dev,
+ custom_touch_format,
+ (unsigned int)sizeof(custom_touch_format));
+ if (retval < 0) {
+ LOGE("Fail to setup the custom touch report format\n");
+ return -EIO;
+ }
+#endif
+ /* preserve the format of touch report */
+ retval = syna_tcm_preserve_touch_report_config(tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to preserve touch report config\n");
+ return -EIO;
+ }
+
+#ifdef ENABLE_CUSTOM_TOUCH_ENTITY
+ /* set up custom touch data parsing method */
+ retval = syna_tcm_set_custom_touch_data_parsing_callback(tcm_dev,
+ syna_dev_parse_custom_touch_report,
+ (void *)tcm);
+ if (retval < 0) {
+ LOGE("Fail to set up custom touch data parsing method\n");
+ return -EIO;
+ }
+#endif
+ return retval;
+}
+
+/**
+ * syna_dev_reflash_startup_work()
+ *
+ * Perform firmware update during system startup.
+ * Function is available when the 'STARTUP_REFLASH' configuration
+ * is enabled.
+ *
+ * @param
+ * [ in] work: handle of work structure
+ *
+ * @return
+ * none.
+ */
+#ifdef STARTUP_REFLASH
+static void syna_dev_reflash_startup_work(struct work_struct *work)
+{
+ int retval;
+ struct delayed_work *delayed_work;
+ struct syna_tcm *tcm;
+ struct tcm_dev *tcm_dev;
+ const struct firmware *fw_entry;
+ const unsigned char *fw_image = NULL;
+ unsigned int fw_image_size;
+
+ delayed_work = container_of(work, struct delayed_work, work);
+ tcm = container_of(delayed_work, struct syna_tcm, reflash_work);
+
+ tcm_dev = tcm->tcm_dev;
+
+ /* get firmware image */
+ retval = request_firmware(&fw_entry,
+ FW_IMAGE_NAME,
+ tcm->pdev->dev.parent);
+ if (retval < 0) {
+ LOGE("Fail to request %s\n", FW_IMAGE_NAME);
+ return;
+ }
+
+ fw_image = fw_entry->data;
+ fw_image_size = fw_entry->size;
+
+ LOGD("Firmware image size = %d\n", fw_image_size);
+
+ pm_stay_awake(&tcm->pdev->dev);
+
+ /* perform fw update */
+#ifdef MULTICHIP_DUT_REFLASH
+ /* do firmware update for the multichip-based device */
+ retval = syna_tcm_romboot_do_multichip_reflash(tcm_dev,
+ fw_image,
+ fw_image_size,
+ RESP_IN_ATTN,
+ false);
+#else
+ /* do firmware update for the common device */
+ retval = syna_tcm_do_fw_update(tcm_dev,
+ fw_image,
+ fw_image_size,
+ RESP_IN_ATTN,
+ false);
+#endif
+ if (retval < 0) {
+ LOGE("Fail to do reflash\n");
+ goto exit;
+ }
+
+ /* re-initialize the app fw */
+ retval = syna_dev_set_up_app_fw(tcm);
+ if (retval < 0) {
+ LOGE("Fail to set up app fw after fw update\n");
+ goto exit;
+ }
+
+ /* allocate the input device if not registered yet */
+ if (tcm->input_dev == NULL) {
+ retval = syna_dev_set_up_input_device(tcm);
+ if (retval < 0) {
+ LOGE("Fail to register input device\n");
+ goto exit;
+ }
+ }
+
+exit:
+ pm_relax(&tcm->pdev->dev);
+}
+#endif
+/**
+ * syna_dev_enter_normal_sensing()
+ *
+ * Helper to enter normal seneing mode
+ *
+ * @param
+ * [ in] tcm: tcm driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_enter_normal_sensing(struct syna_tcm *tcm)
+{
+ int retval = 0;
+
+ if (!tcm)
+ return -EINVAL;
+
+ /* bring out of sleep mode. */
+ retval = syna_tcm_sleep(tcm->tcm_dev, false);
+ if (retval < 0) {
+ LOGE("Fail to exit deep sleep\n");
+ return retval;
+ }
+
+ /* disable low power gesture mode, if needed */
+ if (tcm->lpwg_enabled) {
+ retval = syna_dev_enable_lowpwr_gesture(tcm, false);
+ if (retval < 0) {
+ LOGE("Fail to disable low power gesture mode\n");
+ return retval;
+ }
+ }
+
+ tcm->pwr_state = PWR_ON;
+
+ return 0;
+}
+/**
+ * syna_dev_enter_lowpwr_sensing()
+ *
+ * Helper to enter power-saved sensing mode, that
+ * may be the lower power gesture mode or deep sleep mode.
+ *
+ * @param
+ * [ in] tcm: tcm driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_enter_lowpwr_sensing(struct syna_tcm *tcm)
+{
+ int retval = 0;
+
+ if (!tcm)
+ return -EINVAL;
+
+ /* enable low power gesture mode, if needed */
+ if (tcm->lpwg_enabled) {
+ retval = syna_dev_enable_lowpwr_gesture(tcm, true);
+ if (retval < 0) {
+ LOGE("Fail to disable low power gesture mode\n");
+ return retval;
+ }
+ } else {
+ /* enter sleep mode for non-LPWG cases */
+ if (!tcm->slept_in_early_suspend) {
+ retval = syna_tcm_sleep(tcm->tcm_dev, true);
+ if (retval < 0) {
+ LOGE("Fail to enter deep sleep\n");
+ return retval;
+ }
+ }
+ }
+
+ tcm->pwr_state = LOW_PWR;
+
+ return 0;
+}
+/**
+ * syna_dev_resume()
+ *
+ * Resume from the suspend state.
+ * If RESET_ON_RESUME is defined, a reset is issued to the touch controller.
+ * Otherwise, the touch controller is brought out of sleep mode.
+ *
+ * @param
+ * [ in] dev: an instance of device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_resume(struct device *dev)
+{
+ int retval;
+ struct syna_tcm *tcm = dev_get_drvdata(dev);
+ struct syna_hw_interface *hw_if = tcm->hw_if;
+ bool irq_enabled = true;
+
+ /* exit directly if device isn't in suspend state */
+ if (tcm->pwr_state == PWR_ON)
+ return 0;
+
+ LOGI("Prepare to resume device\n");
+
+#ifdef RESET_ON_RESUME
+ syna_pal_sleep_ms(RESET_ON_RESUME_DELAY_MS);
+
+ if (hw_if->ops_hw_reset) {
+ hw_if->ops_hw_reset(hw_if);
+ } else {
+ retval = syna_tcm_reset(tcm->tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to do reset\n");
+ goto exit;
+ }
+ }
+#else
+ /* enter normal power mode */
+ retval = syna_dev_enter_normal_sensing(tcm);
+ if (retval < 0) {
+ LOGE("Fail to enter normal power mode\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_rezero(tcm->tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to rezero\n");
+ goto exit;
+ }
+#endif /* end of RESET_ON_RESUME */
+
+ LOGI("Prepare to set up application firmware\n");
+
+ /* set up app firmware */
+ retval = syna_dev_set_up_app_fw(tcm);
+ if (retval < 0) {
+ LOGE("Fail to set up app firmware on resume\n");
+ goto exit;
+ }
+
+ retval = 0;
+
+ LOGI("Device resumed (pwr_state:%d)\n", tcm->pwr_state);
+
+exit:
+ /* once lpwg is enabled, irq should be enabled already.
+ * otherwise, set irq back to active mode.
+ */
+ irq_enabled = (!tcm->lpwg_enabled);
+
+ /* enable irq */
+ if (irq_enabled && (hw_if->ops_enable_irq))
+ hw_if->ops_enable_irq(hw_if, true);
+
+ tcm->slept_in_early_suspend = false;
+
+ return retval;
+}
+
+/**
+ * syna_dev_suspend()
+ *
+ * Put device into suspend state.
+ * Enter either the lower power gesture mode or sleep mode.
+ *
+ * @param
+ * [ in] dev: an instance of device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_suspend(struct device *dev)
+{
+#ifdef POWER_ALIVE_AT_SUSPEND
+ int retval;
+#endif
+ struct syna_tcm *tcm = dev_get_drvdata(dev);
+ struct syna_hw_interface *hw_if = tcm->hw_if;
+ bool irq_disabled = true;
+
+ /* exit directly if device is already in suspend state */
+ if (tcm->pwr_state != PWR_ON)
+ return 0;
+
+ LOGI("Prepare to suspend device\n");
+
+ /* clear all input events */
+ syna_dev_free_input_events(tcm);
+
+#ifdef POWER_ALIVE_AT_SUSPEND
+ /* enter power saved mode if power is not off */
+ retval = syna_dev_enter_lowpwr_sensing(tcm);
+ if (retval < 0) {
+ LOGE("Fail to enter suspended power mode\n");
+ return retval;
+ }
+#else
+ tcm->pwr_state = PWR_OFF;
+#endif
+
+ /* once lpwg is enabled, irq should be alive.
+ * otherwise, disable irq in suspend.
+ */
+ irq_disabled = (!tcm->lpwg_enabled);
+
+ /* disable irq */
+ if (irq_disabled && (hw_if->ops_enable_irq))
+ hw_if->ops_enable_irq(hw_if, false);
+
+ LOGI("Device suspended (pwr_state:%d)\n", tcm->pwr_state);
+
+ return 0;
+}
+
+#if defined(ENABLE_DISP_NOTIFIER)
+/**
+ * syna_dev_early_suspend()
+ *
+ * If having early suspend support, enter the sleep mode for
+ * non-lpwg cases.
+ *
+ * @param
+ * [ in] dev: an instance of device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_early_suspend(struct device *dev)
+{
+ int retval;
+ struct syna_tcm *tcm = dev_get_drvdata(dev);
+
+ /* exit directly if device is already in suspend state */
+ if (tcm->pwr_state != PWR_ON)
+ return 0;
+
+ if (!tcm->lpwg_enabled) {
+ retval = syna_tcm_sleep(tcm->tcm_dev, true);
+ if (retval < 0) {
+ LOGE("Fail to enter deep sleep\n");
+ return retval;
+ }
+ }
+
+ tcm->slept_in_early_suspend = true;
+
+ return 0;
+}
+/**
+ * syna_dev_fb_notifier_cb()
+ *
+ * Listen the display screen on/off event and perform the corresponding
+ * actions.
+ *
+ * @param
+ * [ in] nb: instance of notifier_block
+ * [ in] action: fb action
+ * [ in] data: fb event data
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_fb_notifier_cb(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ int retval;
+ int transition;
+#if defined(USE_DRM_PANEL_NOTIFIER)
+ struct drm_panel_notifier *evdata = data;
+#else
+ struct fb_event *evdata = data;
+#endif
+ struct syna_tcm *tcm = container_of(nb, struct syna_tcm, fb_notifier);
+ int time = 0;
+ int disp_blank_powerdown;
+ int disp_early_event_blank;
+ int disp_blank;
+ int disp_blank_unblank;
+
+ if (!evdata || !evdata->data || !tcm)
+ return 0;
+
+ retval = 0;
+
+#if defined(USE_DRM_PANEL_NOTIFIER)
+ disp_blank_powerdown = DRM_PANEL_BLANK_POWERDOWN;
+ disp_early_event_blank = DRM_PANEL_EARLY_EVENT_BLANK;
+ disp_blank = DRM_PANEL_EVENT_BLANK;
+ disp_blank_unblank = DRM_PANEL_BLANK_UNBLANK;
+#else
+ disp_blank_powerdown = FB_BLANK_POWERDOWN;
+ disp_early_event_blank = FB_EARLY_EVENT_BLANK;
+ disp_blank = FB_EVENT_BLANK;
+ disp_blank_unblank = FB_BLANK_UNBLANK;
+#endif
+
+ transition = *(int *)evdata->data;
+
+ /* confirm the firmware flashing is completed before screen off */
+ if (transition == disp_blank_powerdown) {
+ while (ATOMIC_GET(tcm->tcm_dev->firmware_flashing)) {
+ syna_pal_sleep_ms(500);
+
+ time += 500;
+ if (time >= 5000) {
+ LOGE("Timed out waiting for reflashing\n");
+ ATOMIC_SET(tcm->tcm_dev->firmware_flashing, 0);
+ return -EIO;
+ }
+ }
+ }
+
+ if (action == disp_early_event_blank &&
+ transition == disp_blank_powerdown) {
+ retval = syna_dev_early_suspend(&tcm->pdev->dev);
+ } else if (action == disp_blank) {
+ if (transition == disp_blank_powerdown) {
+ retval = syna_dev_suspend(&tcm->pdev->dev);
+ tcm->fb_ready = 0;
+ } else if (transition == disp_blank_unblank) {
+#ifndef RESUME_EARLY_UNBLANK
+ retval = syna_dev_resume(&tcm->pdev->dev);
+ tcm->fb_ready++;
+#endif
+ } else if (action == disp_early_event_blank &&
+ transition == disp_blank_unblank) {
+#ifdef RESUME_EARLY_UNBLANK
+ retval = syna_dev_resume(&tcm->pdev->dev);
+ tcm->fb_ready++;
+#endif
+ }
+ }
+
+ return 0;
+}
+#endif
+
+/**
+ * syna_dev_disconnect()
+ *
+ * This function will power off the connected device.
+ * Then, all the allocated resource will be released.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_disconnect(struct syna_tcm *tcm)
+{
+ struct syna_hw_interface *hw_if = tcm->hw_if;
+
+ if (tcm->is_connected == false) {
+ LOGI("%s already disconnected\n", PLATFORM_DRIVER_NAME);
+ return 0;
+ }
+
+#ifdef STARTUP_REFLASH
+ cancel_delayed_work_sync(&tcm->reflash_work);
+ flush_workqueue(tcm->reflash_workqueue);
+ destroy_workqueue(tcm->reflash_workqueue);
+#endif
+
+ /* free interrupt line */
+ if (hw_if->bdata_attn.irq_id)
+ syna_dev_release_irq(tcm);
+
+ /* unregister input device */
+ syna_dev_release_input_device(tcm);
+
+ tcm->input_dev_params.max_x = 0;
+ tcm->input_dev_params.max_y = 0;
+ tcm->input_dev_params.max_objects = 0;
+
+ /* power off */
+ if (hw_if->ops_power_on)
+ hw_if->ops_power_on(hw_if, false);
+
+ tcm->pwr_state = PWR_OFF;
+ tcm->is_connected = false;
+
+ LOGI("%s device disconnected\n", PLATFORM_DRIVER_NAME);
+
+ return 0;
+}
+
+/**
+ * syna_dev_connect()
+ *
+ * This function will power on and identify the connected device.
+ * At the end of function, the ISR will be registered as well.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_connect(struct syna_tcm *tcm)
+{
+ int retval;
+ struct syna_hw_interface *hw_if = tcm->hw_if;
+ struct tcm_dev *tcm_dev = tcm->tcm_dev;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm_dev\n");
+ return -EINVAL;
+ }
+
+ if (tcm->is_connected) {
+ LOGI("%s already connected\n", PLATFORM_DRIVER_NAME);
+ return 0;
+ }
+
+ /* power on the connected device */
+ if (hw_if->ops_power_on) {
+ retval = hw_if->ops_power_on(hw_if, true);
+ if (retval < 0)
+ return -ENODEV;
+ }
+
+ /* perform a hardware reset */
+ if (hw_if->ops_hw_reset)
+ hw_if->ops_hw_reset(hw_if);
+
+ /* detect which modes of touch controller is running
+ *
+ * the signal of the Touch IC ready is called as "identify"
+ * report and generated by firmware
+ */
+ retval = syna_tcm_detect_device(tcm->tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to detect the device\n");
+ goto err_detect_dev;
+ }
+
+ switch (retval) {
+ case MODE_APPLICATION_FIRMWARE:
+ retval = syna_dev_set_up_app_fw(tcm);
+ if (retval < 0) {
+#ifdef FORCE_CONNECTION
+ LOGW("App firmware is not available yet\n");
+ LOGW("However, connect and skip initialization\n");
+ goto end;
+#else
+ LOGE("Fail to set up application firmware\n");
+ goto err_setup_app_fw;
+#endif
+ }
+
+ /* allocate and register to input device subsystem */
+ retval = syna_dev_set_up_input_device(tcm);
+ if (retval < 0) {
+ LOGE("Fail to set up input device\n");
+ goto err_setup_input_dev;
+ }
+
+ break;
+ default:
+ LOGN("Application firmware not running, current mode: %02x\n",
+ retval);
+ break;
+ }
+
+ /* register the interrupt handler */
+ retval = syna_dev_request_irq(tcm);
+ if (retval < 0) {
+ LOGE("Fail to request the interrupt line\n");
+ goto err_request_irq;
+ }
+
+ /* for the reference,
+ * create a delayed work to perform fw update during the startup time
+ */
+#ifdef STARTUP_REFLASH
+ tcm->reflash_workqueue =
+ create_singlethread_workqueue("syna_reflash");
+ INIT_DELAYED_WORK(&tcm->reflash_work, syna_dev_reflash_startup_work);
+ queue_delayed_work(tcm->reflash_workqueue, &tcm->reflash_work,
+ msecs_to_jiffies(STARTUP_REFLASH_DELAY_TIME_MS));
+#endif
+#ifdef FORCE_CONNECTION
+end:
+#endif
+ tcm->pwr_state = PWR_ON;
+ tcm->is_connected = true;
+
+ LOGI("TCM packrat: %d\n", tcm->tcm_dev->packrat_number);
+ LOGI("Configuration: name(%s), lpwg(%s), hw_reset(%s), irq_ctrl(%s)\n",
+ PLATFORM_DRIVER_NAME,
+ (tcm->lpwg_enabled) ? "yes" : "no",
+ (hw_if->ops_hw_reset) ? "yes" : "no",
+ (hw_if->ops_enable_irq) ? "yes" : "no");
+
+ return 0;
+
+err_request_irq:
+ /* unregister input device */
+ syna_dev_release_input_device(tcm);
+
+err_setup_input_dev:
+#ifdef FORCE_CONNECTION
+#else
+err_setup_app_fw:
+#endif
+err_detect_dev:
+ if (hw_if->ops_power_on)
+ hw_if->ops_power_on(hw_if, false);
+
+ return retval;
+}
+
+#ifdef USE_DRM_PANEL_NOTIFIER
+static struct drm_panel *syna_dev_get_panel(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 NULL;
+
+ 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)) {
+ LOGI("Find available panel\n");
+ return panel;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+/**
+ * syna_dev_probe()
+ *
+ * Install the TouchComm device driver
+ *
+ * @param
+ * [ in] pdev: an instance of platform device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_probe(struct platform_device *pdev)
+{
+ int retval;
+ struct syna_tcm *tcm = NULL;
+ struct tcm_dev *tcm_dev = NULL;
+ struct syna_hw_interface *hw_if = NULL;
+#if defined(USE_DRM_PANEL_NOTIFIER)
+ struct device *dev;
+#endif
+
+ hw_if = pdev->dev.platform_data;
+ if (!hw_if) {
+ LOGE("Fail to find hardware configuration\n");
+ return -EINVAL;
+ }
+
+ tcm = syna_pal_mem_alloc(1, sizeof(struct syna_tcm));
+ if (!tcm) {
+ LOGE("Fail to create the instance of syna_tcm\n");
+ return -ENOMEM;
+ }
+
+ /* allocate the TouchCom device handle
+ * recommend to set polling mode here because isr is not registered yet
+ */
+ retval = syna_tcm_allocate_device(&tcm_dev, hw_if, RESP_IN_POLLING);
+ if ((retval < 0) || (!tcm_dev)) {
+ LOGE("Fail to allocate TouchCom device handle\n");
+ goto err_allocate_cdev;
+ }
+
+ tcm->tcm_dev = tcm_dev;
+ tcm->pdev = pdev;
+ tcm->hw_if = hw_if;
+
+ syna_tcm_buf_init(&tcm->event_data);
+
+ syna_pal_mutex_alloc(&tcm->tp_event_mutex);
+
+#ifdef ENABLE_WAKEUP_GESTURE
+ tcm->lpwg_enabled = true;
+#else
+ tcm->lpwg_enabled = false;
+#endif
+ tcm->irq_wake = false;
+
+ tcm->is_connected = false;
+ tcm->pwr_state = PWR_OFF;
+
+ tcm->dev_connect = syna_dev_connect;
+ tcm->dev_disconnect = syna_dev_disconnect;
+ tcm->dev_set_up_app_fw = syna_dev_set_up_app_fw;
+ tcm->dev_set_normal_sensing = syna_dev_enter_normal_sensing;
+ tcm->dev_set_lowpwr_sensing = syna_dev_enter_lowpwr_sensing;
+
+ platform_set_drvdata(pdev, tcm);
+
+ device_init_wakeup(&pdev->dev, 1);
+
+#if defined(TCM_CONNECT_IN_PROBE)
+ /* connect to target device */
+ retval = tcm->dev_connect(tcm);
+ if (retval < 0) {
+ LOGE("Fail to connect to the device\n");
+ syna_pal_mutex_free(&tcm->tp_event_mutex);
+ goto err_connect;
+ }
+#endif
+
+#ifdef HAS_SYSFS_INTERFACE
+ /* create the device file and register to char device classes */
+ retval = syna_cdev_create_sysfs(tcm, pdev);
+ if (retval < 0) {
+ LOGE("Fail to create the device sysfs\n");
+ syna_pal_mutex_free(&tcm->tp_event_mutex);
+ goto err_create_cdev;
+ }
+#endif
+
+#if defined(ENABLE_DISP_NOTIFIER)
+#if defined(USE_DRM_PANEL_NOTIFIER)
+ dev = syna_request_managed_device();
+ active_panel = syna_dev_get_panel(dev->of_node);
+ if (active_panel) {
+ tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb;
+ retval = drm_panel_notifier_register(active_panel,
+ &tcm->fb_notifier);
+ if (retval < 0) {
+ LOGE("Fail to register FB notifier client\n");
+ goto err_create_cdev;
+ }
+ } else {
+ LOGE("No available drm panel\n");
+ }
+#else
+ tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb;
+ retval = fb_register_client(&tcm->fb_notifier);
+ if (retval < 0) {
+ LOGE("Fail to register FB notifier client\n");
+ goto err_create_cdev;
+ }
+#endif
+#endif
+
+ LOGI("%s TouchComm driver v%d.%s installed\n",
+ PLATFORM_DRIVER_NAME,
+ SYNAPTICS_TCM_DRIVER_VERSION,
+ SYNAPTICS_TCM_DRIVER_SUBVER);
+
+ return 0;
+
+#ifdef HAS_SYSFS_INTERFACE
+err_create_cdev:
+ syna_tcm_remove_device(tcm->tcm_dev);
+#endif
+#if defined(TCM_CONNECT_IN_PROBE)
+ tcm->dev_disconnect(tcm);
+err_connect:
+#endif
+ syna_tcm_buf_release(&tcm->event_data);
+ syna_pal_mutex_free(&tcm->tp_event_mutex);
+err_allocate_cdev:
+ syna_pal_mem_free((void *)tcm);
+
+ return retval;
+}
+
+/**
+ * syna_dev_remove()
+ *
+ * Release all allocated resources and remove the TouchCom device handle
+ *
+ * @param
+ * [ in] pdev: an instance of platform device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_dev_remove(struct platform_device *pdev)
+{
+ struct syna_tcm *tcm = platform_get_drvdata(pdev);
+
+ if (!tcm) {
+ LOGW("Invalid handle to remove\n");
+ return 0;
+ }
+
+#if defined(ENABLE_DISP_NOTIFIER)
+#if defined(USE_DRM_PANEL_NOTIFIER)
+ if (active_panel)
+ drm_panel_notifier_unregister(active_panel,
+ &tcm->fb_notifier);
+#else
+ fb_unregister_client(&tcm->fb_notifier);
+#endif
+#endif
+
+#ifdef HAS_SYSFS_INTERFACE
+ /* remove the cdev and sysfs nodes */
+ syna_cdev_remove_sysfs(tcm);
+#endif
+
+ /* check the connection statusm, and do disconnection */
+ if (tcm->dev_disconnect(tcm) < 0)
+ LOGE("Fail to do device disconnection\n");
+
+ syna_tcm_buf_release(&tcm->event_data);
+
+ syna_pal_mutex_free(&tcm->tp_event_mutex);
+
+ /* remove the allocated tcm device */
+ syna_tcm_remove_device(tcm->tcm_dev);
+
+ /* release the device context */
+ syna_pal_mem_free((void *)tcm);
+
+ return 0;
+}
+
+/**
+ * syna_dev_shutdown()
+ *
+ * Call syna_dev_remove() to release all resources
+ *
+ * @param
+ * [in] pdev: an instance of platform device
+ *
+ * @return
+ * none.
+ */
+static void syna_dev_shutdown(struct platform_device *pdev)
+{
+ syna_dev_remove(pdev);
+}
+
+
+/**
+ * Declare a TouchComm platform device
+ */
+#ifdef CONFIG_PM
+static const struct dev_pm_ops syna_dev_pm_ops = {
+#if !defined(ENABLE_DISP_NOTIFIER)
+ .suspend = syna_dev_suspend,
+ .resume = syna_dev_resume,
+#endif
+};
+#endif
+
+static struct platform_driver syna_dev_driver = {
+ .driver = {
+ .name = PLATFORM_DRIVER_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &syna_dev_pm_ops,
+#endif
+ },
+ .probe = syna_dev_probe,
+ .remove = syna_dev_remove,
+ .shutdown = syna_dev_shutdown,
+};
+
+
+/**
+ * syna_dev_module_init()
+ *
+ * The entry function of the reference driver, which initialize the
+ * lower-level bus and register a platform driver.
+ *
+ * @param
+ * void.
+ *
+ * @return
+ * 0 if the driver registered and bound to a device,
+ * else returns a negative error code and with the driver not registered.
+ */
+static int __init syna_dev_module_init(void)
+{
+ int retval;
+
+ retval = syna_hw_interface_init();
+ if (retval < 0)
+ return retval;
+
+ return platform_driver_register(&syna_dev_driver);
+}
+
+/**
+ * syna_dev_module_exit()
+ *
+ * Function is called when un-installing the driver.
+ * Remove the registered platform driver and the associated bus driver.
+ *
+ * @param
+ * void.
+ *
+ * @return
+ * none.
+ */
+static void __exit syna_dev_module_exit(void)
+{
+ platform_driver_unregister(&syna_dev_driver);
+
+ syna_hw_interface_exit();
+}
+
+module_init(syna_dev_module_init);
+module_exit(syna_dev_module_exit);
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TCM Touch Driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/syna_tcm2.h b/syna_tcm2.h
new file mode 100644
index 0000000..c65e031
--- /dev/null
+++ b/syna_tcm2.h
@@ -0,0 +1,416 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2.h
+ *
+ * The header file is used for the Synaptics TouchComm reference driver.
+ * Platform-specific functions and included headers are implemented in
+ * syna_touchcom_platform.h and syna_touchcom_runtime.h.
+ */
+
+#ifndef _SYNAPTICS_TCM2_DRIVER_H_
+#define _SYNAPTICS_TCM2_DRIVER_H_
+
+#include "syna_tcm2_platform.h"
+#include "synaptics_touchcom_core_dev.h"
+#include "synaptics_touchcom_func_touch.h"
+
+#define PLATFORM_DRIVER_NAME "synaptics_tcm"
+
+#define TOUCH_INPUT_NAME "synaptics_tcm_touch"
+#define TOUCH_INPUT_PHYS_PATH "synaptics_tcm/touch_input"
+
+#define CHAR_DEVICE_NAME "tcm"
+#define CHAR_DEVICE_MODE (0x0600)
+
+#define SYNAPTICS_TCM_DRIVER_ID (1 << 0)
+#define SYNAPTICS_TCM_DRIVER_VERSION 1
+#define SYNAPTICS_TCM_DRIVER_SUBVER "2.0"
+
+/**
+ * @section: Driver Configurations
+ *
+ * The macros in the driver files below are used for doing compile time
+ * configuration of the driver.
+ */
+
+/**
+ * @brief: HAS_SYSFS_INTERFACE
+ * Open to enable the sysfs interface
+ *
+ * @brief: HAS_REFLASH_FEATURE
+ * Open to enable firmware reflash features
+ *
+ * @brief: HAS_ROMBOOT_REFLASH_FEATURE
+ * Open to enable ROMBOOT reflash features
+ *
+ * @brief: HAS_TESTING_FEATURE
+ * Open to enable testing features
+ */
+#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS)
+#define HAS_SYSFS_INTERFACE
+#endif
+#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_REFLASH)
+#define HAS_REFLASH_FEATURE
+#endif
+#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_ROMBOOT)
+#define HAS_ROMBOOT_REFLASH_FEATURE
+#endif
+#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING)
+#define HAS_TESTING_FEATURE
+#endif
+
+/**
+ * @brief: TYPE_B_PROTOCOL
+ * Open to enable the multi-touch (MT) protocol
+ */
+#define TYPE_B_PROTOCOL
+
+/**
+ * @brief: RESET_ON_RESUME
+ * Open if willing to issue a reset to the touch controller
+ * from suspend.
+ * Set "disable" in default.
+ */
+/* #define RESET_ON_RESUME */
+
+/**
+ * @brief ENABLE_WAKEUP_GESTURE
+ * Open if having wake-up gesture support.
+ */
+/* #define ENABLE_WAKEUP_GESTURE */
+
+/**
+ * @brief REPORT_SWAP_XY
+ * Open if trying to swap x and y position coordinate reported.
+ * @brief REPORT_FLIP_X
+ * Open if trying to flip x position coordinate reported.
+ * @brief REPORT_FLIP_Y
+ * Open if trying to flip x position coordinate reported.
+ */
+/* #define REPORT_SWAP_XY */
+/* #define REPORT_FLIP_X */
+/* #define REPORT_FLIP_Y */
+
+/**
+ * @brief REPORT_TOUCH_WIDTH
+ * Open if willing to add the width data to the input event.
+ */
+#define REPORT_TOUCH_WIDTH
+
+/**
+ * @brief USE_CUSTOM_TOUCH_REPORT_CONFIG
+ * Open if willing to set up the format of touch report.
+ * The custom_touch_format[] array in syna_tcm2.c can be used
+ * to describe the customized report format.
+ */
+/* #define USE_CUSTOM_TOUCH_REPORT_CONFIG */
+
+/**
+ * @brief STARTUP_REFLASH
+ * Open if willing to do fw checking and update at startup.
+ * The firmware image will be obtained by request_firmware() API,
+ * so please ensure the image is built-in or included properly.
+ *
+ * This property is available only when SYNA_TCM2_REFLASH
+ * feature is enabled.
+ */
+#if defined(HAS_REFLASH_FEATURE) || defined(HAS_ROMBOOT_REFLASH_FEATURE)
+/* #define STARTUP_REFLASH */
+#endif
+/**
+ * @brief MULTICHIP_DUT_REFLASH
+ * Open if willing to do fw update and the DUT belongs to multi-chip
+ * product. This property dependent on STARTUP_REFLASH property.
+ *
+ * Set "disable" in default.
+ */
+#if defined(HAS_ROMBOOT_REFLASH_FEATURE) && defined(STARTUP_REFLASH)
+/* #define MULTICHIP_DUT_REFLASH */
+#endif
+
+/**
+ * @brief ENABLE_DISP_NOTIFIER
+ * Open if having display notification event and willing to listen
+ * the event from display driver.
+ *
+ * Set "disable" in default due to no generic notifier for DRM
+ */
+#if defined(CONFIG_FB) || defined(CONFIG_DRM_PANEL)
+/* #define ENABLE_DISP_NOTIFIER */
+#endif
+/**
+ * @brief RESUME_EARLY_UNBLANK
+ * Open if willing to resume in early un-blanking state.
+ *
+ * This property is available only when ENABLE_DISP_NOTIFIER
+ * feature is enabled.
+ */
+#ifdef ENABLE_DISP_NOTIFIER
+/* #define RESUME_EARLY_UNBLANK */
+#endif
+/**
+ * @brief USE_DRM_PANEL_NOTIFIER
+ * Open if willing to listen the notification event from
+ * DRM_PANEL. Please be noted that 'struct drm_panel_notifier'
+ * must be implemented in the target BSP.
+ *
+ * This property is available only when ENABLE_DISP_NOTIFIER
+ * feature is enabled.
+ *
+ * Set "disable" in default due to no generic notifier for DRM
+ */
+#if defined(ENABLE_DISP_NOTIFIER) && defined(CONFIG_DRM_PANEL)
+#define USE_DRM_PANEL_NOTIFIER
+#endif
+
+/**
+ * @brief ENABLE_EXTERNAL_FRAME_PROCESS
+ * Open if having external frame process to the userspace application.
+ *
+ * Set "enable" in default
+ *
+ * @brief REPORT_TYPES
+ * Total types of report being used for external frame process.
+ *
+ * @brief EFP_ENABLE / EFP_DISABLE
+ * Specific value to label whether the report is required to be
+ * process or not.
+ *
+ * @brief REPORT_CONCURRENTLY
+ * Open if willing to concurrently handle reports for both kernel
+ * and userspace application.
+ *
+ * Set "disable" in default
+ */
+#define ENABLE_EXTERNAL_FRAME_PROCESS
+#define REPORT_TYPES (256)
+#define EFP_ENABLE (1)
+#define EFP_DISABLE (0)
+/* #define REPORT_CONCURRENTLY */
+
+/**
+ * @brief TCM_CONNECT_IN_PROBE
+ * Open if willing to detect and connect to TouchComm device at
+ * probe function; otherwise, please invoke connect() manually.
+ *
+ * Set "enable" in default
+ */
+#define TCM_CONNECT_IN_PROBE
+
+/**
+ * @brief FORCE_CONNECTION
+ * Open if willing to connect to TouchComm device w/o error outs.
+ *
+ * Set "disable" in default
+ */
+/* #define FORCE_CONNECTION */
+
+/**
+ * @brief ENABLE_CUSTOM_TOUCH_ENTITY
+ * Open if having custom requirements to parse the custom code
+ * entity in the touch report.
+ *
+ * Set "disable" in default
+ */
+/* #define ENABLE_CUSTOM_TOUCH_ENTITY */
+
+/**
+ * @section: Power States
+ *
+ * The below structure enumerates the power states of device
+ */
+enum power_state {
+ PWR_OFF = 0,
+ PWR_ON,
+ LOW_PWR,
+};
+
+/**
+ * @brief: context of the synaptics linux-based driver
+ *
+ * The structure defines the kernel specific data in linux-based driver
+ */
+struct syna_tcm {
+
+ /* TouchComm device core context */
+ struct tcm_dev *tcm_dev;
+
+ /* PLatform device driver */
+ struct platform_device *pdev;
+
+ /* Generic touched data generated by tcm core lib */
+ struct tcm_touch_data_blob tp_data;
+
+ syna_pal_mutex_t tp_event_mutex;
+
+ unsigned char prev_obj_status[MAX_NUM_OBJECTS];
+
+ /* Buffer stored the irq event data */
+ struct tcm_buffer event_data;
+
+ /* Hardware interface layer */
+ struct syna_hw_interface *hw_if;
+
+ /* ISR-related variables */
+ pid_t isr_pid;
+ bool irq_wake;
+
+ /* cdev and sysfs nodes creation */
+ struct cdev char_dev;
+ dev_t char_dev_num;
+ int char_dev_ref_count;
+
+ struct class *device_class;
+ struct device *device;
+
+ struct kobject *sysfs_dir;
+
+ /* Input device registration */
+ struct input_dev *input_dev;
+ struct input_params {
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int max_objects;
+ } input_dev_params;
+
+ /* Workqueue used for fw update */
+ struct delayed_work reflash_work;
+ struct workqueue_struct *reflash_workqueue;
+
+ /* IOCTL-related variables */
+ pid_t proc_pid;
+ struct task_struct *proc_task;
+
+ /* flags */
+ int pwr_state;
+ bool slept_in_early_suspend;
+ bool lpwg_enabled;
+ bool is_attn_redirecting;
+ unsigned char fb_ready;
+ bool is_connected;
+
+ /* framebuffer callbacks notifier */
+#if defined(ENABLE_DISP_NOTIFIER)
+ struct notifier_block fb_notifier;
+#endif
+
+ /* fifo to pass the report to userspace */
+ unsigned int fifo_remaining_frame;
+ struct list_head frame_fifo_queue;
+ wait_queue_head_t wait_frame;
+ unsigned char report_to_queue[REPORT_TYPES];
+
+ /* Specific function pointer to do device connection.
+ *
+ * This function will power on and identify the connected device.
+ * At the end of function, the ISR will be registered as well.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*dev_connect)(struct syna_tcm *tcm);
+
+ /* Specific function pointer to disconnect the device
+ *
+ * This function will power off the connected device.
+ * Then, all the allocated resource will be released.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*dev_disconnect)(struct syna_tcm *tcm);
+
+ /* Specific function pointer to set up app fw firmware
+ *
+ * This function should be called whenever the device initially
+ * powers up, resets, or firmware update.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*dev_set_up_app_fw)(struct syna_tcm *tcm);
+
+ /* Specific function pointer to enter normal sensing mode
+ *
+ * @param
+ * [ in] tcm: tcm driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*dev_set_normal_sensing)(struct syna_tcm *tcm);
+
+ /* Specific function pointer to enter power-saved sensing mode.
+ *
+ * @param
+ * [ in] tcm: tcm driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*dev_set_lowpwr_sensing)(struct syna_tcm *tcm);
+};
+
+/**
+ * @brief: Helpers for cdevice nodes and sysfs nodes creation
+ *
+ * These functions are implemented in syna_touchcom_sysfs.c
+ * and available only when HAS_SYSFS_INTERFACE is enabled.
+ */
+#ifdef HAS_SYSFS_INTERFACE
+
+int syna_cdev_create_sysfs(struct syna_tcm *ptcm,
+ struct platform_device *pdev);
+
+void syna_cdev_remove_sysfs(struct syna_tcm *ptcm);
+
+void syna_cdev_redirect_attn(struct syna_tcm *ptcm);
+
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+void syna_cdev_update_report_queue(struct syna_tcm *tcm,
+ unsigned char code, struct tcm_buffer *pevent_data);
+#endif
+
+#endif
+
+#endif /* end of _SYNAPTICS_TCM2_DRIVER_H_ */
+
diff --git a/syna_tcm2_platform.h b/syna_tcm2_platform.h
new file mode 100644
index 0000000..23d2661
--- /dev/null
+++ b/syna_tcm2_platform.h
@@ -0,0 +1,306 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file: syna_tcm2_platform.h
+ *
+ * This file declares the platform-specific or hardware relevant data.
+ *
+ * The main structure, syna_hw_interface, abstracts the bus transferred,
+ * ATTN signal, RST_N pin, and power control operations.
+ */
+
+#ifndef _SYNAPTICS_TCM2_PLATFORM_H_
+#define _SYNAPTICS_TCM2_PLATFORM_H_
+
+#include "syna_tcm2_runtime.h"
+
+/**
+ * @section: The capability of bus transferred
+ *
+ * Declare read/write capability in bytes (0 = unlimited)
+ */
+#define RD_CHUNK_SIZE (512)
+#define WR_CHUNK_SIZE (512)
+
+
+/**
+ * @section: Defined Hardware-Specific Data
+ *
+ * @brief: syna_hw_bus_data
+ * Hardware Data for bus transferred
+ *
+ * @brief: syna_hw_attn_data
+ * Hardware Data for ATTN signal
+ *
+ * @brief: syna_hw_rst_data
+ * Hardware Data for RST_N pin
+ *
+ * @brief: syna_hw_pwr_data
+ * Hardware Data for power control
+ *
+ * @brief: syna_hw_interface
+ * Contain all above hardware data and abstract the
+ * hardware operations
+ */
+
+/* The hardware data especially for bus transferred */
+struct syna_hw_bus_data {
+ unsigned char type;
+ /* capability of i/o chunk */
+ unsigned int rd_chunk_size;
+ unsigned int wr_chunk_size;
+ /* clock frequency in hz */
+ unsigned int frequency_hz;
+ /* parameters for i2c */
+ unsigned int i2c_addr;
+ /* parameters for spi */
+ unsigned int spi_mode;
+ unsigned int spi_byte_delay_us;
+ unsigned int spi_block_delay_us;
+ /* mutex to protect the i/o, if needed */
+ syna_pal_mutex_t io_mutex;
+};
+
+/* The hardware data especially for ATTN signal */
+struct syna_hw_attn_data {
+ /* parameters */
+ int irq_gpio;
+ int irq_on_state;
+ unsigned long irq_flags;
+ int irq_id;
+ bool irq_enabled;
+ /* mutex to protect the irq control, if needed */
+ syna_pal_mutex_t irq_en_mutex;
+};
+
+/* The hardware data especially for RST_N pin */
+struct syna_hw_rst_data {
+ /* parameters */
+ int reset_gpio;
+ int reset_on_state;
+ unsigned int reset_delay_ms;
+ unsigned int reset_active_ms;
+};
+
+/* The hardware data especially for power control */
+struct syna_hw_pwr_data {
+ /* parameters */
+ int vdd_gpio;
+ int avdd_gpio;
+ int power_on_state;
+ unsigned int power_on_delay_ms;
+ /* voltage */
+ unsigned int vdd;
+ unsigned int vled;
+ unsigned int vio;
+ unsigned int vddtx;
+ /* regulators */
+ const char *vdd_reg_name;
+ void *vdd_reg_dev;
+ const char *avdd_reg_name;
+ void *avdd_reg_dev;
+};
+
+/**
+ * @section: Hardware Interface Abstraction Layer
+ *
+ * The structure contains the hardware-specific implementations
+ * on the target platform.
+ */
+struct syna_hw_interface {
+ /* The handle of hardware device */
+ void *pdev;
+
+ /* Hardware specific data */
+ struct syna_hw_bus_data bdata_io;
+ struct syna_hw_attn_data bdata_attn;
+ struct syna_hw_rst_data bdata_rst;
+ struct syna_hw_pwr_data bdata_pwr;
+
+ /* Operation to do power on/off, if supported
+ *
+ * This is an optional operation.
+ *
+ * Implementation should request that the power device be
+ * enabled with the output at the proper voltage.
+ *
+ * Assign the pointer NULL if power supply module is not controllable.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] en: '1' for powering on, and '0' for powering off
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+ int (*ops_power_on)(struct syna_hw_interface *hw_if,
+ bool en);
+
+ /* Operation to perform the hardware reset, if supported
+ *
+ * This is an optional operation.
+ *
+ * The actual reset waveform should be reference to the device spec.
+ *
+ * Assign the pointer NULL if RST_N pin is not controllable.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+ void (*ops_hw_reset)(struct syna_hw_interface *hw_if);
+
+ /* Operation to set up the bus connection
+ *
+ * This is an optional operation to add in the extra configuration
+ * before doing connection.
+ *
+ * Assign the pointer NULL if this operation is not required.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] config: parameters to change
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+ int (*ops_bus_setup)(struct syna_hw_interface *hw_if,
+ struct syna_hw_bus_data *config);
+
+ /* Operation to read the bare data from bus
+ *
+ * This is an essential operation; otherwise, the communication
+ * will not be created.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [out] rd_data: buffer for storing data retrieved
+ * [ in] rd_len: length of reading data in bytes
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+ int (*ops_read_data)(struct syna_hw_interface *hw_if,
+ unsigned char *rd_data, unsigned int rd_len);
+
+ /* Operation to write the bare data to bus
+ *
+ * This is an essential operation; otherwise, the communication
+ * will not be created.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] wr_data: written data
+ * [ in] wr_len: length of written data in bytes
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+ int (*ops_write_data)(struct syna_hw_interface *hw_if,
+ unsigned char *wr_data, unsigned int wr_len);
+
+ /* Operation to enable/disable the irq, if supported
+ *
+ * This is an optional operation. Providing this operation could
+ * minimize the frequency of ISR being called.
+ *
+ * Once disabled, ISR will not be invoked even ATTN is asserted.
+ *
+ * Assign the pointer NULL if irq is not controllable.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] en: '1' for enabling, and '0' for disabling
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+ int (*ops_enable_irq)(struct syna_hw_interface *hw_if,
+ bool en);
+
+ /* Operation to wait for the signal of interrupt, if supported
+ *
+ * This is an optional operation to help for creating the
+ * custom interrupt handler. Besides, the recommendation is to
+ * implement in one-shot approach if possible.
+ *
+ * Implementation should return control if ATTN is asserted or
+ * specified timeout expire. If timeout is 0, should check the
+ * state of the ATTN signal and return control immediately.
+ *
+ * Assign the pointer NULL if customized ISR is not required.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] timeout_ms: time frame in milliseconds
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+ int (*ops_wait_irq)(struct syna_hw_interface *hw_if,
+ unsigned int timeout_ms);
+
+};
+/* end of structure syna_hw_interface */
+
+
+/**
+ * syna_hw_interface_init()
+ *
+ * Initialize the lower-level hardware interface module.
+ * After returning, the handle of hw interface should be ready.
+ *
+ * @param
+ * void
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_hw_interface_init(void);
+
+/**
+ * syna_hw_interface_exit()
+ *
+ * Delete the lower-level hardware interface module.
+ *
+ * @param
+ * void
+ *
+ * @return
+ * none.
+ */
+void syna_hw_interface_exit(void);
+
+
+#endif /* end of _SYNAPTICS_TCM2_PLATFORM_H_ */
diff --git a/syna_tcm2_platform_i2c.c b/syna_tcm2_platform_i2c.c
new file mode 100644
index 0000000..34ce950
--- /dev/null
+++ b/syna_tcm2_platform_i2c.c
@@ -0,0 +1,898 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2_platform_i2c.c
+ *
+ * This file is the reference code of I2C module used for communicating with
+ * Synaptics TouchCom device using I2C
+ */
+
+#include <linux/i2c.h>
+
+#include "syna_tcm2.h"
+#include "syna_tcm2_platform.h"
+
+#define I2C_MODULE_NAME "synaptics_tcm_i2c"
+
+#define XFER_ATTEMPTS 5
+
+static struct platform_device *syna_i2c_device;
+
+
+/**
+ * syna_request_managed_device()
+ *
+ * Request and return the device pointer for managed
+ *
+ * @param
+ * none.
+ *
+ * @return
+ * a device pointer allocated previously
+ */
+#if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER)
+struct device *syna_request_managed_device(void)
+{
+ if (!syna_i2c_device)
+ return NULL;
+
+ return syna_i2c_device->dev.parent;
+}
+#endif
+
+/**
+ * syna_i2c_hw_reset()
+ *
+ * Toggle the hardware gpio pin to perform the chip reset
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ *
+ * @return
+ * none.
+ */
+static void syna_i2c_hw_reset(struct syna_hw_interface *hw_if)
+{
+ struct syna_hw_rst_data *rst = &hw_if->bdata_rst;
+
+ if (rst->reset_gpio >= 0) {
+ gpio_set_value(rst->reset_gpio, rst->reset_on_state);
+ syna_pal_sleep_ms(rst->reset_active_ms);
+ gpio_set_value(rst->reset_gpio, !rst->reset_on_state);
+ syna_pal_sleep_ms(rst->reset_delay_ms);
+ }
+}
+
+/**
+ * syna_i2c_request_gpio()
+ *
+ * Setup the given gpio
+ *
+ * @param
+ * [ in] gpio: the target gpio
+ * [ in] config: '1' for setting up, and '0' to release the gpio
+ * [ in] dir: default direction of gpio
+ * [ in] state: default state of gpio
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_request_gpio(int gpio, bool config, int dir,
+ int state, char *label)
+{
+ int retval;
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ return -ENODEV;
+ }
+#endif
+
+ if (config) {
+ retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio);
+ if (retval < 0) {
+ LOGE("Fail to set GPIO label\n");
+ return retval;
+ }
+#ifdef DEV_MANAGED_API
+ retval = devm_gpio_request(dev, gpio, label);
+#else /* Legacy API */
+ retval = gpio_request(gpio, label);
+#endif
+ if (retval < 0) {
+ LOGE("Fail 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("Fail to set GPIO %d direction\n", gpio);
+ return retval;
+ }
+ } else {
+#ifdef DEV_MANAGED_API
+ devm_gpio_free(dev, gpio);
+#else /* Legacy API */
+ gpio_free(gpio);
+#endif
+ }
+
+ return 0;
+}
+
+/**
+ * syna_i2c_config_gpio()
+ *
+ * Initialize the GPIOs defined in device tree
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_config_gpio(struct syna_hw_interface *hw_if)
+{
+ int retval;
+ static char str_irq_gpio[32] = {0};
+ static char str_rst_gpio[32] = {0};
+ static char str_vdd_gpio[32] = {0};
+ static char str_avdd_gpio[32] = {0};
+ struct syna_hw_attn_data *attn = &hw_if->bdata_attn;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+ struct syna_hw_rst_data *rst = &hw_if->bdata_rst;
+
+ if (attn->irq_gpio >= 0) {
+ retval = syna_i2c_request_gpio(attn->irq_gpio,
+ true, 0, 0, str_irq_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure interrupt GPIO %d\n",
+ attn->irq_gpio);
+ goto err_set_gpio_irq;
+ }
+ }
+
+ if (rst->reset_gpio >= 0) {
+ retval = syna_i2c_request_gpio(rst->reset_gpio,
+ true, 1, !rst->reset_on_state,
+ str_rst_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure reset GPIO %d\n",
+ rst->reset_gpio);
+ goto err_set_gpio_reset;
+ }
+ }
+
+ if (pwr->vdd_gpio >= 0) {
+ retval = syna_i2c_request_gpio(pwr->vdd_gpio,
+ true, 1, !pwr->power_on_state,
+ str_vdd_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure vdd GPIO %d\n",
+ pwr->vdd_gpio);
+ goto err_set_gpio_vdd;
+ }
+ }
+
+ if (pwr->avdd_gpio >= 0) {
+ retval = syna_i2c_request_gpio(pwr->avdd_gpio,
+ true, 1, !pwr->power_on_state,
+ str_avdd_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure avdd GPIO %d\n",
+ pwr->avdd_gpio);
+ goto err_set_gpio_avdd;
+ }
+ }
+ return 0;
+
+err_set_gpio_avdd:
+ if (pwr->vdd_gpio >= 0)
+ syna_i2c_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL);
+err_set_gpio_vdd:
+ if (rst->reset_gpio >= 0)
+ syna_i2c_request_gpio(rst->reset_gpio, false, 0, 0, NULL);
+err_set_gpio_reset:
+ if (attn->irq_gpio >= 0)
+ syna_i2c_request_gpio(attn->irq_gpio, false, 0, 0, NULL);
+err_set_gpio_irq:
+ return retval;
+}
+
+/**
+ * syna_i2c_enable_regulator()
+ *
+ * Enable or disable the regulator
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] en: '1' for enabling, and '0' for disabling
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_enable_regulator(struct syna_hw_interface *hw_if,
+ bool en)
+{
+ int retval;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+ struct regulator *vdd_reg = pwr->vdd_reg_dev;
+ struct regulator *avdd_reg = pwr->avdd_reg_dev;
+
+ if (!en) {
+ retval = 0;
+ goto disable_pwr_reg;
+ }
+
+ if (vdd_reg) {
+ retval = regulator_enable(vdd_reg);
+ if (retval < 0) {
+ LOGE("Fail to enable vdd regulator\n");
+ goto exit;
+ }
+ }
+
+ if (avdd_reg) {
+ retval = regulator_enable(avdd_reg);
+ if (retval < 0) {
+ LOGE("Fail to enable avdd regulator\n");
+ goto disable_avdd_reg;
+ }
+ syna_pal_sleep_ms(pwr->power_on_delay_ms);
+ }
+
+ return 0;
+
+disable_pwr_reg:
+ if (vdd_reg)
+ regulator_disable(vdd_reg);
+
+disable_avdd_reg:
+ if (avdd_reg)
+ regulator_disable(avdd_reg);
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_i2c_get_regulator()
+ *
+ * Acquire or release the regulator
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] get: '1' for getting the regulator, and '0' for removing
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_get_regulator(struct syna_hw_interface *hw_if,
+ bool get)
+{
+ int retval;
+ struct device *dev = syna_i2c_device->dev.parent;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+
+ if (!get) {
+ retval = 0;
+ goto regulator_put;
+ }
+
+ if (pwr->vdd_reg_name != NULL && *pwr->vdd_reg_name != 0) {
+#ifdef DEV_MANAGED_API
+ pwr->vdd_reg_dev = devm_regulator_get(dev, pwr->vdd_reg_name);
+#else /* Legacy API */
+ pwr->vdd_reg_dev = regulator_get(dev, pwr->vdd_reg_name);
+#endif
+ if (IS_ERR((struct regulator *)pwr->vdd_reg_dev)) {
+ LOGW("Vdd regulator is not ready\n");
+ retval = PTR_ERR((struct regulator *)pwr->vdd_reg_dev);
+ goto exit;
+ }
+ }
+
+ if (pwr->avdd_reg_name != NULL && *pwr->avdd_reg_name != 0) {
+#ifdef DEV_MANAGED_API
+ pwr->avdd_reg_dev = devm_regulator_get(dev, pwr->avdd_reg_name);
+#else /* Legacy API */
+ pwr->avdd_reg_dev = regulator_get(dev, pwr->avdd_reg_name);
+#endif
+ if (IS_ERR((struct regulator *)pwr->avdd_reg_dev)) {
+ LOGW("AVdd regulator is not ready\n");
+ retval = PTR_ERR((struct regulator *)pwr->avdd_reg_dev);
+ goto regulator_vdd_put;
+ }
+ }
+
+ return 0;
+
+regulator_put:
+ if (pwr->vdd_reg_dev) {
+#ifdef DEV_MANAGED_API
+ devm_regulator_put(pwr->vdd_reg_dev);
+#else /* Legacy API */
+ regulator_put(pwr->vdd_reg_dev);
+#endif
+ pwr->vdd_reg_dev = NULL;
+ }
+regulator_vdd_put:
+ if (pwr->avdd_reg_dev) {
+#ifdef DEV_MANAGED_API
+ devm_regulator_put(pwr->avdd_reg_dev);
+#else /* Legacy API */
+ regulator_put(pwr->avdd_reg_dev);
+#endif
+ pwr->avdd_reg_dev = NULL;
+ }
+exit:
+ return retval;
+}
+
+/**
+ * syna_i2c_enable_irq()
+ *
+ * Enable or disable the handling of interrupt
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] en: '1' for enabling, and '0' for disabling
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+static int syna_i2c_enable_irq(struct syna_hw_interface *hw_if,
+ bool en)
+{
+ int retval = 0;
+ struct syna_hw_attn_data *attn = &hw_if->bdata_attn;
+
+ if (attn->irq_id == 0)
+ return 0;
+
+ syna_pal_mutex_lock(&attn->irq_en_mutex);
+
+ /* enable the handling of interrupt */
+ if (en) {
+ if (attn->irq_enabled) {
+ LOGI("Interrupt already enabled\n");
+ retval = 0;
+ goto exit;
+ }
+
+ enable_irq(attn->irq_id);
+ attn->irq_enabled = true;
+
+ LOGD("irq enabled\n");
+ }
+ /* disable the handling of interrupt */
+ else {
+ if (!attn->irq_enabled) {
+ LOGI("Interrupt already disabled\n");
+ retval = 0;
+ goto exit;
+ }
+
+ disable_irq_nosync(attn->irq_id);
+ attn->irq_enabled = false;
+
+ LOGD("irq disabled\n");
+ }
+
+exit:
+ syna_pal_mutex_unlock(&attn->irq_en_mutex);
+
+ return retval;
+}
+
+
+/**
+ * syna_i2c_parse_dt()
+ *
+ * Parse and obtain board specific data from the device tree source file.
+ * Keep the data in structure syna_tcm_hw_data for later using.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] dev: device model
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+#ifdef CONFIG_OF
+static int syna_i2c_parse_dt(struct syna_hw_interface *hw_if,
+ struct device *dev)
+{
+ int retval;
+ u32 value;
+ struct property *prop;
+ struct device_node *np = dev->of_node;
+ const char *name;
+ struct syna_hw_attn_data *attn = &hw_if->bdata_attn;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+ struct syna_hw_rst_data *rst = &hw_if->bdata_rst;
+
+ prop = of_find_property(np, "synaptics,irq-gpio", NULL);
+ if (prop && prop->length) {
+ attn->irq_gpio = of_get_named_gpio_flags(np,
+ "synaptics,irq-gpio", 0,
+ (enum of_gpio_flags *)&attn->irq_flags);
+ } else {
+ attn->irq_gpio = -1;
+ }
+
+ retval = of_property_read_u32(np, "synaptics,irq-on-state", &value);
+ if (retval < 0)
+ attn->irq_on_state = 0;
+ else
+ attn->irq_on_state = value;
+
+ retval = of_property_read_string(np, "synaptics,avdd-name", &name);
+ if (retval < 0)
+ pwr->avdd_reg_name = NULL;
+ else
+ pwr->avdd_reg_name = name;
+
+ retval = of_property_read_string(np, "synaptics,vdd-name", &name);
+ if (retval < 0)
+ pwr->vdd_reg_name = NULL;
+ else
+ pwr->vdd_reg_name = name;
+
+ prop = of_find_property(np, "synaptics,vdd-gpio", NULL);
+ if (prop && prop->length) {
+ pwr->vdd_gpio = of_get_named_gpio_flags(np,
+ "synaptics,vdd-gpio", 0, NULL);
+ } else {
+ pwr->vdd_gpio = -1;
+ }
+
+ prop = of_find_property(np, "synaptics,avdd-gpio", NULL);
+ if (prop && prop->length) {
+ pwr->avdd_gpio = of_get_named_gpio_flags(np,
+ "synaptics,avdd-gpio", 0, NULL);
+ } else {
+ pwr->avdd_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("Fail to read power-on-state property\n");
+ return retval;
+ }
+
+ pwr->power_on_state = value;
+
+ } else {
+ pwr->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("Fail to read power-delay-ms property\n");
+ return retval;
+ }
+
+ pwr->power_on_delay_ms = value;
+
+ } else {
+ pwr->power_on_delay_ms = 0;
+ }
+
+ prop = of_find_property(np, "synaptics,reset-gpio", NULL);
+ if (prop && prop->length) {
+ rst->reset_gpio = of_get_named_gpio_flags(np,
+ "synaptics,reset-gpio", 0, NULL);
+ } else {
+ rst->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("Fail to read reset-on-state property\n");
+ return retval;
+ }
+
+ rst->reset_on_state = value;
+
+ } else {
+ rst->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("Fail to read reset-active-ms property\n");
+ return retval;
+ }
+
+ rst->reset_active_ms = value;
+
+ } else {
+ rst->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("Fail to read reset-delay-ms property\n");
+ return retval;
+ }
+
+ rst->reset_delay_ms = value;
+
+ } else {
+ rst->reset_delay_ms = 0;
+ }
+
+ return 0;
+}
+#endif
+
+
+/**
+ * syna_i2c_read()
+ *
+ * TouchCom over I2C uses the normal I2C addressing and transaction direction
+ * mechanisms to select the device and retrieve the data.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [out] rd_data: buffer for storing data retrieved from device
+ * [ in] rd_len: number of bytes retrieved from device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_read(struct syna_hw_interface *hw_if,
+ unsigned char *rd_data, unsigned int rd_len)
+{
+ int retval;
+ unsigned int attempt;
+ struct i2c_msg msg;
+ struct i2c_client *i2c = hw_if->pdev;
+ struct syna_hw_bus_data *bus = &hw_if->bdata_io;
+
+ if (!i2c) {
+ LOGE("Invalid bus io device\n");
+ return -EINVAL;
+ }
+
+ syna_pal_mutex_lock(&bus->io_mutex);
+
+ msg.addr = i2c->addr;
+ msg.flags = I2C_M_RD;
+ msg.len = rd_len;
+ msg.buf = rd_data;
+
+ for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) {
+ if (i2c_transfer(i2c->adapter, &msg, 1) == 1) {
+ retval = rd_len;
+ goto exit;
+ }
+ LOGE("Transfer attempt %d failed\n", attempt + 1);
+
+ if (attempt + 1 == XFER_ATTEMPTS) {
+ retval = -EIO;
+ goto exit;
+ }
+
+ syna_pal_sleep_ms(20);
+ }
+
+exit:
+ syna_pal_mutex_unlock(&bus->io_mutex);
+
+ return retval;
+}
+
+/**
+ * syna_i2c_write()
+ *
+ * TouchCom over I2C uses the normal I2C addressing and transaction direction
+ * mechanisms to select the device and send the data to the device.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] wr_data: written data
+ * [ in] wr_len: length of written data in bytes
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_write(struct syna_hw_interface *hw_if,
+ unsigned char *wr_data, unsigned int wr_len)
+{
+ int retval;
+ unsigned int attempt;
+ struct i2c_msg msg;
+ struct i2c_client *i2c = hw_if->pdev;
+ struct syna_hw_bus_data *bus = &hw_if->bdata_io;
+
+ if (!i2c) {
+ LOGE("Invalid bus io device\n");
+ return -EINVAL;
+ }
+
+ syna_pal_mutex_lock(&bus->io_mutex);
+
+ msg.addr = i2c->addr;
+ msg.flags = 0;
+ msg.len = wr_len;
+ msg.buf = wr_data;
+
+ for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) {
+ if (i2c_transfer(i2c->adapter, &msg, 1) == 1) {
+ retval = wr_len;
+ goto exit;
+ }
+ LOGE("Transfer attempt %d failed\n", attempt + 1);
+
+ if (attempt + 1 == XFER_ATTEMPTS) {
+ retval = -EIO;
+ goto exit;
+ }
+
+ syna_pal_sleep_ms(20);
+ }
+
+exit:
+ syna_pal_mutex_unlock(&bus->io_mutex);
+
+ return retval;
+}
+
+
+/**
+ * syna_hw_interface
+ *
+ * Provide the hardware specific settings in defaults.
+ * Be noted the followings could be changed after .dtsi is parsed
+ */
+static struct syna_hw_interface syna_i2c_hw_if = {
+ .bdata_io = {
+ .type = BUS_TYPE_I2C,
+ .rd_chunk_size = RD_CHUNK_SIZE,
+ .wr_chunk_size = WR_CHUNK_SIZE,
+ },
+ .bdata_attn = {
+ .irq_enabled = false,
+ .irq_on_state = 0,
+ },
+ .bdata_rst = {
+ .reset_on_state = 0,
+ .reset_delay_ms = 200,
+ .reset_active_ms = 20,
+ },
+ .bdata_pwr = {
+ .power_on_state = 1,
+ .power_on_delay_ms = 200,
+ },
+ .ops_power_on = syna_i2c_enable_regulator,
+ .ops_hw_reset = syna_i2c_hw_reset,
+ .ops_read_data = syna_i2c_read,
+ .ops_write_data = syna_i2c_write,
+ .ops_enable_irq = syna_i2c_enable_irq,
+};
+
+/**
+ * syna_i2c_probe()
+ *
+ * Prepare the specific hardware interface and register the platform i2c device
+ *
+ * @param
+ * [ in] i2c: i2c client device
+ * [ in] dev_id: i2c device id
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *dev_id)
+{
+ int retval;
+ struct syna_hw_attn_data *attn = &syna_i2c_hw_if.bdata_attn;
+ struct syna_hw_bus_data *bus = &syna_i2c_hw_if.bdata_io;
+
+ /* allocate an i2c platform device */
+ syna_i2c_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0);
+ if (!syna_i2c_device) {
+ LOGE("Fail to allocate platform device\n");
+ return _ENODEV;
+ }
+
+#ifdef CONFIG_OF
+ syna_i2c_parse_dt(&syna_i2c_hw_if, &i2c->dev);
+#endif
+
+ syna_pal_mutex_alloc(&attn->irq_en_mutex);
+ syna_pal_mutex_alloc(&bus->io_mutex);
+
+
+ /* keep the i/o device */
+ syna_i2c_hw_if.pdev = i2c;
+
+ syna_i2c_device->dev.parent = &i2c->dev;
+ syna_i2c_device->dev.platform_data = &syna_i2c_hw_if;
+
+ /* enable the regulators */
+ retval = syna_i2c_get_regulator(&syna_i2c_hw_if, true);
+ if (retval < 0)
+ return retval;
+
+ /* initialize the gpio pins */
+ retval = syna_i2c_config_gpio(&syna_i2c_hw_if);
+ if (retval < 0) {
+ LOGE("Fail to config gpio\n");
+ return retval;
+ }
+
+ /* register the i2c platform device */
+ retval = platform_device_add(syna_i2c_device);
+ if (retval < 0) {
+ LOGE("Fail to add platform device\n");
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_i2c_remove()
+ *
+ * Unregister the platform i2c device
+ *
+ * @param
+ * [ in] i2c: i2c client device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_i2c_remove(struct i2c_client *i2c)
+{
+ struct syna_hw_attn_data *attn = &syna_i2c_hw_if.bdata_attn;
+ struct syna_hw_pwr_data *pwr = &syna_i2c_hw_if.bdata_pwr;
+ struct syna_hw_rst_data *rst = &syna_i2c_hw_if.bdata_rst;
+ struct syna_hw_bus_data *bus = &syna_i2c_hw_if.bdata_io;
+
+ /* disable gpios */
+ if (pwr->avdd_gpio >= 0)
+ syna_i2c_request_gpio(pwr->avdd_gpio, false, 0, 0, NULL);
+ if (pwr->vdd_gpio >= 0)
+ syna_i2c_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL);
+ if (rst->reset_gpio >= 0)
+ syna_i2c_request_gpio(rst->reset_gpio, false, 0, 0, NULL);
+ if (attn->irq_gpio >= 0)
+ syna_i2c_request_gpio(attn->irq_gpio, false, 0, 0, NULL);
+
+ /* disable the regulators */
+ syna_i2c_get_regulator(&syna_i2c_hw_if, false);
+
+ syna_pal_mutex_free(&attn->irq_en_mutex);
+ syna_pal_mutex_free(&bus->io_mutex);
+
+ /* remove the platform device */
+ syna_i2c_device->dev.platform_data = NULL;
+ platform_device_unregister(syna_i2c_device);
+
+ return 0;
+}
+
+/**
+ * Describe an i2c device driver and its related declarations
+ */
+static const struct i2c_device_id syna_i2c_id_table[] = {
+ {I2C_MODULE_NAME, 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, syna_i2c_id_table);
+
+#ifdef CONFIG_OF
+static const struct of_device_id syna_i2c_of_match_table[] = {
+ {
+ .compatible = "synaptics,tcm-i2c",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, syna_i2c_of_match_table);
+#else
+#define syna_i2c_of_match_table NULL
+#endif
+
+static struct i2c_driver syna_i2c_driver = {
+ .driver = {
+ .name = I2C_MODULE_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = syna_i2c_of_match_table,
+ },
+ .probe = syna_i2c_probe,
+ .remove = syna_i2c_remove,
+ .id_table = syna_i2c_id_table,
+};
+
+
+/**
+ * syna_hw_interface_init()
+ *
+ * Initialize the lower-level hardware interface module.
+ * After returning, the handle of hw interface should be ready.
+ *
+ * @param
+ * void
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_hw_interface_init(void)
+{
+ return i2c_add_driver(&syna_i2c_driver);
+}
+
+/**
+ * syna_hw_interface_exit()
+ *
+ * Delete the lower-level hardware interface module
+ *
+ * @param
+ * void
+ *
+ * @return
+ * none.
+ */
+void syna_hw_interface_exit(void)
+{
+ i2c_del_driver(&syna_i2c_driver);
+}
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TouchCom I2C Bus Module");
+MODULE_LICENSE("GPL v2");
+
diff --git a/syna_tcm2_platform_spi.c b/syna_tcm2_platform_spi.c
new file mode 100644
index 0000000..e69a968
--- /dev/null
+++ b/syna_tcm2_platform_spi.c
@@ -0,0 +1,1058 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2_platform_spi.c
+ *
+ * This file is the reference code of I2C module used for communicating with
+ * Synaptics TouchCom device using I2C
+ */
+
+#include <linux/spi/spi.h>
+
+#include "syna_tcm2.h"
+#include "syna_tcm2_platform.h"
+
+#define SPI_MODULE_NAME "synaptics_tcm_spi"
+
+static unsigned char *buf;
+
+static unsigned int buf_size;
+
+static struct spi_transfer *xfer;
+
+static struct platform_device *syna_spi_device;
+
+
+/**
+ * syna_request_managed_device()
+ *
+ * Request and return the device pointer for managed
+ *
+ * @param
+ * none.
+ *
+ * @return
+ * a device pointer allocated previously
+ */
+#if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER)
+struct device *syna_request_managed_device(void)
+{
+ if (!syna_spi_device)
+ return NULL;
+
+ return syna_spi_device->dev.parent;
+}
+#endif
+
+/**
+ * syna_spi_hw_reset()
+ *
+ * Toggle the hardware gpio pin to perform the chip reset
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ *
+ * @return
+ * none.
+ */
+static void syna_spi_hw_reset(struct syna_hw_interface *hw_if)
+{
+ struct syna_hw_rst_data *rst = &hw_if->bdata_rst;
+
+ if (rst->reset_gpio >= 0) {
+ gpio_set_value(rst->reset_gpio, rst->reset_on_state);
+ syna_pal_sleep_ms(rst->reset_active_ms);
+ gpio_set_value(rst->reset_gpio, !rst->reset_on_state);
+ syna_pal_sleep_ms(rst->reset_delay_ms);
+ }
+}
+
+/**
+ * syna_spi_request_gpio()
+ *
+ * Setup the given gpio
+ *
+ * @param
+ * [ in] gpio: the target gpio
+ * [ in] config: '1' for setting up, and '0' to release the gpio
+ * [ in] dir: default direction of gpio
+ * [ in] state: default state of gpio
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_request_gpio(int gpio, bool config, int dir,
+ int state, char *label)
+{
+ int retval;
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ return -ENODEV;
+ }
+#endif
+
+ if (config) {
+ retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio);
+ if (retval < 0) {
+ LOGE("Fail to set GPIO label\n");
+ return retval;
+ }
+#ifdef DEV_MANAGED_API
+ retval = devm_gpio_request(dev, gpio, label);
+#else /* Legacy API */
+ retval = gpio_request(gpio, label);
+#endif
+ if (retval < 0) {
+ LOGE("Fail 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("Fail to set GPIO %d direction\n", gpio);
+ return retval;
+ }
+ } else {
+#ifdef DEV_MANAGED_API
+ devm_gpio_free(dev, gpio);
+#else /* Legacy API */
+ gpio_free(gpio);
+#endif
+ }
+
+ return 0;
+}
+
+/**
+ * syna_spi_config_gpio()
+ *
+ * Initialize the GPIOs defined in device tree
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_config_gpio(struct syna_hw_interface *hw_if)
+{
+ int retval;
+ static char str_irq_gpio[32] = {0};
+ static char str_rst_gpio[32] = {0};
+ static char str_vdd_gpio[32] = {0};
+ static char str_avdd_gpio[32] = {0};
+ struct syna_hw_attn_data *attn = &hw_if->bdata_attn;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+ struct syna_hw_rst_data *rst = &hw_if->bdata_rst;
+
+ if (attn->irq_gpio >= 0) {
+ retval = syna_spi_request_gpio(attn->irq_gpio,
+ true, 0, 0, str_irq_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure interrupt GPIO %d\n",
+ attn->irq_gpio);
+ goto err_set_gpio_irq;
+ }
+ }
+
+ if (rst->reset_gpio >= 0) {
+ retval = syna_spi_request_gpio(rst->reset_gpio,
+ true, 1, !rst->reset_on_state,
+ str_rst_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure reset GPIO %d\n",
+ rst->reset_gpio);
+ goto err_set_gpio_reset;
+ }
+ }
+
+ if (pwr->vdd_gpio >= 0) {
+ retval = syna_spi_request_gpio(pwr->vdd_gpio,
+ true, 1, !pwr->power_on_state,
+ str_vdd_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure vdd GPIO %d\n",
+ pwr->vdd_gpio);
+ goto err_set_gpio_vdd;
+ }
+ }
+
+ if (pwr->avdd_gpio >= 0) {
+ retval = syna_spi_request_gpio(pwr->avdd_gpio,
+ true, 1, !pwr->power_on_state,
+ str_avdd_gpio);
+ if (retval < 0) {
+ LOGE("Fail to configure avdd GPIO %d\n",
+ pwr->avdd_gpio);
+ goto err_set_gpio_avdd;
+ }
+ }
+ return 0;
+
+err_set_gpio_avdd:
+ if (pwr->vdd_gpio >= 0)
+ syna_spi_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL);
+err_set_gpio_vdd:
+ if (rst->reset_gpio >= 0)
+ syna_spi_request_gpio(rst->reset_gpio, false, 0, 0, NULL);
+err_set_gpio_reset:
+ if (attn->irq_gpio >= 0)
+ syna_spi_request_gpio(attn->irq_gpio, false, 0, 0, NULL);
+err_set_gpio_irq:
+ return retval;
+}
+
+/**
+ * syna_spi_enable_regulator()
+ *
+ * Enable or disable the regulator
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] en: '1' for enabling, and '0' for disabling
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_enable_regulator(struct syna_hw_interface *hw_if,
+ bool en)
+{
+ int retval;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+ struct regulator *vdd_reg = pwr->vdd_reg_dev;
+ struct regulator *avdd_reg = pwr->avdd_reg_dev;
+
+ if (!en) {
+ retval = 0;
+ goto disable_pwr_reg;
+ }
+
+ if (vdd_reg) {
+ retval = regulator_enable(vdd_reg);
+ if (retval < 0) {
+ LOGE("Fail to enable vdd regulator\n");
+ goto exit;
+ }
+ }
+
+ if (avdd_reg) {
+ retval = regulator_enable(avdd_reg);
+ if (retval < 0) {
+ LOGE("Fail to enable avdd regulator\n");
+ goto disable_avdd_reg;
+ }
+ syna_pal_sleep_ms(pwr->power_on_delay_ms);
+ }
+
+ return 0;
+
+disable_pwr_reg:
+ if (vdd_reg)
+ regulator_disable(vdd_reg);
+
+disable_avdd_reg:
+ if (avdd_reg)
+ regulator_disable(avdd_reg);
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_spi_get_regulator()
+ *
+ * Acquire or release the regulator
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] get: '1' for getting the regulator, and '0' for removing
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_get_regulator(struct syna_hw_interface *hw_if,
+ bool get)
+{
+ int retval;
+ struct device *dev = syna_spi_device->dev.parent;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+
+ if (!get) {
+ retval = 0;
+ goto regulator_put;
+ }
+
+ if (pwr->vdd_reg_name != NULL && *pwr->vdd_reg_name != 0) {
+#ifdef DEV_MANAGED_API
+ pwr->vdd_reg_dev = devm_regulator_get(dev, pwr->vdd_reg_name);
+#else /* Legacy API */
+ pwr->vdd_reg_dev = regulator_get(dev, pwr->vdd_reg_name);
+#endif
+ if (IS_ERR((struct regulator *)pwr->vdd_reg_dev)) {
+ LOGW("Vdd regulator is not ready\n");
+ retval = PTR_ERR((struct regulator *)pwr->vdd_reg_dev);
+ goto exit;
+ }
+ }
+
+ if (pwr->avdd_reg_name != NULL && *pwr->avdd_reg_name != 0) {
+#ifdef DEV_MANAGED_API
+ pwr->avdd_reg_dev = devm_regulator_get(dev, pwr->avdd_reg_name);
+#else /* Legacy API */
+ pwr->avdd_reg_dev = regulator_get(dev, pwr->avdd_reg_name);
+#endif
+ if (IS_ERR((struct regulator *)pwr->avdd_reg_dev)) {
+ LOGW("AVdd regulator is not ready\n");
+ retval = PTR_ERR((struct regulator *)pwr->avdd_reg_dev);
+ goto regulator_vdd_put;
+ }
+ }
+
+ return 0;
+
+regulator_put:
+ if (pwr->vdd_reg_dev) {
+#ifdef DEV_MANAGED_API
+ devm_regulator_put(pwr->vdd_reg_dev);
+#else /* Legacy API */
+ regulator_put(pwr->vdd_reg_dev);
+#endif
+ pwr->vdd_reg_dev = NULL;
+ }
+regulator_vdd_put:
+ if (pwr->avdd_reg_dev) {
+#ifdef DEV_MANAGED_API
+ devm_regulator_put(pwr->avdd_reg_dev);
+#else /* Legacy API */
+ regulator_put(pwr->avdd_reg_dev);
+#endif
+ pwr->avdd_reg_dev = NULL;
+ }
+exit:
+ return retval;
+}
+
+/**
+ * syna_spi_enable_irq()
+ *
+ * Enable or disable the handling of interrupt
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] en: '1' for enabling, and '0' for disabling
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+static int syna_spi_enable_irq(struct syna_hw_interface *hw_if,
+ bool en)
+{
+ int retval = 0;
+ struct syna_hw_attn_data *attn = &hw_if->bdata_attn;
+
+ if (attn->irq_id == 0)
+ return 0;
+
+ syna_pal_mutex_lock(&attn->irq_en_mutex);
+
+ /* enable the handling of interrupt */
+ if (en) {
+ if (attn->irq_enabled) {
+ LOGI("Interrupt already enabled\n");
+ retval = 0;
+ goto exit;
+ }
+
+ enable_irq(attn->irq_id);
+ attn->irq_enabled = true;
+
+ LOGD("irq enabled\n");
+ }
+ /* disable the handling of interrupt */
+ else {
+ if (!attn->irq_enabled) {
+ LOGI("Interrupt already disabled\n");
+ retval = 0;
+ goto exit;
+ }
+
+ disable_irq_nosync(attn->irq_id);
+ attn->irq_enabled = false;
+
+ LOGD("irq disabled\n");
+ }
+
+exit:
+ syna_pal_mutex_unlock(&attn->irq_en_mutex);
+
+ return retval;
+}
+
+
+/**
+ * syna_spi_parse_dt()
+ *
+ * Parse and obtain board specific data from the device tree source file.
+ * Keep the data in structure syna_tcm_hw_data for later using.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] dev: device model
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+#ifdef CONFIG_OF
+static int syna_spi_parse_dt(struct syna_hw_interface *hw_if,
+ struct device *dev)
+{
+ int retval;
+ u32 value;
+ struct property *prop;
+ struct device_node *np = dev->of_node;
+ const char *name;
+ struct syna_hw_attn_data *attn = &hw_if->bdata_attn;
+ struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr;
+ struct syna_hw_rst_data *rst = &hw_if->bdata_rst;
+ struct syna_hw_bus_data *bus = &hw_if->bdata_io;
+
+ prop = of_find_property(np, "synaptics,irq-gpio", NULL);
+ if (prop && prop->length) {
+ attn->irq_gpio = of_get_named_gpio_flags(np,
+ "synaptics,irq-gpio", 0,
+ (enum of_gpio_flags *)&attn->irq_flags);
+ } else {
+ attn->irq_gpio = -1;
+ }
+
+ retval = of_property_read_u32(np, "synaptics,irq-on-state", &value);
+ if (retval < 0)
+ attn->irq_on_state = 0;
+ else
+ attn->irq_on_state = value;
+
+ retval = of_property_read_string(np, "synaptics,avdd-name", &name);
+ if (retval < 0)
+ pwr->avdd_reg_name = NULL;
+ else
+ pwr->avdd_reg_name = name;
+
+ retval = of_property_read_string(np, "synaptics,vdd-name", &name);
+ if (retval < 0)
+ pwr->vdd_reg_name = NULL;
+ else
+ pwr->vdd_reg_name = name;
+
+ prop = of_find_property(np, "synaptics,vdd-gpio", NULL);
+ if (prop && prop->length) {
+ pwr->vdd_gpio = of_get_named_gpio_flags(np,
+ "synaptics,vdd-gpio", 0, NULL);
+ } else {
+ pwr->vdd_gpio = -1;
+ }
+
+ prop = of_find_property(np, "synaptics,avdd-gpio", NULL);
+ if (prop && prop->length) {
+ pwr->avdd_gpio = of_get_named_gpio_flags(np,
+ "synaptics,avdd-gpio", 0, NULL);
+ } else {
+ pwr->avdd_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("Fail to read power-on-state property\n");
+ return retval;
+ }
+
+ pwr->power_on_state = value;
+
+ } else {
+ pwr->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("Fail to read power-delay-ms property\n");
+ return retval;
+ }
+
+ pwr->power_on_delay_ms = value;
+
+ } else {
+ pwr->power_on_delay_ms = 0;
+ }
+
+ prop = of_find_property(np, "synaptics,reset-gpio", NULL);
+ if (prop && prop->length) {
+ rst->reset_gpio = of_get_named_gpio_flags(np,
+ "synaptics,reset-gpio", 0, NULL);
+ } else {
+ rst->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("Fail to read reset-on-state property\n");
+ return retval;
+ }
+
+ rst->reset_on_state = value;
+
+ } else {
+ rst->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("Fail to read reset-active-ms property\n");
+ return retval;
+ }
+
+ rst->reset_active_ms = value;
+
+ } else {
+ rst->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("Fail to read reset-delay-ms property\n");
+ return retval;
+ }
+
+ rst->reset_delay_ms = value;
+
+ } else {
+ rst->reset_delay_ms = 0;
+ }
+
+ prop = of_find_property(np, "synaptics,spi-byte-delay-us", NULL);
+ if (prop && prop->length) {
+ retval = of_property_read_u32(np,
+ "synaptics,spi-byte-delay-us", &value);
+ if (retval < 0) {
+ LOGE("Fail to read byte-delay-us property\n");
+ return retval;
+ }
+
+ bus->spi_byte_delay_us = value;
+
+ } else {
+ bus->spi_byte_delay_us = 0;
+ }
+
+ prop = of_find_property(np, "synaptics,spi-block-delay-us", NULL);
+ if (prop && prop->length) {
+ retval = of_property_read_u32(np,
+ "synaptics,spi-block-delay-us", &value);
+ if (retval < 0) {
+ LOGE("Fail to read block-delay-us property\n");
+ return retval;
+ }
+ bus->spi_block_delay_us = value;
+
+ } else {
+ bus->spi_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("Fail to read synaptics,spi-mode property\n");
+ return retval;
+ }
+
+ bus->spi_mode = value;
+
+ } else {
+ bus->spi_mode = 0;
+ }
+
+
+ return 0;
+}
+#endif
+
+/**
+ * syna_tcm_spi_alloc_mem()
+ *
+ * Manage and allocate the memory to buf being as a temporary buffer for IO
+ *
+ * @param
+ * [ in] count: number of spi_transfer structures to send
+ * [ in] size: size of temporary buffer
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_alloc_mem(unsigned int count, unsigned int size)
+{
+ static unsigned int xfer_count;
+
+ if (count > xfer_count) {
+ syna_pal_mem_free((void *)xfer);
+ xfer = syna_pal_mem_alloc(count, sizeof(*xfer));
+ if (!xfer) {
+ LOGE("Fail to allocate memory for xfer\n");
+ xfer_count = 0;
+ return -ENOMEM;
+ }
+ xfer_count = count;
+ } else {
+ syna_pal_mem_set(xfer, 0, count * sizeof(*xfer));
+ }
+
+ if (size > buf_size) {
+ if (buf_size)
+ syna_pal_mem_free((void *)buf);
+ buf = syna_pal_mem_alloc(size, sizeof(unsigned char));
+ if (!buf) {
+ LOGE("Fail to allocate memory for buf\n");
+ buf_size = 0;
+ return -ENOMEM;
+ }
+ buf_size = size;
+ }
+
+ return 0;
+}
+
+
+/**
+ * syna_spi_read()
+ *
+ * TouchCom over SPI requires the host to assert the SSB signal to address
+ * the device and retrieve the data.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [out] rd_data: buffer for storing data retrieved from device
+ * [ in] rd_len: number of bytes retrieved from device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_read(struct syna_hw_interface *hw_if,
+ unsigned char *rd_data, unsigned int rd_len)
+{
+ int retval;
+ unsigned int idx;
+ struct spi_message msg;
+ struct spi_device *spi = hw_if->pdev;
+ struct syna_hw_bus_data *bus = &hw_if->bdata_io;
+
+ if (!spi) {
+ LOGE("Invalid bus io device\n");
+ return -EINVAL;
+ }
+
+ syna_pal_mutex_lock(&bus->io_mutex);
+
+ spi_message_init(&msg);
+
+ if (bus->spi_byte_delay_us == 0)
+ retval = syna_spi_alloc_mem(1, rd_len);
+ else
+ retval = syna_spi_alloc_mem(rd_len, 1);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory\n");
+ goto exit;
+ }
+
+ if (bus->spi_byte_delay_us == 0) {
+ syna_pal_mem_set(buf, 0xff, rd_len);
+ xfer[0].len = rd_len;
+ xfer[0].tx_buf = buf;
+ xfer[0].rx_buf = rd_data;
+ if (bus->spi_block_delay_us)
+ xfer[0].delay_usecs = bus->spi_block_delay_us;
+ spi_message_add_tail(&xfer[0], &msg);
+ } else {
+ buf[0] = 0xff;
+ for (idx = 0; idx < rd_len; idx++) {
+ xfer[idx].len = 1;
+ xfer[idx].tx_buf = buf;
+ xfer[idx].rx_buf = &rd_data[idx];
+ xfer[idx].delay_usecs = bus->spi_byte_delay_us;
+ if (bus->spi_block_delay_us && (idx == rd_len - 1))
+ xfer[idx].delay_usecs = bus->spi_block_delay_us;
+ spi_message_add_tail(&xfer[idx], &msg);
+ }
+ }
+
+ retval = spi_sync(spi, &msg);
+ if (retval != 0) {
+ LOGE("Failed to complete SPI transfer, error = %d\n", retval);
+ goto exit;
+ }
+
+ retval = rd_len;
+
+exit:
+ syna_pal_mutex_unlock(&bus->io_mutex);
+
+ return retval;
+}
+
+/**
+ * syna_spi_write()
+ *
+ * TouchCom over SPI requires the host to assert the SSB signal to address
+ * the device and send the data to the device.
+ *
+ * @param
+ * [ in] hw_if: the handle of hw interface
+ * [ in] wr_data: written data
+ * [ in] wr_len: length of written data in bytes
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_write(struct syna_hw_interface *hw_if,
+ unsigned char *wr_data, unsigned int wr_len)
+{
+ int retval;
+ unsigned int idx;
+ struct spi_message msg;
+ struct spi_device *spi = hw_if->pdev;
+ struct syna_hw_bus_data *bus = &hw_if->bdata_io;
+
+ if (!spi) {
+ LOGE("Invalid bus io device\n");
+ return -EINVAL;
+ }
+
+ syna_pal_mutex_lock(&bus->io_mutex);
+
+ spi_message_init(&msg);
+
+ if (bus->spi_byte_delay_us == 0)
+ retval = syna_spi_alloc_mem(1, 0);
+ else
+ retval = syna_spi_alloc_mem(wr_len, 0);
+ if (retval < 0) {
+ LOGE("Failed to allocate memory\n");
+ goto exit;
+ }
+
+ if (bus->spi_byte_delay_us == 0) {
+ xfer[0].len = wr_len;
+ xfer[0].tx_buf = wr_data;
+ if (bus->spi_block_delay_us)
+ xfer[0].delay_usecs = bus->spi_block_delay_us;
+ spi_message_add_tail(&xfer[0], &msg);
+ } else {
+ for (idx = 0; idx < wr_len; idx++) {
+ xfer[idx].len = 1;
+ xfer[idx].tx_buf = &wr_data[idx];
+ xfer[idx].delay_usecs = bus->spi_byte_delay_us;
+ if (bus->spi_block_delay_us && (idx == wr_len - 1))
+ xfer[idx].delay_usecs = bus->spi_block_delay_us;
+ spi_message_add_tail(&xfer[idx], &msg);
+ }
+ }
+
+ retval = spi_sync(spi, &msg);
+ if (retval != 0) {
+ LOGE("Fail to complete SPI transfer, error = %d\n", retval);
+ goto exit;
+ }
+
+ retval = wr_len;
+
+exit:
+ syna_pal_mutex_unlock(&bus->io_mutex);
+
+ return retval;
+}
+
+
+/**
+ * syna_hw_interface
+ *
+ * Provide the hardware specific settings in defaults.
+ * Be noted the followings could be changed after .dtsi is parsed
+ */
+static struct syna_hw_interface syna_spi_hw_if = {
+ .bdata_io = {
+ .type = BUS_TYPE_SPI,
+ .rd_chunk_size = RD_CHUNK_SIZE,
+ .wr_chunk_size = WR_CHUNK_SIZE,
+ },
+ .bdata_attn = {
+ .irq_enabled = false,
+ .irq_on_state = 0,
+ },
+ .bdata_rst = {
+ .reset_on_state = 0,
+ .reset_delay_ms = 200,
+ .reset_active_ms = 20,
+ },
+ .bdata_pwr = {
+ .power_on_state = 1,
+ .power_on_delay_ms = 200,
+ },
+ .ops_power_on = syna_spi_enable_regulator,
+ .ops_hw_reset = syna_spi_hw_reset,
+ .ops_read_data = syna_spi_read,
+ .ops_write_data = syna_spi_write,
+ .ops_enable_irq = syna_spi_enable_irq,
+};
+
+/**
+ * syna_spi_probe()
+ *
+ * Prepare the specific hardware interface and register the platform spi device
+ *
+ * @param
+ * [ in] spi: spi device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_probe(struct spi_device *spi)
+{
+ int retval;
+ struct syna_hw_attn_data *attn = &syna_spi_hw_if.bdata_attn;
+ struct syna_hw_bus_data *bus = &syna_spi_hw_if.bdata_io;
+
+ if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
+ LOGE("Full duplex not supported by host\n");
+ return -EIO;
+ }
+
+ /* allocate an spi platform device */
+ syna_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0);
+ if (!syna_spi_device) {
+ LOGE("Fail to allocate platform device\n");
+ return _ENODEV;
+ }
+
+#ifdef CONFIG_OF
+ syna_spi_parse_dt(&syna_spi_hw_if, &spi->dev);
+#endif
+
+ syna_pal_mutex_alloc(&attn->irq_en_mutex);
+ syna_pal_mutex_alloc(&bus->io_mutex);
+
+ switch (bus->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;
+ }
+
+ /* keep the i/o device */
+ syna_spi_hw_if.pdev = spi;
+
+ syna_spi_device->dev.parent = &spi->dev;
+ syna_spi_device->dev.platform_data = &syna_spi_hw_if;
+
+ spi->bits_per_word = 8;
+
+ /* set up spi driver */
+ retval = spi_setup(spi);
+ if (retval < 0) {
+ LOGE("Fail to set up SPI protocol driver\n");
+ return retval;
+ }
+
+ /* enable the regulators */
+ retval = syna_spi_get_regulator(&syna_spi_hw_if, true);
+ if (retval < 0)
+ return retval;
+
+ /* initialize the gpio pins */
+ retval = syna_spi_config_gpio(&syna_spi_hw_if);
+ if (retval < 0) {
+ LOGE("Fail to config gpio\n");
+ return retval;
+ }
+
+ /* register the spi platform device */
+ retval = platform_device_add(syna_spi_device);
+ if (retval < 0) {
+ LOGE("Fail to add platform device\n");
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_spi_remove()
+ *
+ * Unregister the platform spi device
+ *
+ * @param
+ * [ in] spi: spi device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_spi_remove(struct spi_device *spi)
+{
+ struct syna_hw_attn_data *attn = &syna_spi_hw_if.bdata_attn;
+ struct syna_hw_pwr_data *pwr = &syna_spi_hw_if.bdata_pwr;
+ struct syna_hw_rst_data *rst = &syna_spi_hw_if.bdata_rst;
+ struct syna_hw_bus_data *bus = &syna_spi_hw_if.bdata_io;
+
+ /* disable gpios */
+ if (pwr->avdd_gpio >= 0)
+ syna_spi_request_gpio(pwr->avdd_gpio, false, 0, 0, NULL);
+ if (pwr->vdd_gpio >= 0)
+ syna_spi_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL);
+ if (rst->reset_gpio >= 0)
+ syna_spi_request_gpio(rst->reset_gpio, false, 0, 0, NULL);
+ if (attn->irq_gpio >= 0)
+ syna_spi_request_gpio(attn->irq_gpio, false, 0, 0, NULL);
+
+ /* disable the regulators */
+ syna_spi_get_regulator(&syna_spi_hw_if, false);
+
+ syna_pal_mutex_free(&attn->irq_en_mutex);
+ syna_pal_mutex_free(&bus->io_mutex);
+
+ /* remove the platform device */
+ syna_spi_device->dev.platform_data = NULL;
+ platform_device_unregister(syna_spi_device);
+
+ return 0;
+}
+
+/**
+ * Describe an spi device driver and its related declarations
+ */
+static const struct spi_device_id syna_spi_id_table[] = {
+ {SPI_MODULE_NAME, 0},
+ {},
+};
+MODULE_DEVICE_TABLE(spi, syna_spi_id_table);
+
+#ifdef CONFIG_OF
+static const struct of_device_id syna_spi_of_match_table[] = {
+ {
+ .compatible = "synaptics,tcm-spi",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, syna_spi_of_match_table);
+#else
+#define syna_spi_of_match_table NULL
+#endif
+
+static struct spi_driver syna_spi_driver = {
+ .driver = {
+ .name = SPI_MODULE_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = syna_spi_of_match_table,
+ },
+ .probe = syna_spi_probe,
+ .remove = syna_spi_remove,
+ .id_table = syna_spi_id_table,
+};
+
+
+/**
+ * syna_hw_interface_init()
+ *
+ * Initialize the lower-level hardware interface module.
+ * After returning, the handle of hw interface should be ready.
+ *
+ * @param
+ * void
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_hw_interface_init(void)
+{
+ return spi_register_driver(&syna_spi_driver);
+}
+
+/**
+ * syna_hw_interface_exit()
+ *
+ * Delete the lower-level hardware interface module
+ *
+ * @param
+ * void
+ *
+ * @return
+ * none.
+ */
+void syna_hw_interface_exit(void)
+{
+ syna_pal_mem_free((void *)buf);
+
+ syna_pal_mem_free((void *)xfer);
+
+ spi_unregister_driver(&syna_spi_driver);
+}
+
+MODULE_AUTHOR("Synaptics, Inc.");
+MODULE_DESCRIPTION("Synaptics TouchCom SPI Bus Module");
+MODULE_LICENSE("GPL v2");
+
diff --git a/syna_tcm2_runtime.h b/syna_tcm2_runtime.h
new file mode 100644
index 0000000..b5ef979
--- /dev/null
+++ b/syna_tcm2_runtime.h
@@ -0,0 +1,753 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2_runtime.h
+ *
+ * This file abstracts platform-specific headers and C runtime APIs being used
+ * on the target platform.
+ */
+
+#ifndef _SYNAPTICS_TCM2_C_RUNTIME_H_
+#define _SYNAPTICS_TCM2_C_RUNTIME_H_
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/input/mt.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/crc32.h>
+#include <linux/firmware.h>
+#ifdef CONFIG_DRM_PANEL
+#include <drm/drm_panel.h>
+#elif CONFIG_FB
+#include <linux/fb.h>
+#include <linux/notifier.h>
+#endif
+#include <linux/fs.h>
+#include <linux/moduleparam.h>
+
+/**
+ * @brief: DEV_MANAGED_API
+ *
+ * For linux kernel, managed interface was created for resources commonly
+ * used by device drivers using devres.
+ *
+ * Open if willing to use managed-APIs rather than legacy APIs.
+ */
+#define DEV_MANAGED_API
+
+#if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER)
+extern struct device *syna_request_managed_device(void);
+#endif
+
+/**
+ * @section: Log helpers
+ *
+ * @brief: LOGD
+ * Output the debug message
+ *
+ * @brief: LOGI
+ * Output the info message
+ *
+ * @brief: LOGN
+ * Output the notice message
+ *
+ * @brief: LOGW
+ * Output the warning message
+ *
+ * @brief: LOGE
+ * Output the error message
+ */
+#define LOGD(log, ...) \
+ pr_debug("[ debug] %s: " log, __func__, ##__VA_ARGS__)
+#define LOGI(log, ...) \
+ pr_info("[ info] %s: " log, __func__, ##__VA_ARGS__)
+#define LOGN(log, ...) \
+ pr_notice("[ info] %s: " log, __func__, ##__VA_ARGS__)
+#define LOGW(log, ...) \
+ pr_warn("[warning] %s: " log, __func__, ##__VA_ARGS__)
+#define LOGE(log, ...) \
+ pr_err("[ error] %s: " log, __func__, ##__VA_ARGS__)
+
+
+/**
+ * @section: Error Codes returned
+ * Functions usually return 0 or positive value on success.
+ * Thus, please defines negative value here.
+ */
+#define _EIO (-EIO) /* I/O errors */
+#define _ENOMEM (-ENOMEM) /* Out of memory */
+#define _EINVAL (-EINVAL) /* Invalid parameters */
+#define _ENODEV (-ENODEV) /* No such device */
+#define _ETIMEDOUT (-ETIMEDOUT) /* execution timeout */
+
+
+/**
+ * @section: Data Comparison helpers
+ *
+ * @brief: MAX
+ * Find the maximum value between
+ *
+ * @brief: MIN:
+ * Find the minimum value between
+ *
+ * @brief: GET_BIT
+ * Return the value of target bit
+ */
+#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 GET_BIT(var, pos) \
+ (((var) & (1 << (pos))) >> (pos))
+
+
+/**
+ * @section: C Atomic operations
+ *
+ * @brief: ATOMIC_SET
+ * Set an atomic data
+ *
+ * @brief: ATOMIC_GET:
+ * Get an atomic data
+ */
+typedef atomic_t syna_pal_atomic_t;
+
+#define ATOMIC_SET(atomic, value) \
+ atomic_set(&atomic, value)
+
+#define ATOMIC_GET(atomic) \
+ atomic_read(&atomic)
+
+
+/**
+ * @section: C Integer Calculation helpers
+ *
+ * @brief: syna_pal_le2_to_uint
+ * Convert 2-byte data to an unsigned integer
+ *
+ * @brief: syna_pal_le4_to_uint
+ * Convert 4-byte data to an unsigned integer
+ *
+ * @brief: syna_pal_ceil_div
+ * Calculate the ceiling of the integer division
+ */
+
+/**
+ * syna_pal_le2_to_uint()
+ *
+ * Convert 2-byte data in little-endianness to an unsigned integer
+ *
+ * @param
+ * [ in] src: 2-byte data in little-endianness
+ *
+ * @return
+ * an unsigned integer converted
+ */
+static inline unsigned int syna_pal_le2_to_uint(const unsigned char *src)
+{
+ return (unsigned int)src[0] +
+ (unsigned int)src[1] * 0x100;
+}
+/**
+ * syna_pal_le4_to_uint()
+ *
+ * Convert 4-byte data in little-endianness to an unsigned integer
+ *
+ * @param
+ * [ in] src: 4-byte data in little-endianness
+ *
+ * @return
+ * an unsigned integer converted
+ */
+static inline unsigned int syna_pal_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;
+}
+/**
+ * syna_pal_ceil_div()
+ *
+ * Calculate the ceiling of the integer division
+ *
+ * @param
+ * [ in] dividend: the dividend value
+ * [ in] divisor: the divisor value
+ *
+ * @return
+ * the ceiling of the integer division
+ */
+static inline unsigned int syna_pal_ceil_div(unsigned int dividend,
+ unsigned int divisor)
+{
+ return (dividend + divisor - 1) / divisor;
+}
+
+
+/**
+ * @section: C Runtime for Memory Management helpers
+ *
+ * @brief: syna_pal_mem_calloc
+ * Allocate a block of memory space
+ *
+ * @brief: syna_pal_mem_free
+ * Deallocate a block of memory previously allocated
+ *
+ * @brief: syna_pal_mem_set
+ * Fill memory with a constant byte
+ *
+ * @brief: syna_pal_mem_cpy
+ * Ensure the safe size before doing memory copy
+ */
+
+/**
+ * syna_pal_mem_calloc()
+ *
+ * Allocates a block of memory for an array of 'num' elements,
+ * each of them has 'size' bytes long, and initializes all its bits to zero.
+ *
+ * @param
+ * [ in] num: number of elements for an array
+ * [ in] size: number of bytes for each elements
+ *
+ * @return
+ * On success, a pointer to the memory block allocated by the function.
+ */
+static inline void *syna_pal_mem_alloc(unsigned int num, unsigned int size)
+{
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ return NULL;
+ }
+#endif
+
+ if ((int)(num * size) <= 0) {
+ LOGE("Invalid parameter\n");
+ return NULL;
+ }
+
+#ifdef DEV_MANAGED_API
+ return devm_kcalloc(dev, num, size, GFP_KERNEL);
+#else /* Legacy API */
+ return kcalloc(num, size, GFP_KERNEL);
+#endif
+}
+/**
+ * syna_pal_mem_free()
+ *
+ * Deallocate a block of memory previously allocated.
+ *
+ * @param
+ * [ in] ptr: a memory block previously allocated
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_mem_free(void *ptr)
+{
+#ifdef DEV_MANAGED_API
+ struct device *dev = syna_request_managed_device();
+
+ if (!dev) {
+ LOGE("Invalid managed device\n");
+ return;
+ }
+
+ if (ptr)
+ devm_kfree(dev, ptr);
+#else /* Legacy API */
+ kfree(ptr);
+#endif
+}
+/**
+ * syna_pal_mem_set()
+ *
+ * Fill memory with a constant byte
+ *
+ * @param
+ * [ in] ptr: pointer to a memory block
+ * [ in] c: the constant value
+ * [ in] n: number of byte being set
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_mem_set(void *ptr, int c, unsigned int n)
+{
+ memset(ptr, c, n);
+}
+/**
+ * syna_pal_mem_cpy()
+ *
+ * Ensure the safe size before copying the values of num bytes from the
+ * location to the memory block pointed to by destination.
+ *
+ * @param
+ * [out] dest: pointer to the destination space
+ * [ in] dest_size: size of destination array
+ * [ in] src: pointer to the source of data to be copied
+ * [ in] src_size: size of source array
+ * [ in] num: number of bytes to copy
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+static inline int syna_pal_mem_cpy(void *dest, unsigned int dest_size,
+ const void *src, unsigned int src_size, unsigned int num)
+{
+ if (dest == NULL || src == NULL)
+ return -1;
+
+ if (num > dest_size || num > src_size) {
+ LOGE("Invalid size. src:%d, dest:%d, num:%d\n",
+ src_size, dest_size, num);
+ return -1;
+ }
+
+ memcpy((void *)dest, (const void *)src, num);
+
+ return 0;
+}
+
+
+/**
+ * @section: C Runtime for Muxtex Control helpers
+ *
+ * @brief: syna_pal_mutex_alloc
+ * Create a mutex object
+ *
+ * @brief: syna_pal_mutex_free
+ * Release the mutex object previously allocated
+ *
+ * @brief: syna_pal_mutex_lock
+ * Lock the mutex
+ *
+ * @brief: syna_pal_mutex_unlock
+ * Unlock the mutex previously locked
+ */
+
+typedef struct mutex syna_pal_mutex_t;
+
+/**
+ * syna_pal_mutex_alloc()
+ *
+ * Create a mutex object.
+ *
+ * @param
+ * [out] ptr: pointer to the mutex handle being allocated
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+static inline int syna_pal_mutex_alloc(syna_pal_mutex_t *ptr)
+{
+ mutex_init((struct mutex *)ptr);
+ return 0;
+}
+/**
+ * syna_pal_mutex_free()
+ *
+ * Release the mutex object previously allocated.
+ *
+ * @param
+ * [ in] ptr: mutex handle previously allocated
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_mutex_free(syna_pal_mutex_t *ptr)
+{
+ /* do nothing */
+}
+/**
+ * syna_pal_mutex_lock()
+ *
+ * Acquire/lock the mutex.
+ *
+ * @param
+ * [ in] ptr: a mutex handle
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_mutex_lock(syna_pal_mutex_t *ptr)
+{
+ mutex_lock((struct mutex *)ptr);
+}
+/**
+ * syna_pal_mutex_unlock()
+ *
+ * Unlock the locked mutex.
+ *
+ * @param
+ * [ in] ptr: a mutex handle
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_mutex_unlock(syna_pal_mutex_t *ptr)
+{
+ mutex_unlock((struct mutex *)ptr);
+}
+
+
+/**
+ * @section: C Runtime for Completion Event
+ *
+ * @brief: syna_pal_completion_alloc
+ * Allocate a completion event
+ *
+ * @brief: syna_pal_completion_free
+ * Release the completion event previously allocated
+ *
+ * @brief: syna_pal_completion_complete
+ * Complete the completion event being waiting for
+ *
+ * @brief: syna_pal_completion_reset
+ * Reset or reinitialize the completion event
+ *
+ * @brief: syna_pal_completion_wait_for
+ * Wait for the completion event
+ */
+
+typedef struct completion syna_pal_completion_t;
+
+/**
+ * syna_pal_completion_alloc()
+ *
+ * Allocate a completion event, and the default state is not set.
+ * Caller must reset the event before each use.
+ *
+ * @param
+ * [out] ptr: pointer to the completion handle being allocated
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+static inline int syna_pal_completion_alloc(syna_pal_completion_t *ptr)
+{
+ init_completion((struct completion *)ptr);
+ return 0;
+}
+/**
+ * syna_pal_completion_free()
+ *
+ * Release the completion event previously allocated
+ *
+ * @param
+ * [ in] ptr: the completion event previously allocated
+ event
+ * @return
+ * none.
+ */
+static inline void syna_pal_completion_free(syna_pal_completion_t *ptr)
+{
+ /* do nothing */
+}
+/**
+ * syna_pal_completion_complete()
+ *
+ * Complete the completion event being waiting for
+ *
+ * @param
+ * [ in] ptr: the completion event
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_completion_complete(syna_pal_completion_t *ptr)
+{
+ complete((struct completion *)ptr);
+}
+/**
+ * syna_pal_completion_reset()
+ *
+ * Reset or reinitialize the completion event
+ *
+ * @param
+ * [ in] ptr: the completion event
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_completion_reset(syna_pal_completion_t *ptr)
+{
+#if (KERNEL_VERSION(3, 13, 0) > LINUX_VERSION_CODE)
+ init_completion((struct completion *)ptr);
+#else
+ reinit_completion((struct completion *)ptr);
+#endif
+}
+/**
+ * syna_pal_completion_wait_for()
+ *
+ * Wait for the completion event during the given time slot
+ *
+ * @param
+ * [ in] ptr: the completion event
+ * [ in] timeout_ms: time frame in milliseconds
+ *
+ * @return
+ * 0 if a signal is received; otherwise, on timeout or error occurs.
+ */
+static inline int syna_pal_completion_wait_for(syna_pal_completion_t *ptr,
+ unsigned int timeout_ms)
+{
+ int retval;
+
+ retval = wait_for_completion_timeout((struct completion *)ptr,
+ msecs_to_jiffies(timeout_ms));
+ if (retval == 0) /* timeout occurs */
+ return -1;
+
+ return 0;
+}
+
+
+/**
+ * @section: C Runtime to Pause the Execution
+ *
+ * @brief: syna_pal_sleep_ms
+ * Sleep for a fixed amount of time in milliseconds
+ *
+ * @brief: syna_pal_sleep_us
+ * Sleep for a range of time in microseconds
+ *
+ * @brief: syna_pal_busy_delay_ms
+ * Busy wait for a fixed amount of time in milliseconds
+ */
+
+/**
+ * syna_pal_sleep_ms()
+ *
+ * Sleep for a fixed amount of time in milliseconds
+ *
+ * @param
+ * [ in] time_ms: time frame in milliseconds
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_sleep_ms(int time_ms)
+{
+ msleep(time_ms);
+}
+/**
+ * syna_pal_sleep_us()
+ *
+ * Sleep for a range of time in microseconds
+ *
+ * @param
+ * [ in] time_us_min: the min. time frame in microseconds
+ * [ in] time_us_max: the max. time frame in microseconds
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_sleep_us(int time_us_min, int time_us_max)
+{
+ usleep_range(time_us_min, time_us_max);
+}
+/**
+ * syna_pal_busy_delay_ms()
+ *
+ * Busy wait for a fixed amount of time in milliseconds
+ *
+ * @param
+ * [ in] time_ms: time frame in milliseconds
+ *
+ * @return
+ * none.
+ */
+static inline void syna_pal_busy_delay_ms(int time_ms)
+{
+ mdelay(time_ms);
+}
+
+
+/**
+ * @section: C Runtime for String operations
+ *
+ * @brief: syna_pal_str_len
+ * Return the length of C string
+ *
+ * @brief: syna_pal_str_cpy:
+ * Ensure the safe size before doing C string copy
+ *
+ * @brief: syna_pal_str_cmp:
+ * Compare whether the given C strings are equal or not
+ */
+
+/**
+ * syna_pal_str_len()
+ *
+ * Return the length of C string
+ *
+ * @param
+ * [ in] str: an array of characters
+ *
+ * @return
+ * the length of given string
+ */
+static inline unsigned int syna_pal_str_len(const char *str)
+{
+ return (unsigned int)strlen(str);
+}
+/**
+ * syna_pal_str_cpy()
+ *
+ * Copy the C string pointed by source into the array pointed by destination.
+ *
+ * @param
+ * [ in] dest: pointer to the destination C string
+ * [ in] dest_size: size of destination C string
+ * [out] src: pointer to the source of C string to be copied
+ * [ in] src_size: size of source C string
+ * [ in] num: number of bytes to copy
+ *
+ * @return
+ * 0 on success; otherwise, on error.
+ */
+static inline int syna_pal_str_cpy(char *dest, unsigned int dest_size,
+ const char *src, unsigned int src_size, unsigned int num)
+{
+ if (dest == NULL || src == NULL)
+ return -1;
+
+ if (num > dest_size || num > src_size) {
+ LOGE("Invalid size. src:%d, dest:%d, num:%d\n",
+ src_size, dest_size, num);
+ return -1;
+ }
+
+ strncpy(dest, src, num);
+
+ return 0;
+}
+/**
+ * syna_pal_str_cmp()
+ *
+ * Compares up to num characters of the C string str1 to those of the
+ * C string str2.
+ *
+ * @param
+ * [ in] str1: C string to be compared
+ * [ in] str2: C string to be compared
+ * [ in] num: number of characters to compare
+ *
+ * @return
+ * 0 if both strings are equal; otherwise, not equal.
+ */
+static inline int syna_pal_str_cmp(const char *str1, const char *str2,
+ unsigned int num)
+{
+ return strncmp(str1, str2, num);
+}
+/**
+ * syna_pal_hex_to_uint()
+ *
+ * Convert the given string in hex to an integer returned
+ *
+ * @param
+ * [ in] str: C string to be converted
+ * [ in] length: target length
+ *
+ * @return
+ * An integer converted
+ */
+static inline unsigned int syna_pal_hex_to_uint(char *str, int length)
+{
+ unsigned int result = 0;
+ char *ptr = NULL;
+
+ for (ptr = str; ptr != str + length; ++ptr) {
+ result <<= 4;
+ if (*ptr >= 'A')
+ result += *ptr - 'A' + 10;
+ else
+ result += *ptr - '0';
+ }
+
+ return result;
+}
+
+/**
+ * @section: C Runtime for Checksum Calculation
+ *
+ * @brief: syna_pal_crc32
+ * Calculates the CRC32 value
+ */
+
+/**
+ * syna_pal_crc32()
+ *
+ * Calculates the CRC32 value of the data
+ *
+ * @param
+ * [ in] seed: the previous crc32 value
+ * [ in] data: byte data for the calculation
+ * [ in] len: the byte length of the data.
+ *
+ * @return
+ * 0 if both strings are equal; otherwise, not equal.
+ */
+static inline unsigned int syna_pal_crc32(unsigned int seed,
+ const char *data, unsigned int len)
+{
+ return crc32(seed, data, len);
+}
+
+#endif /* end of _SYNAPTICS_TCM2_C_RUNTIME_H_ */
diff --git a/syna_tcm2_sysfs.c b/syna_tcm2_sysfs.c
new file mode 100644
index 0000000..84677bc
--- /dev/null
+++ b/syna_tcm2_sysfs.c
@@ -0,0 +1,2207 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2_sysfs.c
+ *
+ * This file implements cdev and ioctl interface in the reference driver.
+ */
+
+#include <linux/string.h>
+
+#include "syna_tcm2.h"
+#include "synaptics_touchcom_core_dev.h"
+#include "synaptics_touchcom_func_base.h"
+#include "synaptics_touchcom_func_touch.h"
+#ifdef HAS_TESTING_FEATURE
+#include "syna_tcm2_testing.h"
+#endif
+
+
+/* #define ENABLE_PID_TASK */
+
+#define SIG_ATTN (46)
+
+/* structure for IOCTLs
+ */
+struct syna_ioctl_data {
+ unsigned int data_length;
+ unsigned int buf_size;
+ unsigned char __user *buf;
+};
+
+#if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL)
+struct syna_tcm_ioctl_data_compat {
+ unsigned int data_length;
+ unsigned int buf_size;
+ compat_uptr_t __user *buf;
+};
+#endif
+
+/* defines the IOCTLs supported
+ */
+#define IOCTL_MAGIC 's'
+
+/* Previous IOCTLs in early driver */
+#define OLD_RESET_ID (0x00)
+#define OLD_SET_IRQ_MODE_ID (0x01)
+#define OLD_SET_RAW_MODE_ID (0x02)
+#define OLD_CONCURRENT_ID (0x03)
+
+#define IOCTL_OLD_RESET \
+ _IO(IOCTL_MAGIC, OLD_RESET_ID)
+#define IOCTL_OLD_SET_IRQ_MODE \
+ _IOW(IOCTL_MAGIC, OLD_SET_IRQ_MODE_ID, int)
+#define IOCTL_OLD_SET_RAW_MODE \
+ _IOW(IOCTL_MAGIC, OLD_SET_RAW_MODE_ID, int)
+#define IOCTL_OLD_CONCURRENT \
+ _IOW(IOCTL_MAGIC, OLD_CONCURRENT_ID, int)
+
+/* Standard IOCTLs in TCM2 driver */
+#define STD_IOCTL_BEGIN (0x10)
+#define STD_SET_PID_ID (0x11)
+#define STD_ENABLE_IRQ_ID (0x12)
+#define STD_RAW_READ_ID (0x13)
+#define STD_RAW_WRITE_ID (0x14)
+#define STD_GET_FRAME_ID (0x15)
+#define STD_SEND_MESSAGE_ID (0x16)
+#define STD_SET_REPORTS_ID (0x17)
+#define STD_CHECK_FRAMES_ID (0x18)
+#define STD_CLEAN_OUT_FRAMES_ID (0x19)
+
+#define IOCTL_STD_IOCTL_BEGIN \
+ _IOR(IOCTL_MAGIC, STD_IOCTL_BEGIN)
+#define IOCTL_STD_SET_PID \
+ _IOW(IOCTL_MAGIC, STD_SET_PID_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_ENABLE_IRQ \
+ _IOW(IOCTL_MAGIC, STD_ENABLE_IRQ_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_RAW_READ \
+ _IOR(IOCTL_MAGIC, STD_RAW_READ_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_RAW_WRITE \
+ _IOW(IOCTL_MAGIC, STD_RAW_WRITE_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_GET_FRAME \
+ _IOWR(IOCTL_MAGIC, STD_GET_FRAME_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_SEND_MESSAGE \
+ _IOWR(IOCTL_MAGIC, STD_SEND_MESSAGE_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_SET_REPORT_TYPES \
+ _IOW(IOCTL_MAGIC, STD_SET_REPORTS_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_CHECK_FRAMES \
+ _IOWR(IOCTL_MAGIC, STD_CHECK_FRAMES_ID, struct syna_ioctl_data *)
+#define IOCTL_STD_CLEAN_OUT_FRAMES \
+ _IOWR(IOCTL_MAGIC, STD_CLEAN_OUT_FRAMES_ID, struct syna_ioctl_data *)
+
+/* g_sysfs_dir represents the root directory of sysfs nodes being created
+ */
+static struct kobject *g_sysfs_dir;
+
+/* g_extif_mutex is used to protect the access from the userspace application
+ */
+static syna_pal_mutex_t g_extif_mutex;
+
+/* g_cdev_buf is a temporary buffer storing the data from userspace
+ */
+static struct tcm_buffer g_cdev_cbuf;
+
+/* g_fifo_queue_mutex is used to protect the access from
+ * the userspace application
+ */
+static syna_pal_mutex_t g_fifo_queue_mutex;
+
+/* The g_sysfs_io_polling_interval is used to set the polling interval
+ * for syna_tcm_send_command from syna_cdev_ioctl_send_message.
+ * It will set to the mode SYSFS_FULL_INTERRUPT for using the full
+ * interrupt mode. The way to update this variable is through the
+ * syna_cdev_ioctl_enable_irq.
+ */
+unsigned int g_sysfs_io_polling_interval;
+
+/* a buffer to record the streaming report
+ * considering touch report and another reports may be co-enabled
+ * at the same time, give a little buffer here (3 sec x 300 fps)
+ */
+#define FIFO_QUEUE_MAX_FRAMES (1200)
+#define SEND_MESSAGE_HEADER_LENGTH (3)
+
+/* Indicate the interrupt status especially for sysfs using */
+#define SYSFS_DISABLED_INTERRUPT (0)
+#define SYSFS_ENABLED_INTERRUPT (1)
+
+/*Define a data structure that contains a list_head*/
+struct fifo_queue {
+ struct list_head next;
+ unsigned char *fifo_data;
+ unsigned int data_length;
+ struct timeval timestamp;
+};
+
+
+/**
+ * syna_sysfs_info_show()
+ *
+ * Attribute to show the device and driver information to the console.
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [out] buf: string buffer shown on console
+ *
+ * @return
+ * on success, number of characters being output;
+ * otherwise, negative value on error.
+ */
+static ssize_t syna_sysfs_info_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int retval;
+ unsigned int count;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+ struct tcm_dev *tcm_dev;
+ int i;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+ tcm_dev = tcm->tcm_dev;
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ count = 0;
+
+ if (!tcm->is_connected) {
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Device is NOT connected\n");
+ goto exit;
+ }
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Driver version: %d.%s\n",
+ SYNAPTICS_TCM_DRIVER_VERSION,
+ SYNAPTICS_TCM_DRIVER_SUBVER);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Core lib version: %d.%02d\n\n",
+ (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8),
+ (unsigned char)SYNA_TCM_CORE_LIB_VERSION);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "TouchComm version: %d\n", tcm_dev->id_info.version);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ switch (tcm_dev->id_info.mode) {
+ case MODE_APPLICATION_FIRMWARE:
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Firmware mode: Application Firmware, 0x%02x\n",
+ tcm_dev->id_info.mode);
+ if (retval < 0)
+ goto exit;
+ break;
+ case MODE_BOOTLOADER:
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Firmware mode: Bootloader, 0x%02x\n",
+ tcm_dev->id_info.mode);
+ if (retval < 0)
+ goto exit;
+ break;
+ case MODE_ROMBOOTLOADER:
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Firmware mode: Rom Bootloader, 0x%02x\n",
+ tcm_dev->id_info.mode);
+ if (retval < 0)
+ goto exit;
+ break;
+ default:
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Firmware mode: Mode 0x%02x\n",
+ tcm_dev->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 = syna_pal_mem_cpy(buf,
+ PAGE_SIZE - count,
+ tcm_dev->id_info.part_number,
+ sizeof(tcm_dev->id_info.part_number),
+ sizeof(tcm_dev->id_info.part_number));
+ if (retval < 0) {
+ LOGE("Fail to copy part number string\n");
+ goto exit;
+ }
+ buf += sizeof(tcm_dev->id_info.part_number);
+ count += sizeof(tcm_dev->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\n", tcm_dev->packrat_number);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ if (tcm_dev->id_info.mode != MODE_APPLICATION_FIRMWARE) {
+ retval = count;
+ goto exit;
+ }
+
+ retval = snprintf(buf, PAGE_SIZE - count, "Config ID: ");
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ for (i = 0; i < MAX_SIZE_CONFIG_ID; i++) {
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "0x%2x ", tcm_dev->config_id[i]);
+ if (retval < 0)
+ goto exit;
+ buf += retval;
+ count += retval;
+ }
+
+ retval = snprintf(buf, PAGE_SIZE - count, "\n");
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Max X & Y: %d, %d\n", tcm_dev->max_x, tcm_dev->max_y);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Num of objects: %d\n", tcm_dev->max_objects);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Num of cols & rows: %d, %d\n", tcm_dev->cols, tcm_dev->rows);
+ if (retval < 0)
+ goto exit;
+
+ buf += retval;
+ count += retval;
+
+ retval = count;
+
+exit:
+ syna_pal_mutex_unlock(&g_extif_mutex);
+ return retval;
+}
+
+static struct kobj_attribute kobj_attr_info =
+ __ATTR(info, 0444, syna_sysfs_info_show, NULL);
+
+/**
+ * syna_sysfs_irq_en_store()
+ *
+ * Attribute to disable/enable the irq
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [ in] buf: string buffer input
+ * [ in] count: size of buffer input
+ *
+ * @return
+ * on success, return count; otherwise, return error code
+ */
+static ssize_t syna_sysfs_irq_en_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int retval = 0;
+ unsigned int input;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ if (kstrtouint(buf, 10, &input))
+ return -EINVAL;
+
+ if (!tcm->hw_if->ops_enable_irq)
+ return 0;
+
+ if (!tcm->is_connected) {
+ LOGW("Device is NOT connected\n");
+ retval = count;
+ goto exit;
+ }
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ /* disable the interrupt line */
+ if (input == 0) {
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false);
+ if (retval < 0) {
+ LOGE("Fail to disable interrupt\n");
+ goto exit;
+ }
+ } else if (input == 1) {
+ /* enable the interrupt line */
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true);
+ if (retval < 0) {
+ LOGE("Fail to enable interrupt\n");
+ goto exit;
+ }
+ } else {
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ retval = count;
+
+exit:
+ syna_pal_mutex_unlock(&g_extif_mutex);
+ return retval;
+}
+
+static struct kobj_attribute kobj_attr_irq_en =
+ __ATTR(irq_en, 0220, NULL, syna_sysfs_irq_en_store);
+
+/**
+ * syna_sysfs_reset_store()
+ *
+ * Attribute to issue a reset.
+ * "1" for a sw reset; "2" for a hardware reset
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [ in] buf: string buffer input
+ * [ in] count: size of buffer input
+ *
+ * @return
+ * on success, return count; otherwise, return error code
+ */
+static ssize_t syna_sysfs_reset_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int retval = 0;
+ unsigned int input;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ if (kstrtouint(buf, 10, &input))
+ return -EINVAL;
+
+ if (!tcm->is_connected) {
+ LOGW("Device is NOT connected\n");
+ retval = count;
+ goto exit;
+ }
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ if (input == 1) {
+ retval = syna_tcm_reset(tcm->tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to do reset\n");
+ goto exit;
+ }
+ } else if (input == 2) {
+ if (!tcm->hw_if->ops_hw_reset)
+ goto exit;
+
+ tcm->hw_if->ops_hw_reset(tcm->hw_if);
+ } else {
+ goto exit;
+ }
+
+ /* check the fw setup in case the settings is changed */
+ if (IS_APP_FW_MODE(tcm->tcm_dev->dev_mode)) {
+ retval = tcm->dev_set_up_app_fw(tcm);
+ if (retval < 0) {
+ LOGE("Fail to set up app fw\n");
+ goto exit;
+ }
+ }
+
+ retval = count;
+
+exit:
+ syna_pal_mutex_unlock(&g_extif_mutex);
+ return retval;
+}
+
+static struct kobj_attribute kobj_attr_reset =
+ __ATTR(reset, 0220, NULL, syna_sysfs_reset_store);
+
+/**
+ * declaration of sysfs attributes
+ */
+static struct attribute *attrs[] = {
+ &kobj_attr_info.attr,
+ &kobj_attr_irq_en.attr,
+ &kobj_attr_reset.attr,
+ NULL,
+};
+
+static struct attribute_group attr_group = {
+ .attrs = attrs,
+};
+
+/**
+ * syna_sysfs_create_dir()
+ *
+ * Create a directory and register it with sysfs.
+ * Then, create all defined sysfs files.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] pdev: an instance of platform device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_sysfs_create_dir(struct syna_tcm *tcm,
+ struct platform_device *pdev)
+{
+ int retval = 0;
+
+ g_sysfs_dir = kobject_create_and_add("sysfs",
+ &pdev->dev.kobj);
+ if (!g_sysfs_dir) {
+ LOGE("Fail to create sysfs directory\n");
+ return -ENOTDIR;
+ }
+
+ tcm->sysfs_dir = g_sysfs_dir;
+
+ retval = sysfs_create_group(g_sysfs_dir, &attr_group);
+ if (retval < 0) {
+ LOGE("Fail to create sysfs group\n");
+
+ kobject_put(tcm->sysfs_dir);
+ return retval;
+ }
+
+#ifdef HAS_TESTING_FEATURE
+ retval = syna_testing_create_dir(tcm, g_sysfs_dir);
+ if (retval < 0) {
+ LOGE("Fail to create testing sysfs\n");
+
+ sysfs_remove_group(tcm->sysfs_dir, &attr_group);
+ kobject_put(tcm->sysfs_dir);
+ return retval;
+ }
+#endif
+
+ return 0;
+}
+/**
+ * syna_sysfs_remove_dir()
+ *
+ * Remove the allocate sysfs directory
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+void syna_sysfs_remove_dir(struct syna_tcm *tcm)
+{
+ if (!tcm) {
+ LOGE("Invalid tcm device handle\n");
+ return;
+ }
+
+ if (tcm->sysfs_dir) {
+#ifdef HAS_TESTING_FEATURE
+ syna_testing_remove_dir();
+#endif
+
+ sysfs_remove_group(tcm->sysfs_dir, &attr_group);
+
+ kobject_put(tcm->sysfs_dir);
+ }
+
+}
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+/**
+ * syna_cdev_insert_fifo()
+ *
+ * Insert/Push the data to the queue.
+ *
+ * This function is called by syna_cdev_update_report_queue(),
+ * where the event data will be placed as the below format in byte
+ * and use this function to store the data in queue.
+ * [0 ] : status / report code
+ * [1 : 2 ] : length of data frame
+ * [3 : N + 3] : N bytes of data payload
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] buf_ptr: points to a data going to push
+ * [ in] length: data length
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_insert_fifo(struct syna_tcm *tcm,
+ unsigned char *buf_ptr, unsigned int length)
+{
+ int retval = 0;
+ struct fifo_queue *pfifo_data;
+ struct fifo_queue *pfifo_data_temp;
+ static int pre_remaining_frames = -1;
+
+ syna_pal_mutex_lock(&g_fifo_queue_mutex);
+
+ /* check queue buffer limit */
+ if (tcm->fifo_remaining_frame >= FIFO_QUEUE_MAX_FRAMES) {
+ if (tcm->fifo_remaining_frame != pre_remaining_frames)
+ LOGI("Reached %d and drop FIFO first frame\n",
+ tcm->fifo_remaining_frame);
+
+ pfifo_data_temp = list_first_entry(&tcm->frame_fifo_queue,
+ struct fifo_queue, next);
+
+ list_del(&pfifo_data_temp->next);
+ kfree(pfifo_data_temp->fifo_data);
+ kfree(pfifo_data_temp);
+ pre_remaining_frames = tcm->fifo_remaining_frame;
+ tcm->fifo_remaining_frame--;
+ } else if (pre_remaining_frames >= FIFO_QUEUE_MAX_FRAMES) {
+ LOGI("Reached limit, dropped oldest frame, remaining:%d\n",
+ tcm->fifo_remaining_frame);
+ pre_remaining_frames = tcm->fifo_remaining_frame;
+ }
+
+ pfifo_data = kmalloc(sizeof(*pfifo_data), GFP_KERNEL);
+ if (!(pfifo_data)) {
+ LOGE("Failed to allocate memory\n");
+ LOGE("Allocation size = %zu\n", (sizeof(*pfifo_data)));
+ retval = -ENOMEM;
+ goto exit;
+ }
+
+ pfifo_data->fifo_data = kmalloc(length, GFP_KERNEL);
+ if (!(pfifo_data->fifo_data)) {
+ LOGE("Failed to allocate memory, size = %d\n", length);
+ retval = -ENOMEM;
+ goto exit;
+ }
+
+ pfifo_data->data_length = length;
+
+ memcpy((void *)pfifo_data->fifo_data, (void *)buf_ptr, length);
+ do_gettimeofday(&(pfifo_data->timestamp));
+ /* append the data to the tail for FIFO queueing */
+ list_add_tail(&pfifo_data->next, &tcm->frame_fifo_queue);
+ tcm->fifo_remaining_frame++;
+ retval = 0;
+
+exit:
+ syna_pal_mutex_unlock(&g_fifo_queue_mutex);
+ return retval;
+}
+#endif
+/**
+ * syna_cdev_ioctl_check_frame()
+ *
+ * Check the queuing status and wait for the data if it's empty.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in/out] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in] data_size: timeout value for queue waiting
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_check_frame(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int data_size)
+{
+ int retval = 0;
+ int result = 0;
+ unsigned int timeout = 0;
+ unsigned int frames = 0;
+ unsigned char data[4] = {0};
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return _EINVAL;
+ }
+
+ if (buf_size < sizeof(data) || data_size < sizeof(data)) {
+ LOGE("Invalid sync data size, buf_size: %u\n", buf_size);
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ result = copy_from_user(data, ubuf_ptr,
+ sizeof(data));
+ if (result) {
+ LOGE("Fail to copy data from user space\n");
+ retval = -EBADE;
+ goto exit;
+ }
+
+ /* Parese the waiting duration length */
+ timeout = syna_pal_le4_to_uint(&data[0]);
+ LOGD("Time out: %d\n", timeout);
+
+ if (list_empty(&tcm->frame_fifo_queue)) {
+ LOGD("The queue is empty, wait for the frames\n");
+ result = wait_event_interruptible_timeout(tcm->wait_frame,
+ (tcm->fifo_remaining_frame > 0),
+ msecs_to_jiffies(timeout));
+ if (result == 0) {
+ LOGD("Queue waiting timed out after %dms\n", timeout);
+ retval = -ETIMEDOUT;
+ goto exit;
+ }
+ LOGD("Data queued\n");
+ retval = data_size;
+ } else {
+ LOGD("Queue is not empty\n");
+ retval = data_size;
+ }
+
+exit:
+ if (retval > 0) {
+ frames = tcm->fifo_remaining_frame;
+ data[0] = (unsigned char)(frames & 0xff);
+ data[1] = (unsigned char)((frames >> 8) & 0xff);
+ data[2] = (unsigned char)((frames >> 16) & 0xff);
+ data[3] = (unsigned char)((frames >> 24) & 0xff);
+ result = copy_to_user((void *)ubuf_ptr,
+ data, sizeof(data));
+ if (result) {
+ LOGE("Fail to copy data to user space\n");
+ retval = -EBADE;
+ }
+ }
+
+ return retval;
+}
+
+/**
+ * syna_cdev_clean_queue()
+ *
+ * Clean the data queue.
+ * All data in the queue will be cleaned up in every time of device
+ * open and close.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * void.
+ */
+static void syna_cdev_clean_queue(struct syna_tcm *tcm)
+{
+ struct fifo_queue *pfifo_data;
+
+ syna_pal_mutex_lock(&g_fifo_queue_mutex);
+
+ while (!list_empty(&tcm->frame_fifo_queue)) {
+ pfifo_data = list_first_entry(&tcm->frame_fifo_queue,
+ struct fifo_queue, next);
+ list_del(&pfifo_data->next);
+ kfree(pfifo_data->fifo_data);
+ kfree(pfifo_data);
+ if (tcm->fifo_remaining_frame != 0)
+ tcm->fifo_remaining_frame--;
+ }
+
+ LOGD("Queue cleaned, frame: %d\n", tcm->fifo_remaining_frame);
+
+ syna_pal_mutex_unlock(&g_fifo_queue_mutex);
+}
+/**
+ * syna_cdev_ioctl_get_frame()
+ *
+ * Read the data from the queue and return to userspace if data is
+ * copied or the specified timeout is expired.
+ *
+ * Please be noted that the retried data is formatted as follows.
+ * [0 ] : status / report code
+ * [1 : 2 ] : length of data frame
+ * [3 : N + 3] : N bytes of data payload
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [in/out] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [out] frame_size: frame size returned
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_get_frame(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int *frame_size)
+{
+ int retval = 0;
+ int timeout = 0;
+ unsigned char timeout_data[4] = {0};
+ struct fifo_queue *pfifo_data;
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return _EINVAL;
+ }
+
+ if (buf_size < sizeof(timeout_data)) {
+ LOGE("Invalid sync data size, buf_size:%d\n", buf_size);
+ retval = -EINVAL;
+ goto exit;
+ }
+
+#if !defined(ENABLE_EXTERNAL_FRAME_PROCESS)
+ LOGE("ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n");
+ return -EFAULT;
+#endif
+
+ retval = copy_from_user(timeout_data, ubuf_ptr, sizeof(timeout_data));
+ if (retval) {
+ LOGE("Fail to copy data from user space, size:%d\n", retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ /* get the waiting duration */
+ timeout = syna_pal_le4_to_uint(&timeout_data[0]);
+ LOGD("Wait time: %dms\n", timeout);
+
+ if (list_empty(&tcm->frame_fifo_queue)) {
+ LOGD("The queue is empty, wait for the frame\n");
+ retval = wait_event_interruptible_timeout(tcm->wait_frame,
+ (tcm->fifo_remaining_frame > 0),
+ msecs_to_jiffies(timeout));
+ if (retval == 0) {
+ LOGD("Queue waiting timed out after %dms\n", timeout);
+ retval = -ETIMEDOUT;
+ *frame_size = 0;
+ goto exit;
+ }
+ LOGD("Data queued\n");
+ }
+
+ /* confirm the queue status */
+ if (list_empty(&tcm->frame_fifo_queue)) {
+ LOGD("Is queue empty? The remaining frame = %d\n",
+ tcm->fifo_remaining_frame);
+ retval = -ENODATA;
+ goto exit;
+ }
+
+ syna_pal_mutex_lock(&g_fifo_queue_mutex);
+
+ pfifo_data = list_first_entry(&tcm->frame_fifo_queue,
+ struct fifo_queue, next);
+
+ LOGD("Pop data from the queue, data length = %d\n",
+ pfifo_data->data_length);
+
+ if (buf_size >= pfifo_data->data_length) {
+ retval = copy_to_user((void *)ubuf_ptr,
+ pfifo_data->fifo_data,
+ pfifo_data->data_length);
+ if (retval) {
+ LOGE("Fail to copy data to user space, size:%d\n",
+ retval);
+ retval = -EBADE;
+ }
+
+ *frame_size = pfifo_data->data_length;
+
+ } else {
+ LOGE("No enough space for data copy, buf_size:%d data:%d\n",
+ buf_size, pfifo_data->data_length);
+
+ retval = -EOVERFLOW;
+ goto exit;
+ }
+
+ LOGD("From FIFO: (0x%02x, 0x%02x, 0x%02x, 0x%02x)\n",
+ pfifo_data->fifo_data[0], pfifo_data->fifo_data[1],
+ pfifo_data->fifo_data[2], pfifo_data->fifo_data[3]);
+
+ list_del(&pfifo_data->next);
+ kfree(pfifo_data->fifo_data);
+ kfree(pfifo_data);
+ if (tcm->fifo_remaining_frame != 0)
+ tcm->fifo_remaining_frame--;
+
+ syna_pal_mutex_unlock(&g_fifo_queue_mutex);
+
+ if (retval >= 0)
+ retval = pfifo_data->data_length;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_cdev_ioctl_set_reports()
+ *
+ * Assign the report types for queuing. The enabled reports will be queued
+ * into the FIFO queue.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in] report_size: report types data size
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_set_reports(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int report_size)
+{
+ int retval = 0;
+ unsigned char data[REPORT_TYPES] = {0};
+ unsigned int reports = 0;
+ unsigned int report_set = 0;
+
+ if (buf_size < sizeof(data)) {
+ LOGE("Invalid sync data size, buf_size:%d, expected:%d\n",
+ buf_size, (unsigned int)sizeof(data));
+ return -EINVAL;
+ }
+
+#if !defined(ENABLE_EXTERNAL_FRAME_PROCESS)
+ LOGE("ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n");
+ return -EINVAL;
+#endif
+
+ if (report_size == 0) {
+ LOGE("Invalid written size\n");
+ return -EINVAL;
+ }
+
+ retval = copy_from_user(data, ubuf_ptr, report_size);
+ if (retval) {
+ LOGE("Fail to copy data from user space, size:%d\n", retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ retval = syna_pal_mem_cpy(tcm->report_to_queue, REPORT_TYPES,
+ data, sizeof(data), REPORT_TYPES);
+ for (reports = 0 ; reports < REPORT_TYPES ; reports++) {
+ if (tcm->report_to_queue[reports] == EFP_ENABLE) {
+ report_set++;
+ LOGD("Set report 0x%02x for queue\n", reports);
+ }
+ }
+
+ LOGI("Forward %d types of reports to the Queue.\n", report_set);
+
+ retval = report_set;
+
+exit:
+ return retval;
+}
+/**
+ * syna_cdev_ioctl_send_message()
+ *
+ * Send the command/message from userspace.
+ *
+ * For updating the g_sysfs_io_polling_interval, it need to be configured
+ * by syna_cdev_ioctl_enable_irq from userspace.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in/out] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in/out] msg_size: size of message
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_send_message(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int *msg_size)
+{
+ int retval = 0;
+ unsigned char *data = NULL;
+ unsigned char resp_code = 0;
+ unsigned int payload_length = 0;
+ unsigned int delay_ms_resp = RESP_IN_POLLING;
+ struct tcm_buffer resp_data_buf;
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return -ENXIO;
+ }
+
+ if (buf_size < SEND_MESSAGE_HEADER_LENGTH) {
+ LOGE("Invalid sync data size, buf_size:%d\n", buf_size);
+ return -EINVAL;
+ }
+
+ if (*msg_size == 0) {
+ LOGE("Invalid message length, msg size: 0\n");
+ return -EINVAL;
+ }
+
+ syna_tcm_buf_lock(&g_cdev_cbuf);
+
+ retval = syna_tcm_buf_alloc(&g_cdev_cbuf, buf_size);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n",
+ buf_size);
+ goto exit;
+ }
+
+ data = g_cdev_cbuf.buf;
+
+ retval = copy_from_user(data, ubuf_ptr, *msg_size);
+ if (retval) {
+ LOGE("Fail to copy data from user space, size:%d\n", *msg_size);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ payload_length = syna_pal_le2_to_uint(&data[1]);
+ LOGD("Command = 0x%02x, payload length = %d\n",
+ data[0], payload_length);
+
+ /* init a buffer for the response data */
+ syna_tcm_buf_init(&resp_data_buf);
+
+ if (g_sysfs_io_polling_interval == RESP_IN_ATTN)
+ delay_ms_resp = RESP_IN_ATTN;
+ else
+ delay_ms_resp = g_sysfs_io_polling_interval;
+
+ retval = syna_tcm_send_command(tcm->tcm_dev,
+ data[0],
+ &data[3],
+ payload_length,
+ &resp_code,
+ &resp_data_buf,
+ delay_ms_resp);
+ if (retval < 0) {
+ LOGE("Fail to send command:%d\n", retval);
+ goto exit;
+ }
+
+ syna_pal_mem_set(data, 0, buf_size);
+ /* status code */
+ data[0] = resp_code;
+ /* the length for response data */
+ data[1] = (unsigned char)(resp_data_buf.data_length & 0xff);
+ data[2] = (unsigned char)((resp_data_buf.data_length >> 8) & 0xff);
+ /* response data */
+ if (resp_data_buf.data_length > 0) {
+ retval = syna_pal_mem_cpy(&g_cdev_cbuf.buf[3],
+ (g_cdev_cbuf.buf_size - SEND_MESSAGE_HEADER_LENGTH),
+ resp_data_buf.buf,
+ resp_data_buf.buf_size,
+ resp_data_buf.data_length);
+ if (retval < 0) {
+ LOGE("Fail to copy resp data\n");
+ goto exit;
+ }
+ }
+
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+ /* It's for queuing the data when user is polling the command
+ * response for the selected responses. The response will not be
+ * queued if the user doesn't set the report/response types through
+ * syna_cdev_ioctl_set_reports.
+ */
+ if (delay_ms_resp != RESP_IN_ATTN) {
+ if (tcm->report_to_queue[resp_code] == EFP_ENABLE) {
+ syna_cdev_update_report_queue(tcm, resp_code,
+ &resp_data_buf);
+ }
+ }
+#endif
+
+ if (buf_size < resp_data_buf.data_length) {
+ LOGE("No enough space for data copy, buf_size:%d data:%d\n",
+ buf_size, resp_data_buf.data_length);
+ retval = -EOVERFLOW;
+ goto exit;
+ }
+
+ retval = copy_to_user((void *)ubuf_ptr,
+ data, resp_data_buf.data_length);
+ if (retval) {
+ LOGE("Fail to copy data to user space\n");
+ retval = -EBADE;
+ goto exit;
+ }
+
+ *msg_size = resp_data_buf.data_length + 3;
+ retval = *msg_size;
+
+exit:
+ syna_tcm_buf_unlock(&g_cdev_cbuf);
+
+ syna_tcm_buf_release(&resp_data_buf);
+
+ return retval;
+}
+
+/**
+ * syna_cdev_ioctl_enable_irq()
+ *
+ * Enable or disable the irq via IOCTL.
+ *
+ * Expect to get 4 bytes unsigned int parameter from userspace:
+ * 0: disable the irq.
+ * 1: enable the irq and set g_sysfs_io_polling_interval
+ * to RESP_IN_ATTN
+ * otherwise: enable the irq and also assign the polling interval
+ * to a specific time, which will be used when calling
+ * syna_cdev_ioctl_send_message.
+ * the min. polling time is RESP_IN_POLLING
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in] data_size: size of actual data
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_enable_irq(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int data_size)
+{
+ int retval = 0;
+ unsigned int data;
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return -ENXIO;
+ }
+
+ if ((buf_size < sizeof(data)) || (data_size < sizeof(data))) {
+ LOGE("Invalid sync data size, buf_size:%d, data_size:%d\n",
+ buf_size, data_size);
+ return -EINVAL;
+ }
+
+ if (!tcm->hw_if->ops_enable_irq) {
+ LOGW("Not support irq control\n");
+ return -EFAULT;
+ }
+
+ retval = copy_from_user(&data, ubuf_ptr, buf_size);
+ if (retval) {
+ LOGE("Fail to copy data from user space, size:%d\n", retval);
+ return -EBADE;
+ }
+
+ switch (data) {
+ case SYSFS_DISABLED_INTERRUPT:
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false);
+ if (retval < 0) {
+ LOGE("Fail to disable interrupt\n");
+ return retval;
+ }
+
+ g_sysfs_io_polling_interval =
+ tcm->tcm_dev->msg_data.default_resp_reading;
+
+ LOGI("IRQ is disabled by userspace application\n");
+
+ break;
+ case SYSFS_ENABLED_INTERRUPT:
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true);
+ if (retval < 0) {
+ LOGE("Fail to enable interrupt\n");
+ return retval;
+ }
+
+ g_sysfs_io_polling_interval = RESP_IN_ATTN;
+
+ LOGI("IRQ is enabled by userspace application\n");
+
+ break;
+ default:
+ /* recover the interrupt and also assing the polling interval */
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true);
+ if (retval < 0) {
+ LOGE("Fail to enable interrupt\n");
+ return retval;
+ }
+
+ g_sysfs_io_polling_interval = data;
+ if (g_sysfs_io_polling_interval < RESP_IN_POLLING)
+ g_sysfs_io_polling_interval = RESP_IN_POLLING;
+
+ LOGI("IRQ is enabled by userspace application\n");
+ LOGI("Polling interval is set to %d ms\n",
+ g_sysfs_io_polling_interval);
+
+ break;
+ }
+
+ return 0;
+}
+/**
+ * syna_cdev_ioctl_store_pid()
+ *
+ * Save PID through IOCTL interface
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in] data_size: size of actual data
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_store_pid(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int data_size)
+{
+ int retval = 0;
+ unsigned char *data = NULL;
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return -ENXIO;
+ }
+
+ if (buf_size < 4) {
+ LOGE("Invalid sync data size, buf_size:%d\n", buf_size);
+ return -EINVAL;
+ }
+
+ if (data_size < 4) {
+ LOGE("Invalid data_size\n");
+ return -EINVAL;
+ }
+
+ syna_tcm_buf_lock(&g_cdev_cbuf);
+
+ retval = syna_tcm_buf_alloc(&g_cdev_cbuf, buf_size);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for g_cdev_buf, size: %d\n",
+ buf_size);
+ goto exit;
+ }
+
+ data = g_cdev_cbuf.buf;
+
+ retval = copy_from_user(data, ubuf_ptr, data_size);
+ if (retval) {
+ LOGE("Fail to copy data from user space, size:%d\n", retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ tcm->proc_pid = syna_pal_le4_to_uint(&data[0]);
+
+ LOGD("PID: %d\n", (unsigned int)tcm->proc_pid);
+#ifdef ENABLE_PID_TASK
+ if (tcm->proc_pid) {
+ tcm->proc_task = pid_task(
+ find_vpid(tcm->proc_pid),
+ PIDTYPE_PID);
+ if (!tcm->proc_task) {
+ LOGE("Fail to locate task, pid: %d\n",
+ (unsigned int)tcm->proc_pid);
+ retval = -ESRCH;
+ goto exit;
+ }
+ }
+#endif
+exit:
+ syna_tcm_buf_unlock(&g_cdev_cbuf);
+
+ return retval;
+}
+/**
+ * syna_cdev_ioctl_raw_read()
+ *
+ * Read the data from device directly without routing to command wrapper
+ * interface.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [in/out] ubuf_ptr: ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in] rd_size: reading size
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_raw_read(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int rd_size)
+{
+ int retval = 0;
+ unsigned char *data = NULL;
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return -ENXIO;
+ }
+
+ if ((buf_size < 0) || (rd_size > buf_size)) {
+ LOGE("Invalid sync data size, buf_size:%d, rd_size:%d\n",
+ buf_size, rd_size);
+ return -EINVAL;
+ }
+
+ if (rd_size == 0) {
+ LOGE("The read length is 0\n");
+ return 0;
+ }
+
+ syna_tcm_buf_lock(&g_cdev_cbuf);
+
+ retval = syna_tcm_buf_alloc(&g_cdev_cbuf, rd_size);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n",
+ rd_size);
+ goto exit;
+ }
+
+ data = g_cdev_cbuf.buf;
+
+ retval = syna_tcm_read(tcm->tcm_dev,
+ data,
+ rd_size);
+ if (retval < 0) {
+ LOGE("Fail to read raw data, size: %d\n", rd_size);
+ goto exit;
+ }
+
+ if (copy_to_user((void *)ubuf_ptr, data, rd_size)) {
+ LOGE("Fail to copy data to user space\n");
+ retval = -EBADE;
+ goto exit;
+ }
+
+ retval = rd_size;
+
+exit:
+ syna_tcm_buf_unlock(&g_cdev_cbuf);
+
+ return retval;
+}
+/**
+ * syna_cdev_ioctl_raw_write()
+ *
+ * Write the given data to device directly without routing to command wrapper
+ * interface.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] ubuf_ptr: points to a memory space from userspace
+ * [ in] buf_size: size of given space
+ * [ in] wr_size: size to write
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_raw_write(struct syna_tcm *tcm,
+ const unsigned char *ubuf_ptr, unsigned int buf_size,
+ unsigned int wr_size)
+{
+ int retval = 0;
+ unsigned char *data = NULL;
+
+ if (!tcm->is_connected) {
+ LOGE("Not connected\n");
+ return -ENXIO;
+ }
+
+ if ((buf_size < 0) || (wr_size > buf_size)) {
+ LOGE("Invalid sync data size, buf_size:%d, wr_size:%d\n",
+ buf_size, wr_size);
+ return -EINVAL;
+ }
+
+ if (wr_size == 0) {
+ LOGE("Invalid written size\n");
+ return -EINVAL;
+ }
+
+ syna_tcm_buf_lock(&g_cdev_cbuf);
+
+ retval = syna_tcm_buf_alloc(&g_cdev_cbuf, wr_size);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n",
+ wr_size);
+ goto exit;
+ }
+
+ data = g_cdev_cbuf.buf;
+
+ retval = copy_from_user(data, ubuf_ptr, wr_size);
+ if (retval) {
+ LOGE("Fail to copy data from user space, size:%d\n", retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ LOGD("Write command: 0x%02x, legnth: 0x%02x, 0x%02x\n",
+ data[0], data[1], data[2]);
+
+ retval = syna_tcm_write(tcm->tcm_dev,
+ data,
+ wr_size);
+ if (retval < 0) {
+ LOGE("Fail to write raw data, size: %d\n", wr_size);
+ goto exit;
+ }
+
+ retval = wr_size;
+
+exit:
+ syna_tcm_buf_unlock(&g_cdev_cbuf);
+
+ return retval;
+}
+
+/**
+ * syna_cdev_ioctl_dispatch()
+ *
+ * Dispatch the IOCTLs operation based on the given code
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] code: code for the target operation
+ * [ in] ubuf_ptr: points to a memory space from userspace
+ * [ in] ubuf_size: size of given space
+ * [ in] wr_size: written data size
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_dispatch(struct syna_tcm *tcm,
+ unsigned int code, const unsigned char *ubuf_ptr,
+ unsigned int ubuf_size, unsigned int *data_size)
+{
+ int retval = 0;
+
+ switch (code) {
+ case STD_SET_PID_ID:
+ retval = syna_cdev_ioctl_store_pid(tcm,
+ ubuf_ptr, ubuf_size, *data_size);
+ break;
+ case STD_ENABLE_IRQ_ID:
+ retval = syna_cdev_ioctl_enable_irq(tcm,
+ ubuf_ptr, ubuf_size, *data_size);
+ break;
+ case STD_RAW_WRITE_ID:
+ retval = syna_cdev_ioctl_raw_write(tcm,
+ ubuf_ptr, ubuf_size, *data_size);
+ break;
+ case STD_RAW_READ_ID:
+ retval = syna_cdev_ioctl_raw_read(tcm,
+ ubuf_ptr, ubuf_size, *data_size);
+ break;
+ case STD_GET_FRAME_ID:
+ retval = syna_cdev_ioctl_get_frame(tcm,
+ ubuf_ptr, ubuf_size, data_size);
+ break;
+ case STD_SEND_MESSAGE_ID:
+ retval = syna_cdev_ioctl_send_message(tcm,
+ ubuf_ptr, ubuf_size, data_size);
+ break;
+ case STD_SET_REPORTS_ID:
+ retval = syna_cdev_ioctl_set_reports(tcm,
+ ubuf_ptr, ubuf_size, *data_size);
+ break;
+ case STD_CHECK_FRAMES_ID:
+ retval = syna_cdev_ioctl_check_frame(tcm,
+ ubuf_ptr, ubuf_size, *data_size);
+ break;
+ case STD_CLEAN_OUT_FRAMES_ID:
+ LOGD("STD_CLEAN_OUT_FRAMES_ID called\n");
+ syna_cdev_clean_queue(tcm);
+ retval = 0;
+ break;
+ default:
+ LOGE("Unknown ioctl code: 0x%x\n", code);
+ return -EINVAL;
+ }
+
+ return retval;
+}
+/**
+ * syna_cdev_ioctl_old_dispatch()
+ *
+ * Dispatch the old IOCTLs operation based on the given code
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] code: code for the target operation
+ * [ in] arg: argument passed from user-space
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_ioctl_old_dispatch(struct syna_tcm *tcm,
+ unsigned int code, unsigned long arg)
+{
+ int retval = 0;
+
+ switch (code) {
+ case OLD_RESET_ID:
+ retval = syna_tcm_reset(tcm->tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to do reset\n");
+ break;
+ }
+
+ retval = tcm->dev_set_up_app_fw(tcm);
+ if (retval < 0) {
+ LOGE("Fail to set up app fw\n");
+ break;
+ }
+
+ break;
+ case OLD_SET_IRQ_MODE_ID:
+ if (!tcm->hw_if->ops_enable_irq) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (arg == 0)
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if,
+ false);
+ else if (arg == 1)
+ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if,
+ true);
+ break;
+ case OLD_SET_RAW_MODE_ID:
+ if (arg == 0)
+ tcm->is_attn_redirecting = false;
+ else if (arg == 1)
+ tcm->is_attn_redirecting = true;
+
+ break;
+ case OLD_CONCURRENT_ID:
+ retval = 0;
+ break;
+
+ default:
+ LOGE("Unknown ioctl code: 0x%x\n", code);
+ retval = -EINVAL;
+ break;
+ }
+
+ return retval;
+}
+
+/**
+ * syna_cdev_ioctls()
+ *
+ * Used to implements the IOCTL operations
+ *
+ * @param
+ * [ in] filp: represents the file descriptor
+ * [ in] cmd: command code sent from userspace
+ * [ in] arg: arguments sent from userspace
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+#ifdef HAVE_UNLOCKED_IOCTL
+static long syna_cdev_ioctls(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+#else
+static int syna_cdev_ioctls(struct inode *inp, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+#endif
+{
+ int retval = 0;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+ struct syna_ioctl_data ioc_data;
+ unsigned char *ptr = NULL;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ retval = 0;
+
+ /* handle the old IOCTLs */
+ if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) {
+ retval = syna_cdev_ioctl_old_dispatch(tcm,
+ (unsigned int)_IOC_NR(cmd), arg);
+
+ goto exit;
+ } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) {
+ retval = 1;
+ goto exit;
+ }
+
+ retval = copy_from_user(&ioc_data,
+ (void __user *) arg,
+ sizeof(struct syna_ioctl_data));
+ if (retval) {
+ LOGE("Fail to copy ioctl_data from user space, size:%d\n",
+ retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ ptr = ioc_data.buf;
+
+ retval = syna_cdev_ioctl_dispatch(tcm,
+ (unsigned int)_IOC_NR(cmd),
+ (const unsigned char *)ptr,
+ ioc_data.buf_size,
+ &ioc_data.data_length);
+ if (retval < 0)
+ goto exit;
+
+ retval = copy_to_user((void __user *) arg,
+ &ioc_data,
+ sizeof(struct syna_ioctl_data));
+ if (retval) {
+ LOGE("Fail to update ioctl_data to user space, size:%d\n",
+ retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+exit:
+ syna_pal_mutex_unlock(&g_extif_mutex);
+
+ return retval;
+}
+
+#if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL)
+/**
+ * syna_cdev_compat_ioctls()
+ *
+ * Used to implements the IOCTL compatible operations
+ *
+ * @param
+ * [ in] filp: represents the file descriptor
+ * [ in] cmd: command code sent from userspace
+ * [ in] arg: arguments sent from userspace
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static long syna_cdev_compat_ioctls(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int retval = 0;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+ struct syna_tcm_ioctl_data_compat ioc_data;
+ unsigned char *ptr = NULL;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ retval = 0;
+
+ /* handle the old IOCTLs */
+ if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) {
+ retval = syna_cdev_ioctl_old_dispatch(tcm,
+ (unsigned int)_IOC_NR(cmd), arg);
+
+ goto exit;
+ } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) {
+ retval = 1;
+ goto exit;
+ }
+
+ retval = copy_from_user(&ioc_data,
+ (struct syna_tcm_ioctl_data_compat __user *) compat_ptr(arg),
+ sizeof(struct syna_tcm_ioctl_data_compat));
+ if (retval) {
+ LOGE("Fail to copy ioctl_data from user space, size:%d\n",
+ retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+ ptr = compat_ptr((unsigned long)ioc_data.buf);
+
+ retval = syna_cdev_ioctl_dispatch(tcm,
+ (unsigned int)_IOC_NR(cmd),
+ (const unsigned char *)ptr,
+ ioc_data.buf_size,
+ &ioc_data.data_length);
+ if (retval < 0)
+ goto exit;
+
+ retval = copy_to_user(compat_ptr(arg),
+ &ioc_data,
+ sizeof(struct syna_tcm_ioctl_data_compat));
+ if (retval) {
+ LOGE("Fail to update ioctl_data to user space, size:%d\n",
+ retval);
+ retval = -EBADE;
+ goto exit;
+ }
+
+exit:
+ syna_pal_mutex_unlock(&g_extif_mutex);
+
+ return retval;
+}
+#endif
+
+/**
+ * syna_cdev_llseek()
+ *
+ * Used to change the current position in a file.
+ *
+ * @param
+ * [ in] filp: represents the file descriptor
+ * [ in] off: the file position
+ * [ in] whence: flag for seeking
+ *
+ * @return
+ * not support
+ */
+static loff_t syna_cdev_llseek(struct file *filp,
+ loff_t off, int whence)
+{
+ return -EFAULT;
+}
+/**
+ * syna_cdev_read()
+ *
+ * Used to read data through the device file.
+ * Function will use raw write approach.
+ *
+ * @param
+ * [ in] filp: represents the file descriptor
+ * [out] buf: given buffer from userspace
+ * [ in] count: size of buffer
+ * [ in] f_pos: the file position
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static ssize_t syna_cdev_read(struct file *filp,
+ char __user *buf, size_t count, loff_t *f_pos)
+{
+ int retval = 0;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ if (count == 0)
+ return 0;
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ retval = syna_cdev_ioctl_raw_read(tcm,
+ (const unsigned char *)buf, count, count);
+ if (retval != count) {
+ LOGE("Invalid read operation, request:%d, return:%d\n",
+ (unsigned int)count, retval);
+ }
+
+ syna_pal_mutex_unlock(&g_extif_mutex);
+
+ return retval;
+}
+/**
+ * syna_cdev_write()
+ *
+ * Used to send data to device through the device file.
+ * Function will use raw write approach.
+ *
+ * @param
+ * [ in] filp: represents the file descriptor
+ * [ in] buf: given buffer from userspace
+ * [ in] count: size of buffer
+ * [ in] f_pos: the file position
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static ssize_t syna_cdev_write(struct file *filp,
+ const char __user *buf, size_t count, loff_t *f_pos)
+{
+ int retval = 0;
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ if (count == 0)
+ return 0;
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ retval = syna_cdev_ioctl_raw_write(tcm,
+ (const unsigned char *)buf, count, count);
+ if (retval != count) {
+ LOGE("Invalid write operation, request:%d, return:%d\n",
+ (unsigned int)count, retval);
+ }
+
+ syna_pal_mutex_unlock(&g_extif_mutex);
+
+ return retval;
+}
+/**
+ * syna_cdev_open()
+ *
+ * Invoked when the device file is being open, which should be
+ * always the first operation performed on the device file
+ *
+ * @param
+ * [ in] inp: represents a file in rootfs
+ * [ in] filp: represents the file descriptor
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_open(struct inode *inp, struct file *filp)
+{
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ if (tcm->char_dev_ref_count != 0) {
+ LOGN("cdev already open, %d\n",
+ tcm->char_dev_ref_count);
+ return -EBUSY;
+ }
+
+ tcm->char_dev_ref_count++;
+
+ g_sysfs_io_polling_interval = 0;
+
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+ syna_cdev_clean_queue(tcm);
+#endif
+ syna_pal_mutex_unlock(&g_extif_mutex);
+
+ LOGI("cdev open\n");
+
+ return 0;
+}
+/**
+ * syna_cdev_release()
+ *
+ * Invoked when the device file is being released
+ *
+ * @param
+ * [ in] inp: represents a file in rootfs
+ * [ in] filp: represents the file descriptor
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_cdev_release(struct inode *inp, struct file *filp)
+{
+ struct device *p_dev;
+ struct kobject *p_kobj;
+ struct syna_tcm *tcm;
+
+ p_kobj = g_sysfs_dir->parent;
+ p_dev = container_of(p_kobj, struct device, kobj);
+ tcm = dev_get_drvdata(p_dev);
+
+ syna_pal_mutex_lock(&g_extif_mutex);
+
+ if (tcm->char_dev_ref_count <= 0) {
+ LOGN("cdev already closed, %d\n",
+ tcm->char_dev_ref_count);
+ return 0;
+ }
+
+ tcm->char_dev_ref_count--;
+
+ tcm->is_attn_redirecting = false;
+ syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES);
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+ syna_cdev_clean_queue(tcm);
+#endif
+ syna_pal_mutex_unlock(&g_extif_mutex);
+
+ g_sysfs_io_polling_interval = 0;
+
+ LOGI("cdev close\n");
+
+ return 0;
+}
+
+/**
+ * Declare the operations of TouchCom device file
+ */
+static const struct file_operations device_fops = {
+ .owner = THIS_MODULE,
+#ifdef HAVE_UNLOCKED_IOCTL
+ .unlocked_ioctl = syna_cdev_ioctls,
+#if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL)
+ .compat_ioctl = syna_cdev_compat_ioctls,
+#endif
+#else
+ .ioctl = syna_cdev_ioctls,
+#endif
+ .llseek = syna_cdev_llseek,
+ .read = syna_cdev_read,
+ .write = syna_cdev_write,
+ .open = syna_cdev_open,
+ .release = syna_cdev_release,
+};
+/**
+ * syna_cdev_redirect_attn()
+ *
+ * Expose the status of ATTN signal to userspace
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * none.
+ */
+void syna_cdev_redirect_attn(struct syna_tcm *tcm)
+{
+ if (tcm->proc_pid)
+ return;
+}
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+/**
+ * syna_cdev_update_report_queue()
+ *
+ * Push the selected data to the queue.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] code: report type
+ * [ in] pevent_data: report payload
+ *
+ * @return
+ * none.
+ */
+void syna_cdev_update_report_queue(struct syna_tcm *tcm,
+ unsigned char code, struct tcm_buffer *pevent_data)
+{
+ int retval;
+ unsigned char *frame_buffer = NULL;
+ unsigned int frame_length = 0;
+
+ if (pevent_data == NULL) {
+ LOGE("Returned, invalid event data pointer\n");
+ return;
+ }
+ frame_length = pevent_data->data_length + 3;
+ LOGD("The overall queuing data length = %d\n", frame_length);
+ frame_buffer = (unsigned char *)syna_pal_mem_alloc(frame_length,
+ sizeof(unsigned char));
+ if (!frame_buffer) {
+ LOGE("Fail to allocate buffer, size: %d, data_length: %d\n",
+ pevent_data->data_length + 3, pevent_data->data_length);
+ return;
+ }
+
+ frame_buffer[0] = code;
+ frame_buffer[1] = (unsigned char)pevent_data->data_length;
+ frame_buffer[2] = (unsigned char)(pevent_data->data_length >> 8);
+
+ if (pevent_data->data_length > 0) {
+ retval = syna_pal_mem_cpy(&frame_buffer[3],
+ (frame_length - 3),
+ pevent_data->buf,
+ pevent_data->data_length,
+ pevent_data->data_length);
+ if (retval < 0) {
+ LOGE("Fail to copy data to buffer, size: %d\n",
+ pevent_data->data_length);
+ goto exit;
+ }
+ }
+ retval = syna_cdev_insert_fifo(tcm, frame_buffer, frame_length);
+ if (retval < 0) {
+ LOGE("Fail to insert data to fifo\n");
+ goto exit;
+ }
+
+ wake_up_interruptible(&(tcm->wait_frame));
+
+exit:
+ syna_pal_mem_free((void *)frame_buffer);
+}
+#endif
+/**
+ * syna_cdev_devnode()
+ *
+ * Provide the declaration of devtmpfs
+ *
+ * @param
+ * [ in] dev: an instance of device
+ * [ in] mode: mode of created node
+ *
+ * @return
+ * the string of devtmpfs
+ */
+static char *syna_cdev_devnode(struct device *dev, umode_t *mode)
+{
+ if (!mode)
+ return NULL;
+
+ /* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH */
+ *mode = CHAR_DEVICE_MODE;
+
+ return kasprintf(GFP_KERNEL, "%s", dev_name(dev));
+}
+/**
+ * syna_cdev_create_sysfs()
+ *
+ * Create a device node and register it with sysfs.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] pdev: an instance of platform device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_cdev_create_sysfs(struct syna_tcm *tcm,
+ struct platform_device *pdev)
+{
+ int retval = 0;
+ struct class *device_class = NULL;
+ struct device *device = NULL;
+ static int cdev_major_num;
+
+ tcm->device_class = NULL;
+ tcm->device = NULL;
+
+ tcm->is_attn_redirecting = false;
+
+ syna_pal_mutex_alloc(&g_extif_mutex);
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+ syna_pal_mutex_alloc(&g_fifo_queue_mutex);
+#endif
+ syna_tcm_buf_init(&g_cdev_cbuf);
+
+ if (cdev_major_num) {
+ tcm->char_dev_num = MKDEV(cdev_major_num, 0);
+ retval = register_chrdev_region(tcm->char_dev_num, 1,
+ PLATFORM_DRIVER_NAME);
+ if (retval < 0) {
+ LOGE("Fail to register char device\n");
+ goto err_register_chrdev_region;
+ }
+ } else {
+ retval = alloc_chrdev_region(&tcm->char_dev_num, 0, 1,
+ PLATFORM_DRIVER_NAME);
+ if (retval < 0) {
+ LOGE("Fail to allocate char device\n");
+ goto err_alloc_chrdev_region;
+ }
+
+ cdev_major_num = MAJOR(tcm->char_dev_num);
+ }
+
+ cdev_init(&tcm->char_dev, &device_fops);
+ tcm->char_dev.owner = THIS_MODULE;
+
+ retval = cdev_add(&tcm->char_dev, tcm->char_dev_num, 1);
+ if (retval < 0) {
+ LOGE("Fail to add cdev_add\n");
+ goto err_add_chardev;
+ }
+
+ device_class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME);
+ if (IS_ERR(device_class)) {
+ LOGE("Fail to create device class\n");
+ retval = PTR_ERR(device_class);
+ goto err_create_class;
+ }
+
+ device_class->devnode = syna_cdev_devnode;
+
+ device = device_create(device_class, NULL,
+ tcm->char_dev_num, NULL,
+ CHAR_DEVICE_NAME"%d", MINOR(tcm->char_dev_num));
+ if (IS_ERR(tcm->device)) {
+ LOGE("Fail to create character device\n");
+ retval = -ENOENT;
+ goto err_create_device;
+ }
+
+ tcm->device_class = device_class;
+
+ tcm->device = device;
+
+ tcm->char_dev_ref_count = 0;
+ tcm->proc_pid = 0;
+
+#ifdef ENABLE_EXTERNAL_FRAME_PROCESS
+ INIT_LIST_HEAD(&tcm->frame_fifo_queue);
+ init_waitqueue_head(&tcm->wait_frame);
+#endif
+ syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES);
+
+ retval = syna_sysfs_create_dir(tcm, pdev);
+ if (retval < 0) {
+ LOGE("Fail to create sysfs dir\n");
+ retval = -ENOTDIR;
+ goto err_create_dir;
+ }
+
+ return 0;
+
+err_create_dir:
+ device_destroy(device_class, tcm->char_dev_num);
+err_create_device:
+ class_destroy(device_class);
+err_create_class:
+ cdev_del(&tcm->char_dev);
+err_add_chardev:
+ unregister_chrdev_region(tcm->char_dev_num, 1);
+err_alloc_chrdev_region:
+err_register_chrdev_region:
+ return retval;
+}
+/**
+ * syna_cdev_remove_sysfs()
+ *
+ * Remove the allocate cdev device node and release the resource
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * none.
+ */
+void syna_cdev_remove_sysfs(struct syna_tcm *tcm)
+{
+ if (!tcm) {
+ LOGE("Invalid tcm driver handle\n");
+ return;
+ }
+
+ syna_sysfs_remove_dir(tcm);
+
+ syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES);
+ syna_cdev_clean_queue(tcm);
+ syna_pal_mutex_free(&g_fifo_queue_mutex);
+
+ tcm->char_dev_ref_count = 0;
+ tcm->proc_pid = 0;
+
+ if (tcm->device) {
+ device_destroy(tcm->device_class, tcm->char_dev_num);
+ class_destroy(tcm->device_class);
+ cdev_del(&tcm->char_dev);
+ unregister_chrdev_region(tcm->char_dev_num, 1);
+ }
+
+ syna_tcm_buf_release(&g_cdev_cbuf);
+
+ syna_pal_mutex_free(&g_extif_mutex);
+
+ tcm->device_class = NULL;
+
+ tcm->device = NULL;
+}
+
+
diff --git a/syna_tcm2_testing.c b/syna_tcm2_testing.c
new file mode 100644
index 0000000..c41a92e
--- /dev/null
+++ b/syna_tcm2_testing.c
@@ -0,0 +1,660 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file syna_tcm2_testing.c
+ *
+ * This file implements the sample code to perform chip testing.
+ */
+#include "syna_tcm2_testing.h"
+#include "syna_tcm2_testing_limits.h"
+#include "synaptics_touchcom_core_dev.h"
+#include "synaptics_touchcom_func_base.h"
+
+/* g_testing_dir represents the root folder of testing sysfs
+ */
+static struct kobject *g_testing_dir;
+static struct syna_tcm *g_tcm_ptr;
+
+
+/**
+ * syna_testing_compare_byte_vector()
+ *
+ * Sample code to compare the test result with limits
+ * by byte vector
+ *
+ * @param
+ * [ in] data: target test data
+ * [ in] data_size: size of test data
+ * [ in] limit: test limit value to be compared with
+ * [ in] limit_size: size of test limit
+ *
+ * @return
+ * on success, true; otherwise, return false
+ */
+static bool syna_testing_compare_byte_vector(unsigned char *data,
+ unsigned int data_size, const unsigned char *limit,
+ unsigned int limit_size)
+{
+ bool result = false;
+ unsigned char tmp;
+ unsigned char p, l;
+ int i, j;
+
+ if (!data || (data_size == 0)) {
+ LOGE("Invalid test data\n");
+ return false;
+ }
+ if (!limit || (limit_size == 0)) {
+ LOGE("Invalid limits\n");
+ return false;
+ }
+
+ if (limit_size < data_size) {
+ LOGE("Limit size mismatched, data size: %d, limits: %d\n",
+ data_size, limit_size);
+ return false;
+ }
+
+ result = true;
+ for (i = 0; i < data_size; i++) {
+ tmp = data[i];
+
+ for (j = 0; j < 8; j++) {
+ p = GET_BIT(tmp, j);
+ l = GET_BIT(limit[i], j);
+ if (p != l) {
+ LOGE("Fail on TRX-%03d (data:%X, limit:%X)\n",
+ (i*8 + j), p, l);
+ result = false;
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * syna_testing_compare_frame()
+ *
+ * Sample code to compare the test result with limits
+ * by a lower-bound frame
+ *
+ * @param
+ * [ in] data: target test data
+ * [ in] data_size: size of test data
+ * [ in] rows: the number of rows
+ * [ in] cols: the number of column
+ * [ in] limits_hi: upper-bound test limit
+ * [ in] limits_lo: lower-bound test limit
+ *
+ * @return
+ * on success, true; otherwise, return false
+ */
+static bool syna_testing_compare_frame(unsigned char *data,
+ unsigned int data_size, int rows, int cols,
+ const short *limits_hi, const short *limits_lo)
+{
+ bool result = false;
+ short *data_ptr = NULL;
+ short limit;
+ int i, j;
+
+ if (!data || (data_size == 0)) {
+ LOGE("Invalid test data\n");
+ return false;
+ }
+
+ if (data_size < (2 * rows * cols)) {
+ LOGE("Size mismatched, data:%d (exppected:%d)\n",
+ data_size, (2 * rows * cols));
+ result = false;
+ return false;
+ }
+
+ if (rows > LIMIT_BOUNDARY) {
+ LOGE("Rows mismatched, rows:%d (exppected:%d)\n",
+ rows, LIMIT_BOUNDARY);
+ result = false;
+ return false;
+ }
+
+ if (cols > LIMIT_BOUNDARY) {
+ LOGE("Columns mismatched, cols: %d (exppected:%d)\n",
+ cols, LIMIT_BOUNDARY);
+ result = false;
+ return false;
+ }
+
+ result = true;
+
+ if (!limits_hi)
+ goto end_of_upper_bound_limit;
+
+ data_ptr = (short *)&data[0];
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < cols; j++) {
+ limit = limits_hi[i * LIMIT_BOUNDARY + j];
+ if (*data_ptr > limit) {
+ LOGE("Fail on (%2d,%2d)=%5d, limits_hi:%4d\n",
+ i, j, *data_ptr, limit);
+ result = false;
+ }
+ data_ptr++;
+ }
+ }
+
+end_of_upper_bound_limit:
+
+ if (!limits_lo)
+ goto end_of_lower_bound_limit;
+
+ data_ptr = (short *)&data[0];
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < cols; j++) {
+ limit = limits_lo[i * LIMIT_BOUNDARY + j];
+ if (*data_ptr < limit) {
+ LOGE("Fail on (%2d,%2d)=%5d, limits_lo:%4d\n",
+ i, j, *data_ptr, limit);
+ result = false;
+ }
+ data_ptr++;
+ }
+ }
+
+end_of_lower_bound_limit:
+ return result;
+}
+
+/**
+ * syna_testing_device_id()
+ *
+ * Sample code to ensure the device id is expected
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_testing_device_id(struct syna_tcm *tcm)
+{
+ int retval;
+ bool result;
+ struct tcm_identification_info info;
+ char *strptr = NULL;
+
+ LOGI("Start testing\n");
+
+ retval = syna_tcm_identify(tcm->tcm_dev, &info);
+ if (retval < 0) {
+ LOGE("Fail to get identification\n");
+ result = false;
+ goto exit;
+ }
+
+ strptr = strnstr(info.part_number,
+ device_id_limit,
+ strlen(info.part_number));
+ if (strptr != NULL)
+ result = true;
+ else {
+ LOGE("Device ID mismatched, FW: %s (limit: %s)\n",
+ info.part_number, device_id_limit);
+ result = false;
+ }
+
+exit:
+ LOGI("Result = %s\n", (result)?"pass":"fail");
+
+ return ((result) ? 0 : -1);
+}
+
+/**
+ * syna_testing_config_id()
+ *
+ * Sample code to ensure the config id is expected
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_testing_config_id(struct syna_tcm *tcm)
+{
+ int retval;
+ bool result;
+ struct tcm_application_info info;
+ int idx;
+
+ LOGI("Start testing\n");
+
+ retval = syna_tcm_get_app_info(tcm->tcm_dev, &info);
+ if (retval < 0) {
+ LOGE("Fail to get app info\n");
+ result = false;
+ goto exit;
+ }
+
+ result = true;
+ for (idx = 0; idx < sizeof(config_id_limit); idx++) {
+ if (config_id_limit[idx] != info.customer_config_id[idx]) {
+ LOGE("Fail on byte.%d (data: %02X, limit: %02X)\n",
+ idx, info.customer_config_id[idx],
+ config_id_limit[idx]);
+ result = false;
+ }
+ }
+
+exit:
+ LOGI("Result = %s\n", (result)?"pass":"fail");
+
+ return ((result) ? 0 : -1);
+}
+
+/**
+ * syna_testing_check_id_show()
+ *
+ * Attribute to show the result of ID comparsion to the console.
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [out] buf: string buffer shown on console
+ *
+ * @return
+ * on success, number of characters being output;
+ * otherwise, negative value on error.
+ */
+static ssize_t syna_testing_check_id_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int retval;
+ unsigned int count = 0;
+ struct syna_tcm *tcm = g_tcm_ptr;
+
+ if (!tcm->is_connected) {
+ retval = snprintf(buf, PAGE_SIZE,
+ "Device is NOT connected\n");
+ goto exit;
+ }
+
+ count = 0;
+
+ retval = syna_testing_device_id(tcm);
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Device ID check: %s\n",
+ (retval < 0) ? "fail" : "pass");
+
+ buf += retval;
+ count += retval;
+
+ retval = syna_testing_config_id(tcm);
+
+ retval = snprintf(buf, PAGE_SIZE - count,
+ "Config ID check: %s\n",
+ (retval < 0) ? "fail" : "pass");
+
+ buf += retval;
+ count += retval;
+
+ retval = count;
+exit:
+ return retval;
+}
+
+static struct kobj_attribute kobj_attr_check_id =
+ __ATTR(check_id, 0444, syna_testing_check_id_show, NULL);
+
+/**
+ * syna_testing_pt01()
+ *
+ * Sample code to perform PT01 testing
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_testing_pt01(struct syna_tcm *tcm)
+{
+ int retval;
+ bool result = false;
+ struct tcm_buffer test_data;
+
+ syna_tcm_buf_init(&test_data);
+
+ LOGI("Start testing\n");
+
+ retval = syna_tcm_run_production_test(tcm->tcm_dev,
+ TEST_PID01_TRX_TRX_SHORTS,
+ &test_data);
+ if (retval < 0) {
+ LOGE("Fail to run test %d\n", TEST_PID01_TRX_TRX_SHORTS);
+ result = false;
+ goto exit;
+ }
+
+ result = syna_testing_compare_byte_vector(test_data.buf,
+ test_data.data_length,
+ pt01_limits,
+ ARRAY_SIZE(pt01_limits));
+
+exit:
+ LOGI("Result = %s\n", (result)?"pass":"fail");
+
+ syna_tcm_buf_release(&test_data);
+
+ return ((result) ? 0 : -1);
+}
+
+/**
+ * syna_testing_pt01_show()
+ *
+ * Attribute to show the result of PT01 test to the console.
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [out] buf: string buffer shown on console
+ *
+ * @return
+ * on success, number of characters being output;
+ * otherwise, negative value on error.
+ */
+static ssize_t syna_testing_pt01_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int retval;
+ unsigned int count = 0;
+ struct syna_tcm *tcm = g_tcm_ptr;
+
+ if (!tcm->is_connected) {
+ count = snprintf(buf, PAGE_SIZE,
+ "Device is NOT connected\n");
+ goto exit;
+ }
+
+ retval = syna_testing_pt01(tcm);
+
+ count = snprintf(buf, PAGE_SIZE,
+ "TEST PT$01: %s\n",
+ (retval < 0) ? "fail" : "pass");
+
+exit:
+ return count;
+}
+
+static struct kobj_attribute kobj_attr_pt01 =
+ __ATTR(pt01, 0444, syna_testing_pt01_show, NULL);
+
+/**
+ * syna_testing_pt05()
+ *
+ * Sample code to perform PT05 testing
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_testing_pt05(struct syna_tcm *tcm)
+{
+ int retval;
+ bool result = false;
+ struct tcm_buffer test_data;
+
+ syna_tcm_buf_init(&test_data);
+
+ LOGI("Start testing\n");
+
+ retval = syna_tcm_run_production_test(tcm->tcm_dev,
+ TEST_PID05_FULL_RAW_CAP,
+ &test_data);
+ if (retval < 0) {
+ LOGE("Fail to run test %d\n", TEST_PID05_FULL_RAW_CAP);
+ result = false;
+ goto exit;
+ }
+
+ result = syna_testing_compare_frame(test_data.buf,
+ test_data.data_length,
+ tcm->tcm_dev->rows,
+ tcm->tcm_dev->cols,
+ (const short *)&pt05_hi_limits[0],
+ (const short *)&pt05_lo_limits[0]);
+
+exit:
+ LOGI("Result = %s\n", (result)?"pass":"fail");
+
+ syna_tcm_buf_release(&test_data);
+
+ return ((result) ? 0 : -1);
+}
+
+/**
+ * syna_testing_pt05_show()
+ *
+ * Attribute to show the result of PT05 test to the console.
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [out] buf: string buffer shown on console
+ *
+ * @return
+ * on success, number of characters being output;
+ * otherwise, negative value on error.
+ */
+static ssize_t syna_testing_pt05_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int retval;
+ unsigned int count = 0;
+ struct syna_tcm *tcm = g_tcm_ptr;
+
+ if (!tcm->is_connected) {
+ count = snprintf(buf, PAGE_SIZE,
+ "Device is NOT connected\n");
+ goto exit;
+ }
+
+ retval = syna_testing_pt05(tcm);
+
+ count = snprintf(buf, PAGE_SIZE,
+ "TEST PT$05: %s\n", (retval < 0) ? "fail" : "pass");
+
+exit:
+ return count;
+}
+
+static struct kobj_attribute kobj_attr_pt05 =
+ __ATTR(pt05, 0444, syna_testing_pt05_show, NULL);
+
+/**
+ * syna_testing_pt0a()
+ *
+ * Sample code to perform PT0A testing
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+static int syna_testing_pt0a(struct syna_tcm *tcm)
+{
+ int retval;
+ bool result = false;
+ struct tcm_buffer test_data;
+
+ syna_tcm_buf_init(&test_data);
+
+ LOGI("Start testing\n");
+
+ retval = syna_tcm_run_production_test(tcm->tcm_dev,
+ TEST_PID10_DELTA_NOISE,
+ &test_data);
+ if (retval < 0) {
+ LOGE("Fail to run test %d\n", TEST_PID10_DELTA_NOISE);
+ result = false;
+ goto exit;
+ }
+
+ result = syna_testing_compare_frame(test_data.buf,
+ test_data.data_length,
+ tcm->tcm_dev->rows,
+ tcm->tcm_dev->cols,
+ (const short *)&pt0a_hi_limits[0],
+ (const short *)&pt0a_lo_limits[0]);
+
+exit:
+ LOGI("Result = %s\n", (result)?"pass":"fail");
+
+ syna_tcm_buf_release(&test_data);
+
+ return ((result) ? 0 : -1);
+}
+
+/**
+ * syna_testing_pt0a_show()
+ *
+ * Attribute to show the result of PT0A test to the console.
+ *
+ * @param
+ * [ in] kobj: an instance of kobj
+ * [ in] attr: an instance of kobj attribute structure
+ * [out] buf: string buffer shown on console
+ *
+ * @return
+ * on success, number of characters being output;
+ * otherwise, negative value on error.
+ */
+static ssize_t syna_testing_pt0a_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int retval;
+ unsigned int count = 0;
+ struct syna_tcm *tcm = g_tcm_ptr;
+
+ if (!tcm->is_connected) {
+ count = snprintf(buf, PAGE_SIZE,
+ "Device is NOT connected\n");
+ goto exit;
+ }
+
+ retval = syna_testing_pt0a(tcm);
+
+ count = snprintf(buf, PAGE_SIZE,
+ "TEST PT$0A: %s\n", (retval < 0) ? "fail" : "pass");
+
+exit:
+ return count;
+}
+
+static struct kobj_attribute kobj_attr_pt0a =
+ __ATTR(pt0a, 0444, syna_testing_pt0a_show, NULL);
+
+/*
+ * declaration of sysfs attributes
+ */
+static struct attribute *attrs[] = {
+ &kobj_attr_check_id.attr,
+ &kobj_attr_pt01.attr,
+ &kobj_attr_pt05.attr,
+ &kobj_attr_pt0a.attr,
+ NULL,
+};
+
+static struct attribute_group attr_testing_group = {
+ .attrs = attrs,
+};
+
+/**
+ * syna_testing_create_dir()
+ *
+ * Create a directory and register it with sysfs.
+ * Then, create all defined sysfs files.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] sysfs_dir: root directory of sysfs nodes
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_testing_create_dir(struct syna_tcm *tcm,
+ struct kobject *sysfs_dir)
+{
+ int retval = 0;
+
+ g_testing_dir = kobject_create_and_add("testing",
+ sysfs_dir);
+ if (!g_testing_dir) {
+ LOGE("Fail to create testing directory\n");
+ return -EINVAL;
+ }
+
+ retval = sysfs_create_group(g_testing_dir, &attr_testing_group);
+ if (retval < 0) {
+ LOGE("Fail to create sysfs group\n");
+
+ kobject_put(g_testing_dir);
+ return retval;
+ }
+
+ g_tcm_ptr = tcm;
+
+ return 0;
+}
+/**
+ *syna_testing_remove_dir()
+ *
+ * Remove the allocate sysfs directory
+ *
+ * @param
+ * none
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+void syna_testing_remove_dir(void)
+{
+ if (g_testing_dir) {
+ sysfs_remove_group(g_testing_dir, &attr_testing_group);
+
+ kobject_put(g_testing_dir);
+ }
+}
diff --git a/syna_tcm2_testing.h b/syna_tcm2_testing.h
new file mode 100644
index 0000000..bbc7254
--- /dev/null
+++ b/syna_tcm2_testing.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved.
+ *
+ * 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_TCM2_TESTING_H_
+#define _SYNAPTICS_TCM2_TESTING_H_
+
+#include "syna_tcm2.h"
+
+/**
+ * syna_testing_create_dir()
+ *
+ * Create a directory and register it with sysfs.
+ * Then, create all defined sysfs files.
+ *
+ * @param
+ * [ in] tcm: the driver handle
+ * [ in] sysfs_dir: root directory of sysfs nodes
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+int syna_testing_create_dir(struct syna_tcm *tcm,
+ struct kobject *sysfs_dir);
+/**
+ *syna_testing_remove_dir()
+ *
+ * Remove the allocate sysfs directory
+ *
+ * @param
+ * none
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+void syna_testing_remove_dir(void);
+
+
+#endif /* end of _SYNAPTICS_TCM2_TESTING_H_ */
diff --git a/syna_tcm2_testing_limits.h b/syna_tcm2_testing_limits.h
new file mode 100644
index 0000000..46b32bb
--- /dev/null
+++ b/syna_tcm2_testing_limits.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved.
+ *
+ * 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_TCM2_TESTING_LIMITS_H_
+#define _SYNAPTICS_TCM2_TESTING_LIMITS_H_
+
+#define LIMIT_BOUNDARY (40)
+
+/* test limit for the device id checking */
+static const char *device_id_limit = "3908";
+
+/* test limit for the config id checking */
+static const unsigned char config_id_limit[16] = {
+ 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/**
+ * @section test limit for PT01 testing
+ */
+static const unsigned char pt01_limits[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/**
+ * @section test limit for the PT05 testing
+ */
+static const short pt05_hi_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = {
+/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */
+/* 00 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 01 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 02 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 03 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 04 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 05 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 06 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 07 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 08 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 09 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 10 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 11 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 12 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 13 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 14 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 15 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 16 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 17 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 18 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 19 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 20 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 21 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 22 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 23 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 24 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 25 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 26 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 27 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 28 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 29 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 30 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 31 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 32 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 33 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 34 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 35 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 36 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 37 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 38 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+/* 39 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000,
+};
+
+static const short pt05_lo_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = {
+/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */
+/* 00 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 01 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 02 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 03 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 04 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 05 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 06 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 07 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 08 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 09 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 10 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 11 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 12 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 13 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 14 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 15 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 16 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 17 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 18 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 19 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 20 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 21 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 22 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 23 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 24 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 25 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 26 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 27 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 28 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 29 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 30 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 31 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 32 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 33 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 34 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 35 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 36 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 37 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 38 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+/* 39 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
+};
+
+/**
+ * @section test limit for the PT0A testing
+ */
+static const short pt0a_hi_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = {
+/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */
+/* 00 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 01 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 02 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 03 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 04 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 05 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 06 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 07 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 08 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 09 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 10 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 11 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 12 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 13 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 14 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 15 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 16 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 17 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 18 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 19 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 20 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 21 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 22 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 23 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 24 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 25 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 26 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 27 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 28 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 29 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 30 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 31 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 32 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 33 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 34 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 35 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 36 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 37 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 38 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+/* 39 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+};
+
+static const short pt0a_lo_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = {
+/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */
+/* 00 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 01 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 02 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 03 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 04 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 05 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 06 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 07 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 08 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 09 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 10 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 11 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 12 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 13 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 14 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 15 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 16 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 17 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 18 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 19 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 20 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 21 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 22 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 23 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 24 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 25 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 26 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 27 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 28 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 29 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 30 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 31 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 32 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 33 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 34 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 35 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 36 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 37 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 38 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+/* 39 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+};
+
+#endif /* end of _SYNAPTICS_TCM2_TESTING_LIMITS_H_ */
diff --git a/tcm/synaptics_touchcom_core_dev.h b/tcm/synaptics_touchcom_core_dev.h
new file mode 100644
index 0000000..9ac2ee8
--- /dev/null
+++ b/tcm/synaptics_touchcom_core_dev.h
@@ -0,0 +1,1096 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file: synaptics_touchcom_core_dev.h
+ *
+ * This file is the topmost header file for Synaptics TouchComm device, also
+ * defines the TouchComm device context structure which will be passed to
+ * all other functions that expect a device handle.
+ */
+
+#ifndef _SYNAPTICS_TOUCHCOM_CORE_DEV_H_
+#define _SYNAPTICS_TOUCHCOM_CORE_DEV_H_
+
+
+#include "syna_tcm2_platform.h"
+
+
+#define SYNA_TCM_CORE_LIB_VERSION 0x0111
+
+
+/**
+ * @section: Parameters pre-defined
+ *
+ * @brief: MAX_NUM_OBJECTS
+ * Maximum number of objects being detected
+ *
+ * @brief: MAX_SIZE_GESTURE_DATA
+ * Maximum size of gesture data
+ *
+ * @brief: MAX_SIZE_CONFIG_ID
+ * Maximum size of customer configuration ID
+ */
+#define MAX_NUM_OBJECTS (10)
+
+#define MAX_SIZE_GESTURE_DATA (8)
+
+#define MAX_SIZE_CONFIG_ID (16)
+
+/**
+ * @section: Command-handling relevant definitions
+ *
+ * @brief: MESSAGE_HEADER_SIZE
+ * The size of message header
+ *
+ * @brief: CMD_RESPONSE_TIMEOUT_MS
+ * Time frame for a command execution
+ *
+ * @brief: CMD_RESPONSE_POLLING_DELAY_MS
+ * Generic time frame to check the response in polling
+ *
+ * @brief: RD_RETRY_US
+ * For retry reading, delay time range in microsecond
+ *
+ * @brief: WR_DELAY_US
+ * For continued writes, delay time range in microsecond
+ *
+ * @brief: TAT_DELAY_US
+ * For bus turn-around, delay time range in microsecond
+ *
+ * @brief: FW_MODE_SWITCH_DELAY_MS
+ * The default time for fw mode switching
+ *
+ * @brief: RESET_DELAY_MS
+ * The default time after reset in case it's not set properly
+ *
+ * @brief: FORCE_ATTN_DRIVEN
+ * Special flag to read in resp packet in ISR function
+ */
+#define MESSAGE_HEADER_SIZE (4)
+
+#define CMD_RESPONSE_TIMEOUT_MS (3000)
+
+#define CMD_RESPONSE_POLLING_DELAY_MS (10)
+
+#define RD_RETRY_US_MIN (5000)
+#define RD_RETRY_US_MAX (10000)
+
+#define WR_DELAY_US_MIN (500)
+#define WR_DELAY_US_MAX (1000)
+
+#define TAT_DELAY_US_MIN (100)
+#define TAT_DELAY_US_MAX (200)
+
+#define FW_MODE_SWITCH_DELAY_MS (200)
+
+#define RESET_DELAY_MS (200)
+
+#define DEFAULT_FLASH_ERASE_DELAY (~0)
+#define DEFAULT_FLASH_WRITE_DELAY (~0)
+#define DEFAULT_FLASH_READ_DELAY (~0)
+
+#define RESP_IN_ATTN (0)
+#define RESP_IN_POLLING (CMD_RESPONSE_POLLING_DELAY_MS)
+
+/**
+ * @section: Macro to show string in log
+ */
+#define STR(x) #x
+
+/**
+ * @section: Helpers to check the device mode
+ */
+#define IS_APP_FW_MODE(mode) \
+ (mode == MODE_APPLICATION_FIRMWARE)
+
+#define IS_NOT_APP_FW_MODE(mode) \
+ (!IS_APP_FW_MODE(mode))
+
+#define IS_BOOTLOADER_MODE(mode) \
+ ((mode == MODE_BOOTLOADER) || \
+ (mode == MODE_TDDI_BOOTLOADER) || \
+ (mode == MODE_TDDI_HDL_BOOTLOADER) || \
+ (mode == MODE_MULTICHIP_TDDI_BOOTLOADER))
+
+#define IS_ROM_BOOTLOADER_MODE(mode) \
+ (mode == MODE_ROMBOOTLOADER)
+
+
+/**
+ * @section: Types for lower-level bus being used
+ */
+enum bus_connection {
+ BUS_TYPE_NONE,
+ BUS_TYPE_I2C,
+ BUS_TYPE_SPI,
+ BUS_TYPE_I3C,
+};
+
+/**
+ * @section: TouchComm Firmware Modes
+ *
+ * The current mode running is defined in Identify Info Packet.
+ */
+enum tcm_firmware_mode {
+ MODE_UNKNOWN = 0x00,
+ MODE_APPLICATION_FIRMWARE = 0x01,
+ MODE_HOSTDOWNLOAD_FIRMWARE = 0x02,
+ MODE_ROMBOOTLOADER = 0x04,
+ MODE_BOOTLOADER = 0x0b,
+ MODE_TDDI_BOOTLOADER = 0x0c,
+ MODE_TDDI_HDL_BOOTLOADER = 0x0d,
+ MODE_PRODUCTIONTEST_FIRMWARE = 0x0e,
+ MODE_MULTICHIP_TDDI_BOOTLOADER = 0xab,
+};
+
+/**
+ * @section: Status of Application Firmware
+ *
+ * The current status is defined in Application Info Packet.
+ */
+enum tcm_app_status {
+ APP_STATUS_OK = 0x00,
+ APP_STATUS_BOOTING = 0x01,
+ APP_STATUS_UPDATING = 0x02,
+ APP_STATUS_BAD_APP_CONFIG = 0xff,
+};
+
+/**
+ * @section: Field IDs in Dynamic Configuration
+ *
+ * The codes specify the generic dynamic configuration options.
+ */
+enum dynamic_tcm_config_id {
+ DC_UNKNOWN = 0x00,
+ DC_DISABLE_DOZE = 0x01,
+ DC_DISABLE_NOISE_MITIGATION = 0x02,
+ DC_DISABLE_FREQUENCY_SHIFT = 0x03,
+ DC_REQUEST_FREQUENCY_INDEX = 0x04,
+ DC_DISABLE_HSYNC = 0x05,
+ DC_REZERO_ON_EXIT_DEEP_SLEEP = 0x06,
+ DC_ENABLE_CHARGER_CONNECTED = 0x07,
+ DC_DISABLE_BASELINE_RELAXATION = 0x08,
+ DC_ENABLE_WAKEUP_GESTURE_MODE = 0x09,
+ DC_REQUEST_TESTING_FINGERS = 0x0a,
+ DC_ENABLE_GRIP_SUPPRESSION = 0x0b,
+ DC_ENABLE_THICK_GLOVE = 0x0c,
+ DC_ENABLE_GLOVE = 0x0d,
+ DC_ENABLE_FACE_DETECTION = 0x0e,
+ DC_INHIBIT_ACTIVE_GESTURE = 0x0f,
+ DC_DISABLE_PROXIMITY = 0x10,
+};
+
+/**
+ * @section: TouchComm Commands
+ *
+ * List the generic commands supported in TouchComm command-response protocol.
+ */
+enum tcm_command {
+ CMD_NONE = 0x00,
+ CMD_CONTINUE_WRITE = 0x01,
+ CMD_IDENTIFY = 0x02,
+ CMD_RESET = 0x04,
+ CMD_ENABLE_REPORT = 0x05,
+ CMD_DISABLE_REPORT = 0x06,
+ CMD_TCM2_ACK = 0x07,
+ CMD_TCM2_RETRY = 0x08,
+ CMD_TCM2_SET_MAX_READ_LENGTH = 0x09,
+ CMD_TCM2_GET_REPORT = 0x0a,
+ 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,
+ CMD_GET_ROMBOOT_INFO = 0x40,
+ CMD_WRITE_PROGRAM_RAM = 0x41,
+ CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE = 0x42,
+ CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED = 0x43,
+ CMD_ENTER_IO_BRIDGE_MODE = 0x44,
+ CMD_ROMBOOT_DOWNLOAD = 0x45,
+};
+
+/**
+ * @section: TouchComm Status Codes
+ *
+ * Define the following status codes for all command responses.
+ * 0x00: (v1) no commands are pending and no reports are available.
+ * 0x01: (v1 & v2) the previous command succeeded.
+ * 0x03: (v1 & v2) the payload continues a previous response.
+ * 0x04: (v2) command was written, but no reports were available.
+ * 0x07: (v2) the previous write was successfully received.
+ * 0x08: (v2) the previous write was corrupt. The host should resend.
+ * 0x09: (v2) the previous command failed.
+ * 0x0c: (v1 & v2) write was larger than the device's receive buffer.
+ * 0x0d: (v1 & v2) a command was sent before the previous command completed.
+ * 0x0e: (v1 & v2) the requested command is not implemented.
+ * 0x0f: (v1 & v2) generic communication error, probably incorrect payload.
+ *
+ * 0xfe: self-defined status for a corrupted packet.
+ * 0xff: self-defined status for an invalid data.
+ */
+enum tcm_status_code {
+ STATUS_IDLE = 0x00,
+ STATUS_OK = 0x01,
+ STATUS_CONTINUED_READ = 0x03,
+ STATUS_NO_REPORT_AVAILABLE = 0x04,
+ STATUS_ACK = 0x07,
+ STATUS_RETRY_REQUESTED = 0x08,
+ STATUS_CMD_FAILED = 0x09,
+ STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
+ STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
+ STATUS_NOT_IMPLEMENTED = 0x0e,
+ STATUS_ERROR = 0x0f,
+ STATUS_PACKET_CORRUPTED = 0xfe,
+ STATUS_INVALID = 0xff,
+};
+
+/**
+ * @section: TouchComm Report Codes
+ *
+ * Define the following report codes generated by TouchComm firmware.
+ * 0x10: Identify Info Packet
+ * 0x11: Touch Report
+ * 0x12: Delta Cap. Image
+ * 0x13: Raw Cap. Image
+ */
+enum tcm_report_type {
+ REPORT_IDENTIFY = 0x10,
+ REPORT_TOUCH = 0x11,
+ REPORT_DELTA = 0x12,
+ REPORT_RAW = 0x13,
+};
+
+/**
+ * @section: States in Command Processing
+ *
+ * List the states in command processing.
+ */
+enum tcm_command_status {
+ CMD_STATE_IDLE = 0,
+ CMD_STATE_BUSY = 1,
+ CMD_STATE_ERROR = -1,
+};
+
+/**
+ * @section: Production Test Items
+ *
+ * List the generic production test items
+ */
+enum tcm_test_code {
+ TEST_NOT_IMPLEMENTED = 0x00,
+
+ TEST_PID01_TRX_TRX_SHORTS = 0x01,
+ TEST_PID02_TRX_SENSOR_OPENS = 0x02,
+ TEST_PID03_TRX_GROUND_SHORTS = 0x03,
+ TEST_PID05_FULL_RAW_CAP = 0x05,
+ TEST_PID06_EE_SHORT = 0x06,
+ TEST_PID07_DYNAMIC_RANGE = 0x07,
+ TEST_PID08_HIGH_RESISTANCE = 0x08,
+ TEST_PID10_DELTA_NOISE = 0x0a,
+ TEST_PID11_OPEN_DETECTION = 0x0b,
+ TEST_PID12 = 0x0c,
+ TEST_PID13 = 0x0d,
+ TEST_PID14_DOZE_DYNAMIC_RANGE = 0x0e,
+ TEST_PID15_DOZE_NOISE = 0x0f,
+ TEST_PID16_SENSOR_SPEED = 0x10,
+ TEST_PID17_ADC_RANGE = 0x11,
+ TEST_PID18_HYBRID_ABS_RAW = 0x12,
+ TEST_PID29_HYBRID_ABS_NOISE = 0x1D,
+
+ TEST_PID_MAX,
+};
+
+
+/**
+ * @section: Internal Buffer Structure
+ *
+ * This structure is taken as the internal common buffer.
+ */
+struct tcm_buffer {
+ unsigned char *buf;
+ unsigned int buf_size;
+ unsigned int data_length;
+ syna_pal_mutex_t buf_mutex;
+ unsigned char ref_cnt;
+};
+
+/**
+ * @section: TouchComm Identify Info Packet
+ * Ver.1: size is 24 (0x18) bytes
+ * Ver.2: size is extended to 32 (0x20) bytes
+ *
+ * The identify packet provides the basic TouchComm information and indicate
+ * that the device is ready to receive commands.
+ *
+ * The report is received whenever the device initially powers up, resets,
+ * or switches fw between bootloader and application modes.
+ */
+struct tcm_identification_info {
+ unsigned char version;
+ unsigned char mode;
+ unsigned char part_number[16];
+ unsigned char build_id[4];
+ unsigned char max_write_size[2];
+ /* extension in ver.2 */
+ unsigned char max_read_size[2];
+ unsigned char reserved[6];
+};
+
+
+/**
+ * @section: TouchComm Application Information Packet
+ *
+ * The application info packet provides the information about the application
+ * firmware as well as the touch controller.
+ */
+struct tcm_application_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[MAX_SIZE_CONFIG_ID];
+ 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];
+ unsigned char num_of_force_elecs[2];
+};
+
+/**
+ * @section: TouchComm boot information packet
+ *
+ * The boot info packet provides the information of TouchBoot.
+ */
+struct 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];
+ /* extension in ver.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];
+};
+
+/**
+ * @section: TouchComm ROMboot information packet
+ *
+ * The ROMboot info packet provides the information of ROM bootloader.
+ */
+struct tcm_romboot_info {
+ unsigned char version;
+ unsigned char status;
+ unsigned char asic_id[2];
+ unsigned char write_block_size_words;
+ unsigned char max_write_payload_size[2];
+ unsigned char last_reset_reason;
+ unsigned char pc_at_time_of_last_reset[2];
+};
+
+/**
+ * @section: TouchComm features description packet
+ *
+ * The features description packet tells which features are supported.
+ */
+struct tcm_features_info {
+ unsigned char byte[16];
+};
+
+/**
+ * @section: Data blob for touch data reported
+ *
+ * Once receiving a touch report generated by firmware, the touched data
+ * will be parsed and converted to touch_data_blob structure as a data blob.
+ *
+ * @subsection: tcm_touch_data_blob
+ * The touch_data_blob contains all sorts of touched data entities.
+ *
+ * @subsection tcm_objects_data_blob
+ * The objects_data_blob includes the data for each active objects.
+ *
+ * @subsection tcm_gesture_data_blob
+ * The gesture_data_blob contains the gesture data if detected.
+ */
+struct tcm_objects_data_blob {
+ 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 tcm_gesture_data_blob {
+ union {
+ struct {
+ unsigned char tap_x[2];
+ unsigned char tap_y[2];
+ };
+ struct {
+ unsigned char swipe_x[2];
+ unsigned char swipe_y[2];
+ unsigned char swipe_direction[2];
+ };
+ unsigned char data[MAX_SIZE_GESTURE_DATA];
+ };
+};
+struct tcm_touch_data_blob {
+
+ /* for each active objects */
+ unsigned int num_of_active_objects;
+ struct tcm_objects_data_blob object_data[MAX_NUM_OBJECTS];
+
+ /* for gesture */
+ unsigned int gesture_id;
+ struct tcm_gesture_data_blob gesture_data;
+
+ /* various data */
+ unsigned int timestamp;
+ unsigned int buttons_state;
+ 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_cpu_cycles;
+ unsigned int fd_data;
+ unsigned int force_data;
+ unsigned int fingerprint_area_meet;
+ unsigned int sensing_mode;
+};
+
+/**
+ * @section: Callback function used for custom entity parsing in touch report
+ *
+ * Allow to implement the custom handling for"new" custom entity in touch report
+ *
+ * @param
+ * [ in] code: the code of current touch entity
+ * [ in] config: the report configuration stored
+ * [in/out] config_offset: offset of current position in report config,
+ * the updated position should be returned.
+ * [ in] report: touch report given
+ * [in/out] report_offset: offset of current position in touch report,
+ * the updated position should be returned.
+ * [ in] report_size: size of given touch report
+ * [ in] callback_data: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+typedef int (*tcm_touch_data_parse_callback_t) (const unsigned char code,
+ const unsigned char *config, unsigned int *config_offset,
+ const unsigned char *report, unsigned int *report_offset,
+ unsigned int report_size, void *callback_data);
+
+/**
+ * @section: Callback function used for custom gesture parsing in touch report
+ *
+ * Allow to implement the custom handling for gesture data.
+ *
+ * @param
+ * [ in] code: the code of current touch entity
+ * [ in] config: the report configuration stored
+ * [in/out] config_offset: offset of current position in report config,
+ * the updated position should be returned.
+ * [ in] report: touch report given
+ * [in/out] report_offset: offset of current position in touch report,
+ * the updated position should be returned.
+ * [ in] report_size: size of given touch report
+ * [ in] callback_data: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+typedef int (*tcm_gesture_parse_callback_t) (const unsigned char code,
+ const unsigned char *config, unsigned int *config_offset,
+ const unsigned char *report, unsigned int *report_offset,
+ unsigned int report_size, void *callback_data);
+
+/**
+ * @section: TouchComm Message Handling Wrapper
+ *
+ * The structure contains the essential buffers and parameters to implement
+ * the command-response protocol for both TouchCom ver.1 and TouchCom ver.2.
+ */
+struct tcm_message_data_blob {
+
+ /* parameters for command processing */
+ syna_pal_atomic_t command_status;
+ unsigned char command;
+ unsigned char status_report_code;
+ unsigned int payload_length;
+ unsigned char response_code;
+ unsigned char report_code;
+ unsigned char seq_toggle;
+ unsigned int default_resp_reading;
+
+ /* completion event command processing */
+ syna_pal_completion_t cmd_completion;
+
+ /* internal buffers
+ * in : buffer storing the data being read 'in'
+ * out : buffer storing the data being sent 'out'
+ * temp: 'temp' buffer used for continued read operation
+ */
+ struct tcm_buffer in;
+ struct tcm_buffer out;
+ struct tcm_buffer temp;
+
+ /* mutex for the protection of command processing */
+ syna_pal_mutex_t cmd_mutex;
+
+ /* mutex for the read/write protection */
+ syna_pal_mutex_t rw_mutex;
+
+};
+
+/**
+ * @section: TouchComm core device context structure
+ *
+ * The device context contains parameters and internal buffers, that will
+ * be passed to all other functions that expect a device handle.
+ *
+ * Calling syna_tcm_allocate_device() can allocate this structure, and
+ * syna_tcm_remove_device() releases the structure if no longer needed.
+ */
+struct tcm_dev {
+
+ /* basic device information */
+ unsigned char dev_mode;
+ unsigned int packrat_number;
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int max_objects;
+ unsigned int rows;
+ unsigned int cols;
+ unsigned char config_id[MAX_SIZE_CONFIG_ID];
+
+ /* capability of read/write transferred
+ * being assigned through syna_hw_interface
+ */
+ unsigned int max_wr_size;
+ unsigned int max_rd_size;
+
+ /* hardware-specific data structure
+ * defined in syna_touchcom_platform.h
+ */
+ struct syna_hw_interface *hw_if;
+
+ /* TouchComm defined structures */
+ struct tcm_identification_info id_info;
+ struct tcm_application_info app_info;
+ struct tcm_boot_info boot_info;
+
+ /* internal buffers
+ * report: record the TouchComm report to caller
+ * resp : record the command response to caller
+ */
+ struct tcm_buffer report_buf;
+ struct tcm_buffer resp_buf;
+ struct tcm_buffer external_buf;
+
+ /* touch report configuration */
+ struct tcm_buffer touch_config;
+
+ /* TouchComm message handling wrapper */
+ struct tcm_message_data_blob msg_data;
+
+ /* indicate that fw update is on-going */
+ syna_pal_atomic_t firmware_flashing;
+
+ /* abstraction to read a TouchComm message from device.
+ * Function will be assigned by syna_tcm_detect_device().
+ *
+ * After read_message() returned, the retrieved data will be available
+ * and stored either in buffer.report or buffer.resp based on the
+ * code returned.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] status_report_code: status code or report code received
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+ int (*read_message)(struct tcm_dev *tcm_dev,
+ unsigned char *status_report_code);
+
+ /* abstraction to write a TouchComm message to device and retrieve the
+ * response to command.
+ * Function will be assigned by syna_tcm_detect_device().
+ *
+ * After calling write_message(), the response code is returned
+ * and the response data is stored in buffer.resp.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: TouchComm command to write
+ * [ in] payload: data payload, if any
+ * [ in] payload_length: length of data payload, if any
+ * [out] resp_code: response code returned
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the polling time;
+ * or, set '0' (RESP_IN_ATTN) for ATTN driven
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+ int (*write_message)(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_length, unsigned char *resp_code,
+ unsigned int delay_ms_resp);
+
+
+ /* callbacks
+ * custom_touch_data_parse_func: custom touch data entity parsing
+ * custom_gesture_parse_func : custom gesture data entity parsing
+ */
+ tcm_touch_data_parse_callback_t custom_touch_data_parse_func;
+ void *cbdata_touch_data_parse;
+ tcm_gesture_parse_callback_t custom_gesture_parse_func;
+ void *cbdata_gesture_parse;
+
+};
+/* end of structure syna_tcm_dev */
+
+
+/**
+ * @section: Protocol detection
+ *
+ * @brief: syna_tcm_v1_detect
+ * Check whether TouchComm ver.1 firmware is running
+ *
+ * @brief: syna_tcm_v2_detect
+ * Check whether TouchComm ver.2 firmware is running
+ */
+
+/* syna_tcm_v1_detect()
+ *
+ * Check whether TouchComm ver.1 firmware is running.
+ * Function is implemented in synaptics_tcm2_core_v1.c.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: raw 4-byte data
+ * [ in] size: length of input data in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_v1_detect(struct tcm_dev *tcm_dev, unsigned char *data,
+ unsigned int data_len);
+
+/* syna_tcm_v2_detect()
+ *
+ * Check whether TouchComm ver.2 firmware is running.
+ * Function is implemented in synaptics_tcm2_core_v2.c.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: raw 4-byte data
+ * [ in] size: length of input data in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_v2_detect(struct tcm_dev *tcm_dev, unsigned char *data,
+ unsigned int data_len);
+
+/**
+ * @section: Buffers Management helpers
+ *
+ * @brief: syna_tcm_buf_alloc
+ * Allocate the requested memory space for the buffer structure
+ *
+ * @brief: syna_tcm_buf_realloc
+ * Extend the requested memory space for the buffer structure
+ *
+ * @brief: syna_tcm_buf_init
+ * Initialize the buffer structure
+ *
+ * @brief: syna_tcm_buf_lock
+ * Protect the access of current buffer structure
+ *
+ * @brief: syna_tcm_buf_unlock
+ * Open the access of current buffer structure
+ *
+ * @brief: syna_tcm_buf_release
+ * Release the buffer structure
+ */
+
+/**
+ * syna_tcm_buf_alloc()
+ *
+ * Allocate the requested memory space for the given buffer only if
+ * the existed buffer is not enough for the requirement.
+ *
+ * @param
+ * [ in] pbuf: pointer to an internal buffer
+ * [ in] size: required size to be allocated
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static inline int syna_tcm_buf_alloc(struct tcm_buffer *pbuf,
+ unsigned int size)
+{
+ if (!pbuf) {
+ LOGE("Invalid buffer structure\n");
+ return -1;
+ }
+
+ if (size > pbuf->buf_size) {
+ if (pbuf->buf)
+ syna_pal_mem_free((void *)pbuf->buf);
+
+ pbuf->buf = syna_pal_mem_alloc(size, sizeof(unsigned char));
+ if (!(pbuf->buf)) {
+ LOGE("Fail to allocate memory (size = %d)\n",
+ (int)(size*sizeof(unsigned char)));
+ pbuf->buf_size = 0;
+ pbuf->data_length = 0;
+ return -1;
+ }
+ pbuf->buf_size = size;
+ }
+
+ syna_pal_mem_set(pbuf->buf, 0x00, pbuf->buf_size);
+ pbuf->data_length = 0;
+
+ return 0;
+}
+
+/**
+ * syna_tcm_buf_realloc()
+ *
+ * Extend the requested memory space for the given buffer only if
+ * the existed buffer is not enough for the requirement.
+ * Then, move the content to the new memory space.
+ *
+ * @param
+ * [ in] pbuf: pointer to an internal buffer
+ * [ in] size: required size to be extended
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static inline int syna_tcm_buf_realloc(struct tcm_buffer *pbuf,
+ unsigned int size)
+{
+ int retval;
+ unsigned char *temp_src;
+ unsigned int temp_size = 0;
+
+ if (!pbuf) {
+ LOGE("Invalid buffer structure\n");
+ return -1;
+ }
+
+ if (size > pbuf->buf_size) {
+ temp_src = pbuf->buf;
+ temp_size = pbuf->buf_size;
+
+ pbuf->buf = syna_pal_mem_alloc(size, sizeof(unsigned char));
+ if (!(pbuf->buf)) {
+ LOGE("Fail to allocate memory (size = %d)\n",
+ (int)(size * sizeof(unsigned char)));
+ syna_pal_mem_free((void *)temp_src);
+ pbuf->buf_size = 0;
+ return -1;
+ }
+
+ retval = syna_pal_mem_cpy(pbuf->buf,
+ size,
+ temp_src,
+ temp_size,
+ temp_size);
+ if (retval < 0) {
+ LOGE("Fail to copy data\n");
+ syna_pal_mem_free((void *)temp_src);
+ syna_pal_mem_free((void *)pbuf->buf);
+ pbuf->buf_size = 0;
+ return retval;
+ }
+
+ syna_pal_mem_free((void *)temp_src);
+ pbuf->buf_size = size;
+ }
+
+ return 0;
+}
+/**
+ * syna_tcm_buf_init()
+ *
+ * Initialize the buffer structure.
+ *
+ * @param
+ * [ in] pbuf: pointer to an internal buffer
+ *
+ * @return
+ * none
+ */
+static inline void syna_tcm_buf_init(struct tcm_buffer *pbuf)
+{
+ pbuf->buf_size = 0;
+ pbuf->data_length = 0;
+ pbuf->ref_cnt = 0;
+ pbuf->buf = NULL;
+ syna_pal_mutex_alloc(&pbuf->buf_mutex);
+}
+/**
+ * syna_tcm_buf_lock()
+ *
+ * Protect the access of current buffer structure.
+ *
+ * @param
+ * [ in] pbuf: pointer to an internal buffer
+ *
+ * @return
+ * none
+ */
+static inline void syna_tcm_buf_lock(struct tcm_buffer *pbuf)
+{
+ if (pbuf->ref_cnt != 0) {
+ LOGE("Buffer access out-of balance, %d\n", pbuf->ref_cnt);
+ return;
+ }
+
+ syna_pal_mutex_lock(&pbuf->buf_mutex);
+ pbuf->ref_cnt++;
+}
+/**
+ * syna_tcm_buf_unlock()
+ *
+ * Open the access of current buffer structure.
+ *
+ * @param
+ * [ in] pbuf: pointer to an internal buffer
+ *
+ * @return
+ * none
+ */
+static inline void syna_tcm_buf_unlock(struct tcm_buffer *pbuf)
+{
+ if (pbuf->ref_cnt != 1) {
+ LOGE("Buffer access out-of balance, %d\n", pbuf->ref_cnt);
+ return;
+ }
+
+ pbuf->ref_cnt--;
+ syna_pal_mutex_unlock(&pbuf->buf_mutex);
+}
+/**
+ * syna_tcm_buf_release()
+ *
+ * Release the buffer structure.
+ *
+ * @param
+ * [ in] pbuf: pointer to an internal buffer
+ *
+ * @return
+ * none
+ */
+static inline void syna_tcm_buf_release(struct tcm_buffer *pbuf)
+{
+ if (pbuf->ref_cnt != 0)
+ LOGE("Buffer access hold, %d\n", pbuf->ref_cnt);
+
+ syna_pal_mutex_free(&pbuf->buf_mutex);
+ syna_pal_mem_free((void *)pbuf->buf);
+ pbuf->buf_size = 0;
+ pbuf->data_length = 0;
+ pbuf->ref_cnt = 0;
+}
+/**
+ * syna_tcm_buf_copy()
+ *
+ * Helper to copy data from the source buffer to the destination buffer.
+ * The size of destination buffer may be reallocated, if the size is
+ * smaller than the actual data size to be copied.
+ *
+ * @param
+ * [out] dest: pointer to an internal buffer
+ * [ in] src: required size to be extended
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static inline int syna_tcm_buf_copy(struct tcm_buffer *dest,
+ struct tcm_buffer *src)
+{
+ int retval = 0;
+
+ if (dest->buf_size < src->data_length) {
+ retval = syna_tcm_buf_alloc(dest, src->data_length + 1);
+ if (retval < 0) {
+ LOGE("Fail to reallocate the given buffer, size: %d\n",
+ src->data_length + 1);
+ return retval;
+ }
+ }
+
+ /* copy data content to the destination */
+ retval = syna_pal_mem_cpy(dest->buf,
+ dest->buf_size,
+ src->buf,
+ src->buf_size,
+ src->data_length);
+ if (retval < 0) {
+ LOGE("Fail to copy data to caller, size: %d\n",
+ src->data_length);
+ return retval;
+ }
+
+ dest->data_length = src->data_length;
+
+ return retval;
+}
+/**
+ * @section: Reads / Writes Abstraction Function
+ *
+ * @brief: syna_tcm_read
+ * Read the data from bus directly
+ *
+ * @brief: syna_tcm_write
+ * Write the data to bus directly
+ */
+
+/**
+ * syna_tcm_read()
+ *
+ * The bare read function, reading in the requested data bytes
+ * from bus directly.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] rd_data: buffer for storing data retrieved from device
+ * [ in] rd_len: length of reading data in bytes
+ *
+ * @return
+ * the number of data bytes retrieved;
+ * otherwise, negative value, on error.
+ */
+static inline int syna_tcm_read(struct tcm_dev *tcm_dev,
+ unsigned char *rd_data, unsigned int rd_len)
+{
+ struct syna_hw_interface *hw_if;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ hw_if = tcm_dev->hw_if;
+ if (!hw_if->ops_read_data) {
+ LOGE("Invalid hw ops_read function\n");
+ return _ENODEV;
+ }
+
+ return hw_if->ops_read_data(hw_if, rd_data, rd_len);
+}
+
+/**
+ * syna_tcm_write()
+ *
+ * The bare write function, writing the given data bytes to bus directly.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] wr_data: written data
+ * [ in] wr_len: length of written data in bytes
+ *
+ * @return
+ * the number of data bytes retrieved;
+ * otherwise, negative value, on error.
+ */
+static inline int syna_tcm_write(struct tcm_dev *tcm_dev,
+ unsigned char *wr_data, unsigned int wr_len)
+{
+ struct syna_hw_interface *hw_if;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ hw_if = tcm_dev->hw_if;
+ if (!hw_if->ops_write_data) {
+ LOGE("Invalid hw ops_write function\n");
+ return _ENODEV;
+ }
+
+ return hw_if->ops_write_data(hw_if, wr_data, wr_len);
+}
+
+
+#endif /* end of _SYNAPTICS_TOUCHCOM_CORE_DEV_H_ */
diff --git a/tcm/synaptics_touchcom_core_v1.c b/tcm/synaptics_touchcom_core_v1.c
new file mode 100644
index 0000000..d10380f
--- /dev/null
+++ b/tcm/synaptics_touchcom_core_v1.c
@@ -0,0 +1,1078 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_core_v1.c
+ *
+ * This file implements the TouchComm version 1 command-response protocol.
+ */
+
+#include "synaptics_touchcom_core_dev.h"
+
+#define TCM_V1_MESSAGE_MARKER 0xa5
+#define TCM_V1_MESSAGE_PADDING 0x5a
+
+/**
+ * @section: Header of TouchComm v1 Message Packet
+ *
+ * The 4-byte header in the TouchComm v1 packet
+ */
+struct tcm_v1_message_header {
+ union {
+ struct {
+ unsigned char marker;
+ unsigned char code;
+ unsigned char length[2];
+ };
+ unsigned char data[MESSAGE_HEADER_SIZE];
+ };
+};
+
+/**
+ * syna_tcm_v1_parse_idinfo()
+ *
+ * Copy the given data to the identification info structure
+ * and parse the basic information, e.g. fw build id.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: data buffer
+ * [ in] size: size of given data buffer
+ * [ in] data_len: length of actual data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v1_parse_idinfo(struct tcm_dev *tcm_dev,
+ unsigned char *data, unsigned int size, unsigned int data_len)
+{
+ int retval;
+ unsigned int wr_size = 0;
+ unsigned int build_id = 0;
+ struct tcm_identification_info *id_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!data) || (data_len == 0)) {
+ LOGE("Invalid given data buffer\n");
+ return _EINVAL;
+ }
+
+ id_info = &tcm_dev->id_info;
+
+ retval = syna_pal_mem_cpy((unsigned char *)id_info,
+ sizeof(struct tcm_identification_info),
+ data,
+ size,
+ MIN(sizeof(*id_info), data_len));
+ if (retval < 0) {
+ LOGE("Fail to copy identification info\n");
+ return retval;
+ }
+
+ build_id = syna_pal_le4_to_uint(id_info->build_id);
+
+ wr_size = syna_pal_le2_to_uint(id_info->max_write_size);
+ tcm_dev->max_wr_size = MIN(wr_size, WR_CHUNK_SIZE);
+ if (tcm_dev->max_wr_size == 0) {
+ tcm_dev->max_wr_size = wr_size;
+ LOGD("max_wr_size = %d\n", tcm_dev->max_wr_size);
+ }
+
+ LOGI("TCM Fw mode: 0x%02x\n", id_info->mode);
+
+ if (tcm_dev->packrat_number != build_id)
+ tcm_dev->packrat_number = build_id;
+
+ tcm_dev->dev_mode = id_info->mode;
+
+ return 0;
+}
+
+/**
+ * syna_tcm_v1_dispatch_report()
+ *
+ * Handle the TouchCom report packet being received.
+ *
+ * If it's an identify report, parse the identification packet and signal
+ * the command completion just in case.
+ * Otherwise, copy the data from internal buffer.in to internal buffer.report
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+static void syna_tcm_v1_dispatch_report(struct tcm_dev *tcm_dev)
+{
+ int retval;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ tcm_msg->report_code = tcm_msg->status_report_code;
+
+ if (tcm_msg->payload_length == 0) {
+ tcm_dev->report_buf.data_length = 0;
+ goto exit;
+ }
+
+ /* The identify report may be resulted from reset or fw mode switching
+ */
+ if (tcm_msg->report_code == REPORT_IDENTIFY) {
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_tcm_v1_parse_idinfo(tcm_dev,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to identify device\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return;
+ }
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ /* in case, the identify info packet is caused by the command */
+ if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) {
+ switch (tcm_msg->command) {
+ case CMD_RESET:
+ LOGD("Reset by CMD_RESET\n");
+ case CMD_REBOOT_TO_ROM_BOOTLOADER:
+ case CMD_RUN_BOOTLOADER_FIRMWARE:
+ case CMD_RUN_APPLICATION_FIRMWARE:
+ case CMD_ENTER_PRODUCTION_TEST_MODE:
+ case CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE:
+ tcm_msg->status_report_code = STATUS_OK;
+ tcm_msg->response_code = STATUS_OK;
+ ATOMIC_SET(tcm_msg->command_status,
+ CMD_STATE_IDLE);
+ syna_pal_completion_complete(cmd_completion);
+ goto exit;
+ default:
+ LOGN("Device has been reset\n");
+ ATOMIC_SET(tcm_msg->command_status,
+ CMD_STATE_ERROR);
+ syna_pal_completion_complete(cmd_completion);
+ goto exit;
+ }
+ }
+ }
+
+ /* store the received report into the internal buffer.report */
+ syna_tcm_buf_lock(&tcm_dev->report_buf);
+
+ retval = syna_tcm_buf_alloc(&tcm_dev->report_buf,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.report\n");
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+ goto exit;
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_pal_mem_cpy(tcm_dev->report_buf.buf,
+ tcm_dev->report_buf.buf_size,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to copy payload to buf_report\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+ goto exit;
+ }
+
+ tcm_dev->report_buf.data_length = tcm_msg->payload_length;
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+
+exit:
+ return;
+}
+
+/**
+ * syna_tcm_v1_dispatch_response()
+ *
+ * Handle the response packet.
+ *
+ * Copy the data from internal buffer.in to internal buffer.resp,
+ * and then signal the command completion.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+static void syna_tcm_v1_dispatch_response(struct tcm_dev *tcm_dev)
+{
+ int retval;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ tcm_msg->response_code = tcm_msg->status_report_code;
+
+ if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_BUSY)
+ return;
+
+ if (tcm_msg->payload_length == 0) {
+ tcm_dev->resp_buf.data_length = tcm_msg->payload_length;
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+ goto exit;
+ }
+
+ /* copy the received resp data into the internal buffer.resp */
+ syna_tcm_buf_lock(&tcm_dev->resp_buf);
+
+ retval = syna_tcm_buf_alloc(&tcm_dev->resp_buf,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.resp\n");
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ goto exit;
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_pal_mem_cpy(tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to copy payload to internal resp_buf\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ goto exit;
+ }
+
+ tcm_dev->resp_buf.data_length = tcm_msg->payload_length;
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+
+exit:
+ syna_pal_completion_complete(cmd_completion);
+}
+
+
+/**
+ * syna_tcm_v1_read()
+ *
+ * Read in a TouchCom packet from device.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] rd_length: number of reading bytes;
+ * '0' means to read the message header only
+ * [in/out] buf: pointer to a buffer which is stored the retrieved data
+ * [out] buf_size: size of the buffer pointed
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v1_read(struct tcm_dev *tcm_dev, unsigned int rd_length,
+ unsigned char *buf, unsigned int buf_size)
+{
+ int retval;
+ unsigned int max_rd_size;
+ int retry;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (rd_length == 0)
+ return 0;
+
+ if (rd_length > buf_size) {
+ LOGE("Invalid read length, len: %d, buf_size: %d\n",
+ rd_length, buf_size);
+ return _EINVAL;
+ }
+
+ max_rd_size = tcm_dev->max_rd_size;
+
+ if ((max_rd_size != 0) && (rd_length > max_rd_size)) {
+ LOGE("Invalid read length, len: %d, max_rd_size: %d\n",
+ rd_length, max_rd_size);
+ return _EINVAL;
+ }
+
+ /* read in the message header from device
+ * will do retry if the packet is not expected
+ */
+ for (retry = 0; retry < 10; retry++) {
+ retval = syna_tcm_read(tcm_dev,
+ buf,
+ rd_length
+ );
+ if (retval < 0) {
+ LOGE("Fail to read %d bytes to device\n", rd_length);
+ goto exit;
+ }
+
+ /* check the message header */
+ if (buf[0] == TCM_V1_MESSAGE_MARKER)
+ break;
+
+ LOGE("Incorrect header marker, 0x%02x (retry:%d)\n",
+ buf[0], retry);
+
+ retval = _EIO;
+ syna_pal_sleep_us(RD_RETRY_US_MIN, RD_RETRY_US_MAX);
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_v1_write()
+ *
+ * Construct the TouchCom v1 packet and send it to device.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: command code
+ * [ in] payload: data payload if any
+ * [ in] payload_len: length of data payload if have any
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v1_write(struct tcm_dev *tcm_dev, unsigned char command,
+ unsigned char *payload, unsigned int payload_len)
+{
+ int retval = 0;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ int size;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+
+ syna_tcm_buf_lock(&tcm_msg->out);
+
+ /* allocate the space storing the written data */
+ retval = syna_tcm_buf_alloc(&tcm_msg->out, payload_len + 3);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.out\n");
+ goto exit;
+ }
+
+ if (command != CMD_CONTINUE_WRITE) {
+ /* construct the command packet
+ * size = 1-byte command + 2-byte length + payload
+ */
+ size = payload_len + 3;
+
+ tcm_msg->out.buf[0] = command;
+ tcm_msg->out.buf[1] = (unsigned char)payload_len;
+ tcm_msg->out.buf[2] = (unsigned char)(payload_len >> 8);
+
+ if (payload_len > 0) {
+ retval = syna_pal_mem_cpy(&tcm_msg->out.buf[3],
+ tcm_msg->out.buf_size - 3,
+ payload,
+ payload_len,
+ payload_len
+ );
+ if (retval < 0) {
+ LOGE("Fail to copy payload\n");
+ goto exit;
+ }
+ }
+ } else {
+ /* construct the continued writes packet
+ * size = 1-byte continued write + payload
+ */
+ size = payload_len + 1;
+
+ tcm_msg->out.buf[0] = CMD_CONTINUE_WRITE;
+
+ retval = syna_pal_mem_cpy(&tcm_msg->out.buf[1],
+ tcm_msg->out.buf_size - 1,
+ payload,
+ payload_len,
+ payload_len
+ );
+ if (retval < 0) {
+ LOGE("Fail to copy continued write\n");
+ goto exit;
+ }
+ }
+
+ /* write command packet to the device */
+ retval = syna_tcm_write(tcm_dev,
+ tcm_msg->out.buf,
+ size
+ );
+ if (retval < 0) {
+ LOGE("Fail to write %d bytes to device\n", size);
+ goto exit;
+ }
+
+exit:
+ syna_tcm_buf_unlock(&tcm_msg->out);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v1_continued_read()
+ *
+ * The remaining data payload is read in continuously until the end of data.
+ * All the retrieved data is appended to the internal buffer.in.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] length: length of payload data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v1_continued_read(struct tcm_dev *tcm_dev,
+ unsigned int length)
+{
+ int retval = 0;
+ 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;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+
+ /* continued read packet contains the header, payload, and a padding */
+ total_length = MESSAGE_HEADER_SIZE + length + 1;
+ remaining_length = total_length - MESSAGE_HEADER_SIZE;
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ /* in case the current buf.in is smaller than requested size */
+ retval = syna_tcm_buf_realloc(&tcm_msg->in,
+ total_length + 1);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf_in\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return retval;
+ }
+
+ /* available chunk space for payload =
+ * total chunk size - (header marker byte + header status byte)
+ */
+ if (tcm_dev->max_rd_size == 0)
+ chunk_space = remaining_length;
+ else
+ chunk_space = tcm_dev->max_rd_size - 2;
+
+ chunks = syna_pal_ceil_div(remaining_length, chunk_space);
+ chunks = chunks == 0 ? 1 : chunks;
+
+ offset = MESSAGE_HEADER_SIZE;
+
+ syna_tcm_buf_lock(&tcm_msg->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_msg->in.buf[offset] = TCM_V1_MESSAGE_PADDING;
+ offset += xfer_length;
+ remaining_length -= xfer_length;
+ continue;
+ }
+
+ /* allocate the internal temp buffer */
+ retval = syna_tcm_buf_alloc(&tcm_msg->temp,
+ xfer_length + 2);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.temp\n");
+ goto exit;
+ }
+ /* retrieve data from the bus
+ * data should include header marker and status code
+ */
+ retval = syna_tcm_v1_read(tcm_dev,
+ xfer_length + 2,
+ tcm_msg->temp.buf,
+ tcm_msg->temp.buf_size
+ );
+ if (retval < 0) {
+ LOGE("Fail to read %d bytes from device\n",
+ xfer_length + 2);
+ goto exit;
+ }
+
+ /* check the data content */
+ code = tcm_msg->temp.buf[1];
+
+ if (code != STATUS_CONTINUED_READ) {
+ LOGE("Incorrect status code 0x%02x at %d out of %d\n",
+ code, idx, chunks);
+ retval = _EIO;
+ goto exit;
+ }
+
+ /* copy data from internal buffer.temp to buffer.in */
+ retval = syna_pal_mem_cpy(&tcm_msg->in.buf[offset],
+ tcm_msg->in.buf_size - offset,
+ &tcm_msg->temp.buf[2],
+ tcm_msg->temp.buf_size - 2,
+ xfer_length);
+ if (retval < 0) {
+ LOGE("Fail to copy payload\n");
+ goto exit;
+ }
+
+ offset += xfer_length;
+ remaining_length -= xfer_length;
+ }
+
+exit:
+ syna_tcm_buf_unlock(&tcm_msg->temp);
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v1_read_message()
+ *
+ * Read in a TouchCom packet from device.
+ * The packet including its payload is read in from device and stored in
+ * the internal buffer.resp or buffer.report based on the code received.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] status_report_code: status code or report code received
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static int syna_tcm_v1_read_message(struct tcm_dev *tcm_dev,
+ unsigned char *status_report_code)
+{
+ int retval = 0;
+ struct tcm_v1_message_header *header;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_mutex_t *rw_mutex = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ rw_mutex = &tcm_msg->rw_mutex;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ if (status_report_code)
+ *status_report_code = STATUS_INVALID;
+
+ syna_pal_mutex_lock(rw_mutex);
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ /* read in the message header from device */
+ retval = syna_tcm_v1_read(tcm_dev,
+ MESSAGE_HEADER_SIZE,
+ tcm_msg->in.buf,
+ tcm_msg->in.buf_size
+ );
+ if (retval < 0) {
+ LOGE("Fail to read message header from device\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ tcm_msg->status_report_code = STATUS_INVALID;
+ tcm_msg->payload_length = 0;
+ goto exit;
+ }
+
+ /* check the message header */
+ header = (struct tcm_v1_message_header *)tcm_msg->in.buf;
+
+ tcm_msg->status_report_code = header->code;
+
+ tcm_msg->payload_length = syna_pal_le2_to_uint(header->length);
+
+ if (tcm_msg->status_report_code != STATUS_IDLE)
+ LOGD("Status code: 0x%02x, length: %d (%02x %02x %02x %02x)\n",
+ tcm_msg->status_report_code, tcm_msg->payload_length,
+ header->data[0], header->data[1], header->data[2],
+ header->data[3]);
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ if ((tcm_msg->status_report_code <= STATUS_ERROR) ||
+ (tcm_msg->status_report_code == STATUS_INVALID)) {
+ switch (tcm_msg->status_report_code) {
+ case STATUS_OK:
+ break;
+ case STATUS_CONTINUED_READ:
+ LOGE("Out-of-sync continued read\n");
+ case STATUS_IDLE:
+ tcm_msg->payload_length = 0;
+ retval = 0;
+ goto exit;
+ default:
+ LOGE("Incorrect Status code, 0x%02x\n",
+ tcm_msg->status_report_code);
+ tcm_msg->payload_length = 0;
+ goto do_dispatch;
+ }
+ }
+
+ if (tcm_msg->payload_length == 0)
+ goto do_dispatch;
+
+ /* retrieve the remaining data, if any */
+ retval = syna_tcm_v1_continued_read(tcm_dev,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to do continued read\n");
+ goto exit;
+ }
+
+ /* refill the header for dispatching */
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ tcm_msg->in.buf[0] = TCM_V1_MESSAGE_MARKER;
+ tcm_msg->in.buf[1] = tcm_msg->status_report_code;
+ tcm_msg->in.buf[2] = (unsigned char)tcm_msg->payload_length;
+ tcm_msg->in.buf[3] = (unsigned char)(tcm_msg->payload_length >> 8);
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+do_dispatch:
+ /* duplicate the data to external buffer */
+ syna_tcm_buf_lock(&tcm_dev->external_buf);
+ if (tcm_msg->payload_length > 0) {
+ retval = syna_tcm_buf_alloc(&tcm_dev->external_buf,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory, external_buf invalid\n");
+ } else {
+ retval = syna_pal_mem_cpy(&tcm_dev->external_buf.buf[0],
+ tcm_msg->payload_length,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0)
+ LOGE("Fail to copy data to external buffer\n");
+ }
+ }
+ tcm_dev->external_buf.data_length = tcm_msg->payload_length;
+ syna_tcm_buf_unlock(&tcm_dev->external_buf);
+
+ /* process the retrieved packet */
+ if (tcm_msg->status_report_code >= REPORT_IDENTIFY)
+ syna_tcm_v1_dispatch_report(tcm_dev);
+ else
+ syna_tcm_v1_dispatch_response(tcm_dev);
+
+ /* copy the status report code to caller */
+ if (status_report_code)
+ *status_report_code = tcm_msg->status_report_code;
+
+ retval = 0;
+
+exit:
+ /* raise the completion event when errors out */
+ if (retval < 0) {
+ if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) {
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR);
+ syna_pal_completion_complete(cmd_completion);
+ }
+ }
+
+ syna_pal_mutex_unlock(rw_mutex);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v1_write_message()
+ *
+ * Write message including command and its payload to TouchCom device.
+ * Then, the response of the command generated by the device will be
+ * read in and stored in internal buffer.resp.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: TouchComm command
+ * [ in] payload: data payload, if any
+ * [ in] payload_len: length of data payload, if any
+ * [out] resp_code: response code returned
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static int syna_tcm_v1_write_message(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_len, unsigned char *resp_code,
+ unsigned int delay_ms_resp)
+{
+ int retval = 0;
+ unsigned int idx;
+ unsigned int chunks;
+ unsigned int chunk_space;
+ unsigned int xfer_length;
+ unsigned int remaining_length;
+ int timeout = 0;
+ int polling_ms = 0;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_mutex_t *cmd_mutex = NULL;
+ syna_pal_mutex_t *rw_mutex = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+ bool has_irq_ctrl = false;
+ bool in_polling = false;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ cmd_mutex = &tcm_msg->cmd_mutex;
+ rw_mutex = &tcm_msg->rw_mutex;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ if (resp_code)
+ *resp_code = STATUS_INVALID;
+
+ /* indicate which mode is used */
+ in_polling = (delay_ms_resp != RESP_IN_ATTN);
+
+ /* irq control is enabled only when the operations is implemented
+ * and the current status of irq is enabled.
+ * do not enable irq if it is disabled by someone.
+ */
+ has_irq_ctrl = (bool)(tcm_dev->hw_if->ops_enable_irq != NULL);
+ has_irq_ctrl &= tcm_dev->hw_if->bdata_attn.irq_enabled;
+
+ /* disable irq when using polling mode */
+ if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq)
+ tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, false);
+
+ syna_pal_mutex_lock(cmd_mutex);
+
+ syna_pal_mutex_lock(rw_mutex);
+
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY);
+
+ /* reset the command completion */
+ syna_pal_completion_reset(cmd_completion);
+
+ tcm_msg->command = command;
+
+ remaining_length = payload_len;
+
+ /* available space for payload = total size - command byte */
+ if (tcm_dev->max_wr_size == 0)
+ chunk_space = remaining_length;
+ else
+ chunk_space = tcm_dev->max_wr_size - 1;
+
+ chunks = syna_pal_ceil_div(remaining_length, chunk_space);
+ chunks = chunks == 0 ? 1 : chunks;
+
+ LOGD("Command: 0x%02x, payload len: %d\n", command, payload_len);
+
+ /* send out command packets
+ *
+ * separate into several sub-packets if the overall size is over
+ * than the maximum write size.
+ */
+ for (idx = 0; idx < chunks; idx++) {
+ if (remaining_length > chunk_space)
+ xfer_length = chunk_space;
+ else
+ xfer_length = remaining_length;
+
+ if (idx == 0) {
+ retval = syna_tcm_v1_write(tcm_dev,
+ tcm_msg->command,
+ &payload[0],
+ xfer_length);
+ } else {
+ retval = syna_tcm_v1_write(tcm_dev,
+ CMD_CONTINUE_WRITE,
+ &payload[idx * chunk_space],
+ xfer_length);
+ }
+
+ if (retval < 0) {
+ LOGE("Fail to write %d bytes to device\n",
+ xfer_length);
+ syna_pal_mutex_unlock(rw_mutex);
+ goto exit;
+ }
+
+ remaining_length -= xfer_length;
+
+ if (chunks > 1)
+ syna_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX);
+ }
+
+ syna_pal_mutex_unlock(rw_mutex);
+
+ /* handle the command response
+ *
+ * assuming to select the polling mode, the while-loop below will
+ * repeatedly read in the respose data based on the given polling
+ * time; otherwise, wait until receiving a completion event from
+ * interupt thread.
+ */
+ timeout = 0;
+ if (!in_polling)
+ polling_ms = CMD_RESPONSE_TIMEOUT_MS;
+ else
+ polling_ms = delay_ms_resp;
+
+ do {
+ /* wait for the completion event triggered by read_message */
+ retval = syna_pal_completion_wait_for(cmd_completion,
+ polling_ms);
+ /* reset the status when times out and keep in polling */
+ if (retval < 0)
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY);
+
+ /* break when geting a valid resp; otherwise, keep in polling */
+ if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_IDLE)
+ goto check_response;
+
+ if (in_polling) {
+ /* retrieve the message packet back */
+ retval = syna_tcm_v1_read_message(tcm_dev, NULL);
+ /* keep in polling if still not having a valid resp */
+ if (retval < 0)
+ syna_pal_completion_reset(cmd_completion);
+ }
+
+ timeout += polling_ms + 10;
+
+ } while (timeout < CMD_RESPONSE_TIMEOUT_MS);
+
+ /* check the status of response data
+ * according to the touchcomm spec, each command message
+ * should have an associated reponse message.
+ */
+check_response:
+ if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_IDLE) {
+ if (timeout >= CMD_RESPONSE_TIMEOUT_MS) {
+ LOGE("Timed out wait for response of command 0x%02x\n",
+ command);
+ retval = _ETIMEDOUT;
+ goto exit;
+ } else {
+ LOGE("Fail to get valid response of command 0x%02x\n",
+ command);
+ retval = _EIO;
+ goto exit;
+ }
+ }
+
+ /* copy response code to the caller */
+ if (resp_code)
+ *resp_code = tcm_msg->response_code;
+
+ if (tcm_msg->response_code != STATUS_OK) {
+ LOGE("Error code 0x%02x of command 0x%02x\n",
+ tcm_msg->response_code, tcm_msg->command);
+ retval = _EIO;
+ } else {
+ retval = 0;
+ }
+
+exit:
+ tcm_msg->command = CMD_NONE;
+
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+
+ syna_pal_mutex_unlock(cmd_mutex);
+
+ /* recovery the irq if using polling mode */
+ if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq)
+ tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, true);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v1_detect()
+ *
+ * For TouchCom v1 protocol, the given raw data must start with a specific
+ * maker code. If so, read the remaining packet from TouchCom device.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: raw 4-byte data
+ * [ in] size: length of input data in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_v1_detect(struct tcm_dev *tcm_dev, unsigned char *data,
+ unsigned int size)
+{
+ int retval;
+ struct tcm_v1_message_header *header;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ unsigned int payload_length = 0;
+ unsigned char resp_code = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!data) || (size < MESSAGE_HEADER_SIZE)) {
+ LOGE("Invalid parameters\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+
+ header = (struct tcm_v1_message_header *)data;
+
+ if (header->marker != TCM_V1_MESSAGE_MARKER)
+ return _ENODEV;
+
+ /* after initially powering on, the identify report should be the
+ * first packet
+ */
+ if (header->code == REPORT_IDENTIFY) {
+
+ payload_length = syna_pal_le2_to_uint(header->length);
+
+ /* retrieve the identify info packet */
+ retval = syna_tcm_v1_continued_read(tcm_dev,
+ payload_length);
+ if (retval < 0) {
+ LOGE("Fail to read in identify info packet\n");
+ return retval;
+ }
+ } else {
+ /* if not, send an identify command instead */
+ retval = syna_tcm_v1_write_message(tcm_dev,
+ CMD_IDENTIFY,
+ NULL,
+ 0,
+ &resp_code,
+ RESP_IN_POLLING);
+ if (retval < 0) {
+ /* in case the identify command is not working,
+ * send a rest command as the workaround
+ */
+ retval = syna_tcm_v1_write_message(tcm_dev,
+ CMD_RESET,
+ NULL,
+ 0,
+ &resp_code,
+ RESET_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to identify the device\n");
+ return _ENODEV;
+ }
+ }
+
+ payload_length = tcm_msg->payload_length;
+ }
+
+ /* parse the identify info packet */
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_tcm_v1_parse_idinfo(tcm_dev,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ payload_length);
+ if (retval < 0) {
+ LOGE("Fail to identify device\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return retval;
+ }
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ /* expose the read / write operations */
+ tcm_dev->read_message = syna_tcm_v1_read_message;
+ tcm_dev->write_message = syna_tcm_v1_write_message;
+
+ return retval;
+}
diff --git a/tcm/synaptics_touchcom_core_v2.c b/tcm/synaptics_touchcom_core_v2.c
new file mode 100644
index 0000000..c3f0d56
--- /dev/null
+++ b/tcm/synaptics_touchcom_core_v2.c
@@ -0,0 +1,1445 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_core_v2.c
+ *
+ * This file implements the TouchComm version 2 command-response protocol
+ */
+
+#include "synaptics_touchcom_core_dev.h"
+
+#define BITS_IN_MESSAGE_HEADER (MESSAGE_HEADER_SIZE * 8)
+
+#define HOST_PRIMARY (0)
+
+#define COMMAND_RETRY_TIMES (5)
+
+#define CHECK_PACKET_CRC
+
+/**
+ * @section: Header of TouchComm v2 Message Packet
+ *
+ * The 4-byte header in the TouchComm v2 packet
+ */
+struct tcm_v2_message_header {
+ union {
+ struct {
+ unsigned char code;
+ unsigned char length[2];
+ unsigned char byte3;
+ };
+ unsigned char data[MESSAGE_HEADER_SIZE];
+ };
+};
+
+/* helper to execute a tcm v2 command
+ */
+static int syna_tcm_v2_execute_cmd_request(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_length);
+
+/**
+ * @section: Lookup table for checksum calculation
+ *
+ * @subsection: crc6_table
+ * lookup table for crc6 calculation
+ *
+ * @subsection: crc16_table
+ * lookup table for crc16 calculation
+ */
+static unsigned short crc6_table[16] = {
+ 0, 268, 536, 788, 1072, 1340, 1576, 1828,
+ 2144, 2412, 2680, 2932, 3152, 3420, 3656, 3908
+};
+
+static unsigned short crc16_table[256] = {
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+};
+
+/**
+ * syna_tcm_v2_crc6()
+ *
+ * Calculate the crc-6 with polynomial for TouchCom v2 header.
+ *
+ * @param
+ * [ in] p: byte array for the calculation
+ * [ in] bits: number of bits
+ *
+ * @return
+ * the crc-6 value
+ */
+static unsigned char syna_tcm_v2_crc6(unsigned char *p, unsigned int bits)
+{
+ unsigned short r = 0x003F << 2;
+ unsigned short x;
+
+ for (; bits > 8; bits -= 8) {
+ r ^= *p++;
+ r = (r << 4) ^ crc6_table[r >> 4];
+ r = (r << 4) ^ crc6_table[r >> 4];
+ }
+
+ if (bits > 0) {
+ x = *p;
+ while (bits--) {
+ if (x & 0x80)
+ r ^= 0x80;
+
+ x <<= 1;
+ r <<= 1;
+ if (r & 0x100)
+ r ^= (0x03 << 2);
+ }
+ }
+
+ return (unsigned char)((r >> 2) & 0x3F);
+}
+/**
+ * syna_tcm_v2_crc6()
+ *
+ * Calculate the crc-16 for TouchCom v2 packet.
+ *
+ * @param
+ * [ in] p: byte array for the calculation
+ * [ in] len: length in bytes
+ *
+ * @return
+ * the crc-16 value
+ */
+static unsigned short syna_tcm_v2_crc16(unsigned char *p, unsigned int len)
+{
+ unsigned short r = 0xFFFF;
+
+ while (len--)
+ r = (r << 8) ^ crc16_table[(r >> 8) ^ *p++];
+
+ return r;
+}
+
+/**
+ * syna_tcm_v2_set_max_read_size()
+ *
+ * Configure the max length for message reading.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+static int syna_tcm_v2_set_max_read_size(struct tcm_dev *tcm_dev)
+{
+ int retval;
+ unsigned int rd_size;
+ struct tcm_identification_info *id_info;
+ unsigned char data[2] = { 0 };
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ id_info = &tcm_dev->id_info;
+
+ rd_size = syna_pal_le2_to_uint(id_info->max_read_size);
+
+ if (rd_size == 0) {
+ LOGE("Invalid max_read_length: %d\n", rd_size);
+ return 0;
+ }
+
+ if (rd_size == tcm_dev->max_rd_size)
+ return 0;
+
+ tcm_dev->max_rd_size = MIN(rd_size, tcm_dev->max_rd_size);
+
+ LOGD("max_rd_size = %d\n", tcm_dev->max_rd_size);
+
+ data[0] = (unsigned char)tcm_dev->max_rd_size;
+ data[1] = (unsigned char)(tcm_dev->max_rd_size >> 8);
+
+ retval = syna_tcm_v2_execute_cmd_request(tcm_dev,
+ CMD_TCM2_SET_MAX_READ_LENGTH,
+ data,
+ sizeof(data));
+ if (retval < 0) {
+ LOGE("Fail to set max_read_length\n");
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_tcm_v2_parse_idinfo()
+ *
+ * Copy the given data to the identification info structure
+ * and parse the basic information, e.g. fw build id.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: data buffer
+ * [ in] size: size of given data buffer
+ * [ in] data_len: length of actual data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v2_parse_idinfo(struct tcm_dev *tcm_dev,
+ unsigned char *data, unsigned int size, unsigned int data_len)
+{
+ int retval;
+ unsigned int wr_size = 0;
+ unsigned int build_id = 0;
+ struct tcm_identification_info *id_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!data) || (data_len == 0)) {
+ LOGE("Invalid given data buffer\n");
+ return _EINVAL;
+ }
+
+ id_info = &tcm_dev->id_info;
+
+ retval = syna_pal_mem_cpy((unsigned char *)id_info,
+ sizeof(struct tcm_identification_info),
+ data,
+ size,
+ MIN(sizeof(*id_info), data_len));
+ if (retval < 0) {
+ LOGE("Fail to copy identification info\n");
+ return retval;
+ }
+
+ build_id = syna_pal_le4_to_uint(id_info->build_id);
+
+ wr_size = syna_pal_le2_to_uint(id_info->max_write_size);
+ tcm_dev->max_wr_size = MIN(wr_size, WR_CHUNK_SIZE);
+ if (tcm_dev->max_wr_size == 0) {
+ tcm_dev->max_wr_size = wr_size;
+ LOGD("max_wr_size = %d\n", tcm_dev->max_wr_size);
+ }
+
+ LOGI("TCM Fw mode: 0x%02x\n", id_info->mode);
+
+ if (tcm_dev->packrat_number != build_id)
+ tcm_dev->packrat_number = build_id;
+
+ /* set up the max. reading length */
+ retval = syna_tcm_v2_set_max_read_size(tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to setup the max reading length\n");
+ return retval;
+ }
+
+ tcm_dev->dev_mode = id_info->mode;
+
+ return 0;
+}
+
+/**
+ * syna_tcm_v2_dispatch_report()
+ *
+ * Handle the TouchCom report packet being received.
+ *
+ * If it's an identify report, parse the identification packet and signal
+ * the command completion just in case.
+ * Otherwise, copy the data from internal buffer.in to internal buffer.report
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+static void syna_tcm_v2_dispatch_report(struct tcm_dev *tcm_dev)
+{
+ int retval;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ tcm_msg->report_code = tcm_msg->status_report_code;
+
+ if (tcm_msg->payload_length == 0) {
+ tcm_dev->resp_buf.data_length = tcm_msg->payload_length;
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+ goto exit;
+ }
+
+ /* The identify report may be resulted from reset or fw mode switching
+ */
+ if (tcm_msg->report_code == REPORT_IDENTIFY) {
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_tcm_v2_parse_idinfo(tcm_dev,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to identify device\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return;
+ }
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ /* in case, the identify info packet is caused by the command */
+ if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) {
+ switch (tcm_msg->command) {
+ case CMD_RESET:
+ LOGD("Reset by CMD_RESET\n");
+ case CMD_REBOOT_TO_ROM_BOOTLOADER:
+ case CMD_RUN_BOOTLOADER_FIRMWARE:
+ case CMD_RUN_APPLICATION_FIRMWARE:
+ case CMD_ENTER_PRODUCTION_TEST_MODE:
+ case CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE:
+ tcm_msg->status_report_code = STATUS_OK;
+ tcm_msg->response_code = STATUS_OK;
+ ATOMIC_SET(tcm_msg->command_status,
+ CMD_STATE_IDLE);
+ syna_pal_completion_complete(cmd_completion);
+ goto exit;
+ default:
+ LOGN("Device has been reset\n");
+ ATOMIC_SET(tcm_msg->command_status,
+ CMD_STATE_ERROR);
+ syna_pal_completion_complete(cmd_completion);
+ goto exit;
+ }
+ }
+ }
+
+ /* store the received report into the internal buffer.report */
+ syna_tcm_buf_lock(&tcm_dev->report_buf);
+
+ retval = syna_tcm_buf_alloc(&tcm_dev->report_buf,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.report\n");
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+ goto exit;
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_pal_mem_cpy(tcm_dev->report_buf.buf,
+ tcm_dev->report_buf.buf_size,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to copy payload to buf_report\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+ goto exit;
+ }
+
+ tcm_dev->report_buf.data_length = tcm_msg->payload_length;
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+
+exit:
+ return;
+}
+
+/**
+ * syna_tcm_v2_dispatch_response()
+ *
+ * Handle the response packet.
+ *
+ * Copy the data from internal buffer.in to internal buffer.resp,
+ * and then signal the command completion.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+static void syna_tcm_v2_dispatch_response(struct tcm_dev *tcm_dev)
+{
+ int retval;
+ unsigned int resp_data_length;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_BUSY)
+ return;
+
+ resp_data_length = tcm_msg->payload_length;
+
+ if (resp_data_length == 0) {
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+ goto exit;
+ }
+
+ /* store the received report into the temporary buffer */
+ syna_tcm_buf_lock(&tcm_dev->resp_buf);
+
+ retval = syna_tcm_buf_alloc(&tcm_dev->resp_buf,
+ resp_data_length + 1);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.resp\n");
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR);
+ goto exit;
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_pal_mem_cpy(tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ resp_data_length);
+ if (retval < 0) {
+ LOGE("Fail to copy payload to internal resp_buf\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR);
+ goto exit;
+ }
+
+ tcm_dev->resp_buf.data_length = resp_data_length;
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+
+exit:
+ syna_pal_completion_complete(cmd_completion);
+}
+
+/**
+ * syna_tcm_v2_read()
+ *
+ * Read in a TouchCom packet from device.
+ * Checking the CRC is necessary to ensure a valid message received.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] rd_length: number of reading bytes;
+ * '0' means to read the message header only
+ * [out] buf: pointer to a buffer which is stored the retrieved data
+ * [out] buf_size: size of the buffer pointed
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v2_read(struct tcm_dev *tcm_dev, unsigned int rd_length,
+ unsigned char **buf, unsigned int *buf_size)
+{
+ int retval;
+ struct tcm_v2_message_header *header;
+ int max_rd_size;
+ int xfer_len;
+ unsigned char crc6 = 0;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ max_rd_size = tcm_dev->max_rd_size;
+
+ /* continued packet crc if containing payload data */
+ xfer_len = (rd_length > 0) ? (rd_length + 2) : rd_length;
+ xfer_len += sizeof(struct tcm_v2_message_header);
+
+ if ((max_rd_size != 0) && (xfer_len > max_rd_size)) {
+ LOGE("Invalid xfer length, len: %d, max_rd_size: %d\n",
+ xfer_len, max_rd_size);
+ tcm_msg->status_report_code = STATUS_INVALID;
+ return _EINVAL;
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->temp);
+
+ /* allocate the internal temp buffer */
+ retval = syna_tcm_buf_alloc(&tcm_msg->temp, xfer_len);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.temp\n");
+ goto exit;
+ }
+ /* read data from the bus */
+ retval = syna_tcm_read(tcm_dev,
+ tcm_msg->temp.buf,
+ xfer_len);
+ if (retval < 0) {
+ LOGE("Fail to read from device\n");
+ goto exit;
+ }
+
+ header = (struct tcm_v2_message_header *)tcm_msg->temp.buf;
+
+ /* check header crc always */
+ crc6 = syna_tcm_v2_crc6(header->data, BITS_IN_MESSAGE_HEADER);
+ if (crc6 != 0) {
+ LOGE("Invalid header crc: 0x%02x\n", (header->byte3 & 0x3f));
+
+ tcm_msg->status_report_code = STATUS_PACKET_CORRUPTED;
+ goto exit;
+ }
+
+#ifdef CHECK_PACKET_CRC
+ /* check packet crc */
+ if (rd_length > 0) {
+ if (syna_tcm_v2_crc16(&tcm_msg->temp.buf[0], xfer_len) != 0) {
+ LOGE("Invalid packet crc: %02x %02x\n",
+ tcm_msg->temp.buf[xfer_len - 2],
+ tcm_msg->temp.buf[xfer_len - 1]);
+
+ tcm_msg->status_report_code = STATUS_PACKET_CORRUPTED;
+ goto exit;
+ }
+ }
+#endif
+
+ tcm_msg->status_report_code = header->code;
+
+ tcm_msg->payload_length = syna_pal_le2_to_uint(header->length);
+
+ if (tcm_msg->status_report_code != STATUS_IDLE)
+ LOGD("Status code: 0x%02x, length: %d (%02x %02x %02x %02x)\n",
+ tcm_msg->status_report_code, tcm_msg->payload_length,
+ header->data[0], header->data[1], header->data[2],
+ header->data[3]);
+
+ *buf = tcm_msg->temp.buf;
+ *buf_size = tcm_msg->temp.buf_size;
+
+exit:
+ syna_tcm_buf_unlock(&tcm_msg->temp);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_write()
+ *
+ * Construct the TouchCom v2 packet and send it to device.
+ * Add 4-byte header at the beginning of a message and appended crc if needed.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: command code
+ * [ in] payload: data payload if any
+ * [ in] payload_len: length of data payload if have any
+ * [ in] resend: flag for re-sending the packet
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v2_write(struct tcm_dev *tcm_dev, unsigned char command,
+ unsigned char *payload, unsigned int payload_len, bool resend)
+{
+ int retval;
+ struct tcm_v2_message_header *header;
+ unsigned char bits = BITS_IN_MESSAGE_HEADER - 6;
+ int xfer_len;
+ int size = MESSAGE_HEADER_SIZE + payload_len;
+ unsigned short crc16;
+ int max_wr_size;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ max_wr_size = tcm_dev->max_wr_size;
+
+ /* continued packet crc if containing payload data */
+ xfer_len = (payload_len > 0) ? (payload_len + 2) : payload_len;
+ xfer_len += sizeof(struct tcm_v2_message_header);
+
+ if ((max_wr_size != 0) && (xfer_len > max_wr_size)) {
+ LOGE("Invalid xfer length, len: %d, max_wr_size: %d\n",
+ xfer_len, max_wr_size);
+ tcm_msg->status_report_code = STATUS_INVALID;
+ return _EINVAL;
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->out);
+
+ /* allocate the internal out buffer */
+ retval = syna_tcm_buf_alloc(&tcm_msg->out, xfer_len);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf.out\n");
+ goto exit;
+ }
+
+ /* construct packet header */
+ header = (struct tcm_v2_message_header *)tcm_msg->out.buf;
+
+ if (resend)
+ tcm_msg->seq_toggle -= 1;
+
+ header->code = command;
+ header->length[0] = (unsigned char)payload_len;
+ header->length[1] = (unsigned char)(payload_len >> 8);
+ header->byte3 = ((HOST_PRIMARY & 0x01) << 7);
+ header->byte3 |= ((tcm_msg->seq_toggle++ & 0x01) << 6);
+ header->byte3 |= syna_tcm_v2_crc6(header->data, bits);
+
+ /* copy payload, if any */
+ if (payload_len) {
+ retval = syna_pal_mem_cpy(
+ &tcm_msg->out.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->out.buf_size - MESSAGE_HEADER_SIZE,
+ payload,
+ payload_len,
+ payload_len);
+ if (retval < 0) {
+ LOGE("Fail to copy payload data\n");
+ goto exit;
+ }
+
+ /* append packet crc */
+ crc16 = syna_tcm_v2_crc16(&tcm_msg->out.buf[0], size);
+ tcm_msg->out.buf[size] = (unsigned char)((crc16 >> 8) & 0xFF);
+ tcm_msg->out.buf[size + 1] = (unsigned char)(crc16 & 0xFF);
+ }
+
+ /* write command packet to the bus */
+ retval = syna_tcm_write(tcm_dev,
+ tcm_msg->out.buf,
+ xfer_len);
+ if (retval < 0) {
+ LOGE("Fail to write to device\n");
+ goto exit;
+ }
+
+exit:
+ syna_tcm_buf_unlock(&tcm_msg->out);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_continued_read()
+ *
+ * Write a CMD_ACK to read in the remaining data payload continuously
+ * until the end of data. All the retrieved data is appended to the
+ * internal buffer.in.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] length: remaining data length in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v2_continued_read(struct tcm_dev *tcm_dev,
+ unsigned int length)
+{
+ int retval;
+ unsigned char *tmp_buf;
+ unsigned int tmp_buf_size;
+ int retry_cnt = 0;
+ 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;
+ unsigned char command;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+
+ /* continued read packet contains the header and its payload */
+ total_length = MESSAGE_HEADER_SIZE + tcm_msg->payload_length;
+
+ remaining_length = length;
+
+ offset = tcm_msg->payload_length - length;
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ /* extend the internal buf_in if needed */
+ retval = syna_tcm_buf_realloc(&tcm_msg->in,
+ total_length + 1);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal buf_in\n");
+ goto exit;
+ }
+
+ /* available space for payload = total chunk size - header - crc */
+ chunk_space = tcm_dev->max_rd_size;
+ if (chunk_space == 0)
+ chunk_space = remaining_length;
+ else
+ chunk_space = chunk_space - (MESSAGE_HEADER_SIZE + 2);
+
+ chunks = syna_pal_ceil_div(remaining_length, chunk_space);
+ chunks = chunks == 0 ? 1 : chunks;
+
+ offset += MESSAGE_HEADER_SIZE;
+
+ /* send CMD_ACK for a continued read */
+ command = CMD_TCM2_ACK;
+
+ for (idx = 0; idx < chunks; idx++) {
+retry:
+ LOGD("Command: 0x%02x\n", command);
+
+ /* construct the command packet */
+ retval = syna_tcm_v2_write(tcm_dev,
+ command,
+ NULL,
+ 0,
+ false);
+ if (retval < 0) {
+ LOGE("Fail to send CMD_TCM2_ACK in continued read\n");
+ goto exit;
+ }
+
+ if (remaining_length > chunk_space)
+ xfer_length = chunk_space;
+ else
+ xfer_length = remaining_length;
+
+ /* read in the requested size of data */
+ retval = syna_tcm_v2_read(tcm_dev,
+ xfer_length,
+ &tmp_buf,
+ &tmp_buf_size);
+ if (retval < 0) {
+ LOGE("Fail to read %d bytes from device\n",
+ xfer_length);
+ goto exit;
+ }
+
+ /* If see an error, retry the previous read transaction
+ * Send RETRY instead of CMD_ACK
+ */
+ if (tcm_msg->status_report_code == STATUS_PACKET_CORRUPTED) {
+ if (retry_cnt > COMMAND_RETRY_TIMES) {
+ LOGE("Continued read packet corrupted\n");
+ goto exit;
+ }
+
+ retry_cnt += 1;
+ command = CMD_TCM2_RETRY;
+
+ LOGW("Read corrupted, retry %d\n", retry_cnt);
+ goto retry;
+ }
+
+ retry_cnt = 0;
+ command = CMD_TCM2_ACK;
+
+ /* append data from temporary buffer to in_buf */
+ syna_tcm_buf_lock(&tcm_msg->temp);
+
+ /* copy data from internal buffer.temp to buffer.in */
+ retval = syna_pal_mem_cpy(&tcm_msg->in.buf[offset],
+ tcm_msg->in.buf_size - offset,
+ &tmp_buf[MESSAGE_HEADER_SIZE],
+ tmp_buf_size - MESSAGE_HEADER_SIZE,
+ xfer_length);
+ if (retval < 0) {
+ LOGE("Fail to copy payload to internal buf_in\n");
+ syna_tcm_buf_unlock(&tcm_msg->temp);
+ goto exit;
+ }
+
+ syna_tcm_buf_unlock(&tcm_msg->temp);
+
+ remaining_length -= xfer_length;
+
+ offset += xfer_length;
+ }
+
+ retval = 0;
+
+exit:
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_get_response()
+ *
+ * Read in the response packet from device.
+ * If containing payload data, use continued_read() function and read the
+ * remaining payload data.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v2_get_response(struct tcm_dev *tcm_dev)
+{
+ int retval;
+ struct tcm_v2_message_header *header;
+ unsigned char *tmp_buf;
+ unsigned int tmp_buf_size;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+
+ /* read in the message header at first */
+ retval = syna_tcm_v2_read(tcm_dev,
+ 0,
+ &tmp_buf,
+ &tmp_buf_size);
+ if (retval < 0) {
+ LOGE("Fail to read message header from device\n");
+ return retval;
+ }
+
+ /* error out once the response packet is corrupted */
+ if (tcm_msg->status_report_code == STATUS_PACKET_CORRUPTED)
+ return 0;
+
+
+ /* allocate the required space = header + payload */
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ retval = syna_tcm_buf_alloc(&tcm_msg->in,
+ MESSAGE_HEADER_SIZE + tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to reallocate memory for internal buf.in\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return retval;
+ }
+
+ retval = syna_pal_mem_cpy(tcm_msg->in.buf,
+ tcm_msg->in.buf_size,
+ tmp_buf,
+ tmp_buf_size,
+ MESSAGE_HEADER_SIZE);
+ if (retval < 0) {
+ LOGE("Fail to copy data to internal buf_in\n");
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return retval;
+ }
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ /* read in payload, if any */
+ if (tcm_msg->payload_length > 0) {
+
+ retval = syna_tcm_v2_continued_read(tcm_dev,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to read in payload data, size: %d)\n",
+ tcm_msg->payload_length);
+ return retval;
+ }
+ }
+
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ header = (struct tcm_v2_message_header *)tcm_msg->in.buf;
+
+ tcm_msg->payload_length = syna_pal_le2_to_uint(header->length);
+ tcm_msg->status_report_code = header->code;
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_send_cmd()
+ *
+ * Forward the given command and payload to syna_tcm_v2_write().
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: command code
+ * [ in] payload: data payload if any
+ * [ in] payload_len: length of data payload if have any
+ * [ in] resend: flag for re-sending the packet
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static inline int syna_tcm_v2_send_cmd(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int length, bool resend)
+{
+ int retval;
+
+ retval = syna_tcm_v2_write(tcm_dev,
+ command,
+ payload,
+ length,
+ resend);
+ if (retval < 0)
+ LOGE("Fail to write Command 0x%02x to device\n", command);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_execute_cmd_request()
+ *
+ * Process the command message.
+ * The helper is responsible for sending the given command and its payload,
+ * to device. Once the total size of message is over the wr_chunk, divide
+ * into continued writes
+ *
+ * In addition, the response to the command generated by the device will be
+ * read in immediately.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: command code
+ * [ in] payload: data payload if any
+ * [ in] payload_length: length of payload in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_v2_execute_cmd_request(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_length)
+{
+ int retval;
+ unsigned int idx;
+ unsigned int offset;
+ unsigned int chunks;
+ unsigned int xfer_length;
+ unsigned int remaining_length;
+ int retry_cnt = 0;
+ unsigned int chunk_space;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ chunk_space = tcm_dev->max_wr_size;
+
+ remaining_length = payload_length;
+
+ /* available space for payload = total size - header - crc */
+ if (chunk_space == 0)
+ chunk_space = remaining_length;
+ else
+ chunk_space = chunk_space - (MESSAGE_HEADER_SIZE + 2);
+
+ chunks = syna_pal_ceil_div(remaining_length, chunk_space);
+
+ chunks = chunks == 0 ? 1 : chunks;
+
+ offset = 0;
+
+ /* process the command message and handle the response
+ * to the command
+ */
+ for (idx = 0; idx < chunks; idx++) {
+ if (remaining_length > chunk_space)
+ xfer_length = chunk_space;
+ else
+ xfer_length = remaining_length;
+
+retry:
+ /* send command to device */
+ command = (idx == 0) ? command : CMD_CONTINUE_WRITE;
+
+ LOGD("Command: 0x%02x\n", command);
+
+ retval = syna_tcm_v2_send_cmd(tcm_dev,
+ command,
+ &payload[offset],
+ xfer_length,
+ (retry_cnt > 0));
+ if (retval < 0)
+ goto exit;
+
+ /* bus turnaround delay */
+ syna_pal_sleep_us(TAT_DELAY_US_MIN, TAT_DELAY_US_MAX);
+
+ /* get the response to the command immediately */
+ retval = syna_tcm_v2_get_response(tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to get the response to command 0x%02x\n",
+ command);
+ goto exit;
+ }
+
+ /* check the response code */
+ tcm_msg->response_code = tcm_msg->status_report_code;
+
+ LOGD("Response code: 0x%x\n", tcm_msg->response_code);
+
+ if (tcm_msg->status_report_code >= REPORT_IDENTIFY)
+ goto next;
+
+ switch (tcm_msg->status_report_code) {
+ case STATUS_NO_REPORT_AVAILABLE:
+ case STATUS_OK:
+ case STATUS_ACK:
+ retry_cnt = 0;
+ break;
+ case STATUS_PACKET_CORRUPTED:
+ case STATUS_RETRY_REQUESTED:
+ retry_cnt += 1;
+ break;
+ default:
+ LOGE("Incorrect status code 0x%02x of command 0x%02x\n",
+ tcm_msg->status_report_code, command);
+ goto exit;
+ }
+
+ if (retry_cnt > 0) {
+ if (command == CMD_RESET) {
+ LOGE("Command CMD_RESET corrupted, exit\n");
+ /* assume ACK and wait for interrupt assertion
+ * once the response of reset is corrupted
+ */
+ tcm_msg->response_code = STATUS_ACK;
+ goto exit;
+ } else if (retry_cnt > COMMAND_RETRY_TIMES) {
+ LOGE("Command 0x%02x corrupted\n", command);
+ goto exit;
+ }
+
+ LOGN("Command 0x%02x, retry %d\n", command, retry_cnt);
+ syna_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX);
+
+ goto retry;
+ }
+next:
+ offset += xfer_length;
+
+ remaining_length -= xfer_length;
+
+ if (chunks > 1)
+ syna_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX);
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_read_message()
+ *
+ * Send a CMD_GET_REPORT to acquire a TouchCom v2 report packet from device.
+ * Meanwhile, the retrieved data will be stored in the internal buffer.resp
+ * or buffer.report.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] status_report_code: status code or report code received
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static int syna_tcm_v2_read_message(struct tcm_dev *tcm_dev,
+ unsigned char *status_report_code)
+{
+ int retval;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_mutex_t *rw_mutex = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ rw_mutex = &tcm_msg->rw_mutex;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ if (status_report_code)
+ *status_report_code = STATUS_INVALID;
+
+ syna_pal_mutex_lock(rw_mutex);
+
+ /* request a command */
+ retval = syna_tcm_v2_execute_cmd_request(tcm_dev,
+ CMD_TCM2_GET_REPORT,
+ NULL,
+ 0);
+ if (retval < 0) {
+ LOGE("Fail to send command CMD_TCM2_GET_REPORT\n");
+
+ if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) {
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR);
+ syna_pal_completion_complete(cmd_completion);
+ }
+ goto exit;
+ }
+
+ /* duplicate the data to external buffer */
+ syna_tcm_buf_lock(&tcm_dev->external_buf);
+ if (tcm_msg->payload_length > 0) {
+ retval = syna_tcm_buf_alloc(&tcm_dev->external_buf,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory, external_buf invalid\n");
+ syna_tcm_buf_unlock(&tcm_dev->external_buf);
+ goto exit;
+ } else {
+ retval = syna_pal_mem_cpy(&tcm_dev->external_buf.buf[0],
+ tcm_msg->payload_length,
+ &tcm_msg->in.buf[MESSAGE_HEADER_SIZE],
+ tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE,
+ tcm_msg->payload_length);
+ if (retval < 0) {
+ LOGE("Fail to copy data to external buffer\n");
+ syna_tcm_buf_unlock(&tcm_dev->external_buf);
+ goto exit;
+ }
+ }
+ }
+ tcm_dev->external_buf.data_length = tcm_msg->payload_length;
+ syna_tcm_buf_unlock(&tcm_dev->external_buf);
+
+ if (tcm_msg->response_code == STATUS_NO_REPORT_AVAILABLE)
+ goto exit;
+
+ /* process the retrieved packet */
+ if (tcm_msg->status_report_code >= REPORT_IDENTIFY)
+ syna_tcm_v2_dispatch_report(tcm_dev);
+ else
+ syna_tcm_v2_dispatch_response(tcm_dev);
+
+ /* copy the status report code to caller */
+ if (status_report_code)
+ *status_report_code = tcm_msg->status_report_code;
+
+exit:
+ syna_pal_mutex_unlock(rw_mutex);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_v2_write_message()
+ *
+ * Write message including command and its payload to TouchCom device.
+ * Then, the response of the command generated by the device will be
+ * read in and stored in internal buffer.resp.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: TouchComm command
+ * [ in] payload: data payload, if any
+ * [ in] payload_length: length of data payload, if any
+ * [out] resp_code: response code returned
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ *
+ * @return
+ * 0 or positive value on success; otherwise, on error.
+ */
+static int syna_tcm_v2_write_message(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_length, unsigned char *resp_code,
+ unsigned int delay_ms_resp)
+{
+ int retval;
+ int timeout = 0;
+ int polling_ms = 0;
+ struct tcm_message_data_blob *tcm_msg = NULL;
+ syna_pal_mutex_t *cmd_mutex = NULL;
+ syna_pal_mutex_t *rw_mutex = NULL;
+ syna_pal_completion_t *cmd_completion = NULL;
+ bool has_irq_ctrl = false;
+ bool in_polling = false;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_msg = &tcm_dev->msg_data;
+ cmd_mutex = &tcm_msg->cmd_mutex;
+ rw_mutex = &tcm_msg->rw_mutex;
+ cmd_completion = &tcm_msg->cmd_completion;
+
+ if (resp_code)
+ *resp_code = STATUS_INVALID;
+
+ /* indicate which mode is used */
+ in_polling = (delay_ms_resp != RESP_IN_ATTN);
+
+ /* irq control is enabled only when the operations is implemented
+ * and the current status of irq is enabled.
+ * do not enable irq if it is disabled by someone.
+ */
+ has_irq_ctrl = (bool)(tcm_dev->hw_if->ops_enable_irq != NULL);
+ has_irq_ctrl &= tcm_dev->hw_if->bdata_attn.irq_enabled;
+
+ /* disable irq when using polling mode */
+ if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq)
+ tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, false);
+
+ LOGD("write command: 0x%02x, payload size: %d\n",
+ command, payload_length);
+
+ syna_pal_mutex_lock(cmd_mutex);
+
+ syna_pal_mutex_lock(rw_mutex);
+
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY);
+
+ /* reset the command completion */
+ syna_pal_completion_reset(cmd_completion);
+
+ tcm_msg->command = command;
+
+ /* request a command execution */
+ retval = syna_tcm_v2_execute_cmd_request(tcm_dev,
+ command,
+ payload,
+ payload_length);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x to device\n", command);
+ goto exit;
+ }
+
+ syna_pal_mutex_unlock(rw_mutex);
+
+ /* waiting for the resp data only at STATUS_ACK */
+ if (tcm_msg->response_code != STATUS_ACK) {
+ syna_tcm_v2_dispatch_response(tcm_dev);
+
+ goto check_response;
+ }
+
+ /* handle the report generated by the command
+ *
+ * assuming to select the polling mode, the while-loop below will
+ * repeatedly read in the respose data based on the given polling
+ * time; otherwise, wait until receiving a completion event from
+ * interupt thread.
+ */
+ timeout = 0;
+ if (!in_polling)
+ polling_ms = CMD_RESPONSE_TIMEOUT_MS;
+ else
+ polling_ms = delay_ms_resp;
+
+ do {
+ /* wait for the completion event triggered by read_message */
+ retval = syna_pal_completion_wait_for(cmd_completion,
+ polling_ms);
+ /* reset the status when times out and keep in polling */
+ if (retval < 0)
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY);
+
+ /* break when geting a valid resp; otherwise, keep in polling */
+ if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_IDLE)
+ goto check_response;
+
+ if (in_polling) {
+ /* retrieve the message packet back */
+ retval = syna_tcm_v2_read_message(tcm_dev, NULL);
+ /* keep in polling if still not having a valid resp */
+ if (retval < 0)
+ syna_pal_completion_reset(cmd_completion);
+ }
+
+ timeout += polling_ms + 10;
+
+ } while (timeout < CMD_RESPONSE_TIMEOUT_MS);
+
+ /* check the status of response data
+ * according to the touchcomm spec, each command message
+ * should have an associated reponse message.
+ */
+check_response:
+ if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_IDLE) {
+ if (timeout >= CMD_RESPONSE_TIMEOUT_MS) {
+ LOGE("Timed out wait for response of command 0x%02x\n",
+ command);
+ retval = _ETIMEDOUT;
+ goto exit;
+ } else {
+ LOGE("Fail to get valid response of command 0x%02x\n",
+ command);
+ retval = _EIO;
+ goto exit;
+ }
+ }
+
+ /* copy response code to the caller */
+ if (resp_code)
+ *resp_code = tcm_msg->response_code;
+
+ if (tcm_msg->response_code != STATUS_OK) {
+ LOGE("Error code 0x%02x of command 0x%02x\n",
+ tcm_msg->response_code, tcm_msg->command);
+ retval = _EIO;
+ } else {
+ retval = 0;
+ }
+
+exit:
+ tcm_msg->command = CMD_NONE;
+
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+
+ syna_pal_mutex_unlock(cmd_mutex);
+
+ /* recovery the irq if using polling mode */
+ if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq)
+ tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, true);
+
+ return retval;
+}
+
+ /**
+ * syna_tcm_v2_detect()
+ *
+ * For TouchCom v2 protocol, the given data must have a valid crc-6 at the end.
+ * If so, send an identify command to identify the device.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: raw 4-byte data
+ * [ in] size: length of input data in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_v2_detect(struct tcm_dev *tcm_dev, unsigned char *data,
+ unsigned int size)
+{
+ int retval;
+ unsigned char resp_code = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!data) || (size < MESSAGE_HEADER_SIZE)) {
+ LOGE("Invalid parameters\n");
+ return _EINVAL;
+ }
+
+ if (syna_tcm_v2_crc6(data, BITS_IN_MESSAGE_HEADER) != 0)
+ return _ENODEV;
+
+ /* send an identify command to identify the device */
+ retval = syna_tcm_v2_write_message(tcm_dev,
+ CMD_IDENTIFY,
+ NULL,
+ 0,
+ &resp_code,
+ RESP_IN_POLLING);
+ if (retval < 0) {
+ LOGE("Fail to get identification info from device\n");
+ return retval;
+ }
+
+ /* expose the read / write operations */
+ tcm_dev->read_message = syna_tcm_v2_read_message;
+ tcm_dev->write_message = syna_tcm_v2_write_message;
+
+ return retval;
+}
diff --git a/tcm/synaptics_touchcom_func_base.c b/tcm/synaptics_touchcom_func_base.c
new file mode 100644
index 0000000..8e83c15
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_base.c
@@ -0,0 +1,1678 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_base.c
+ *
+ * This file implements generic and foundational functions supported in
+ * Synaptics TouchComm communication protocol.
+ *
+ * The declarations are in synaptics_touchcom_func_base.h.
+ */
+
+#include "synaptics_touchcom_func_base.h"
+#include "synaptics_touchcom_func_touch.h"
+
+
+/**
+ * syna_tcm_change_resp_read()
+ *
+ * Helper to change the default resp reading method, which was previously set
+ * when calling syna_tcm_allocate_device
+ *
+ * @param
+ * [in] tcm_dev: the device handle
+ * [in] request: resp reading method to change
+ * set '0' or 'RESP_IN_ATTN' for ATTN-driven; otherwise,
+ * assign a positive value standing for the polling time
+ * @return
+ * none.
+ */
+void syna_tcm_change_resp_read(struct tcm_dev *tcm_dev, unsigned int request)
+{
+ if (request == RESP_IN_ATTN) {
+ tcm_dev->msg_data.default_resp_reading = RESP_IN_ATTN;
+
+ LOGI("Change default resp reading method by attn\n");
+ } else {
+ if (request < RESP_IN_POLLING)
+ request = RESP_IN_POLLING;
+
+ tcm_dev->msg_data.default_resp_reading = request;
+
+ LOGI("Change default resp reading method by polling (%dms)\n",
+ tcm_dev->msg_data.default_resp_reading);
+ }
+}
+
+/**
+ * syna_tcm_init_message_wrap()
+ *
+ * Initialize internal buffers and related structures for command processing.
+ * The function must be called to prepare all essential structures for
+ * command wrapper.
+ *
+ * @param
+ * [in] tcm_msg: message wrapper structure
+ * [in] resp_reading: default method to retrieve the resp data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_init_message_wrap(struct tcm_message_data_blob *tcm_msg,
+ unsigned int resp_reading)
+{
+ /* initialize internal buffers */
+ syna_tcm_buf_init(&tcm_msg->in);
+ syna_tcm_buf_init(&tcm_msg->out);
+ syna_tcm_buf_init(&tcm_msg->temp);
+
+ /* allocate the completion event for command processing */
+ if (syna_pal_completion_alloc(&tcm_msg->cmd_completion) < 0) {
+ LOGE("Fail to allocate cmd completion event\n");
+ return _EINVAL;
+ }
+
+ /* allocate the cmd_mutex for command protection */
+ if (syna_pal_mutex_alloc(&tcm_msg->cmd_mutex) < 0) {
+ LOGE("Fail to allocate cmd_mutex\n");
+ return _EINVAL;
+ }
+
+ /* allocate the rw_mutex for rw protection */
+ if (syna_pal_mutex_alloc(&tcm_msg->rw_mutex) < 0) {
+ LOGE("Fail to allocate rw_mutex\n");
+ return _EINVAL;
+ }
+
+ /* set default state of command_status */
+ ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE);
+
+ /* allocate the internal buffer.in at first */
+ syna_tcm_buf_lock(&tcm_msg->in);
+
+ if (syna_tcm_buf_alloc(&tcm_msg->in, MESSAGE_HEADER_SIZE) < 0) {
+ LOGE("Fail to allocate memory for buf.in (size = %d)\n",
+ MESSAGE_HEADER_SIZE);
+ tcm_msg->in.buf_size = 0;
+ tcm_msg->in.data_length = 0;
+ syna_tcm_buf_unlock(&tcm_msg->in);
+ return _EINVAL;
+ }
+ tcm_msg->in.buf_size = MESSAGE_HEADER_SIZE;
+
+ syna_tcm_buf_unlock(&tcm_msg->in);
+
+ tcm_msg->default_resp_reading = resp_reading;
+
+ LOGI("Resp reading method (default): %s\n",
+ (resp_reading == RESP_IN_ATTN) ? "attn" : "polling");
+
+ return 0;
+}
+
+/**
+ * syna_tcm_del_message_wrap()
+ *
+ * Remove message wrapper interface and internal buffers.
+ * Call the function once the message wrapper is no longer needed.
+ *
+ * @param
+ * [in] tcm_msg: message wrapper structure
+ *
+ * @return
+ * none.
+ */
+static void syna_tcm_del_message_wrap(struct tcm_message_data_blob *tcm_msg)
+{
+ /* release the mutex */
+ syna_pal_mutex_free(&tcm_msg->rw_mutex);
+ syna_pal_mutex_free(&tcm_msg->cmd_mutex);
+
+ /* release the completion event */
+ syna_pal_completion_free(&tcm_msg->cmd_completion);
+
+ /* release internal buffers */
+ syna_tcm_buf_release(&tcm_msg->temp);
+ syna_tcm_buf_release(&tcm_msg->out);
+ syna_tcm_buf_release(&tcm_msg->in);
+}
+
+/**
+ * syna_tcm_allocate_device()
+ *
+ * Create the TouchCom core device handle.
+ * This function must be called in order to allocate the main device handle,
+ * structure syna_tcm_dev, which will be passed to all other operations and
+ * functions within the entire source code.
+ *
+ * Meanwhile, caller has to prepare specific syna_tcm_hw_interface structure,
+ * so that all the implemented functions can access hardware components
+ * through syna_tcm_hw_interface.
+ *
+ * @param
+ * [out] ptcm_dev_ptr: a pointer to the device handle returned
+ * [ in] hw_if: hardware-specific data on target platform
+ * [ in] resp_reading: default resp reading method
+ * set 'RESP_IN_ATTN' to apply ATTN-driven method;
+ * set 'RESP_IN_POLLING' to read in resp by polling
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_allocate_device(struct tcm_dev **ptcm_dev_ptr,
+ struct syna_hw_interface *hw_if, unsigned int resp_reading)
+{
+ int retval = 0;
+ struct tcm_dev *tcm_dev = NULL;
+
+ if (!hw_if) {
+ LOGE("Invalid parameter of hw_if\n");
+ return _EINVAL;
+ }
+
+ if ((!hw_if->ops_read_data) || (!hw_if->ops_write_data)) {
+ LOGE("Invalid hw read write operation\n");
+ return _EINVAL;
+ }
+
+ *ptcm_dev_ptr = NULL;
+
+ /* allocate the core device handle */
+ tcm_dev = (struct tcm_dev *)syna_pal_mem_alloc(
+ 1,
+ sizeof(struct tcm_dev));
+ if (!tcm_dev) {
+ LOGE("Fail to create tcm device handle\n");
+ return _ENOMEM;
+ }
+
+ /* link to the given hardware data */
+ tcm_dev->hw_if = hw_if;
+
+ tcm_dev->max_rd_size = hw_if->bdata_io.rd_chunk_size;
+ tcm_dev->max_wr_size = hw_if->bdata_io.wr_chunk_size;
+
+ tcm_dev->write_message = NULL;
+ tcm_dev->read_message = NULL;
+
+ /* allocate internal buffers */
+ syna_tcm_buf_init(&tcm_dev->report_buf);
+ syna_tcm_buf_init(&tcm_dev->resp_buf);
+ syna_tcm_buf_init(&tcm_dev->external_buf);
+ syna_tcm_buf_init(&tcm_dev->touch_config);
+
+ /* initialize the command wrapper interface */
+ retval = syna_tcm_init_message_wrap(&tcm_dev->msg_data,
+ resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to initialize command interface\n");
+ goto err_init_message_wrap;
+ }
+
+ /* return the created device handle */
+ *ptcm_dev_ptr = tcm_dev;
+
+ LOGI("TouchComm core module created, ver.: %d.%02d\n",
+ (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8),
+ (unsigned char)SYNA_TCM_CORE_LIB_VERSION & 0xff);
+
+ LOGI("Capability: wr_chunk(%d), rd_chunk(%d), irq_control(%s)\n",
+ tcm_dev->max_wr_size, tcm_dev->max_rd_size,
+ (hw_if->ops_enable_irq) ? "yes" : "no");
+
+ return 0;
+
+err_init_message_wrap:
+ syna_tcm_buf_release(&tcm_dev->touch_config);
+ syna_tcm_buf_release(&tcm_dev->external_buf);
+ syna_tcm_buf_release(&tcm_dev->report_buf);
+ syna_tcm_buf_release(&tcm_dev->resp_buf);
+
+ tcm_dev->hw_if = NULL;
+
+ syna_pal_mem_free((void *)tcm_dev);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_remove_device()
+ *
+ * Remove the TouchCom core device handler.
+ * This function must be invoked when the device is no longer needed.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+void syna_tcm_remove_device(struct tcm_dev *tcm_dev)
+{
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return;
+ }
+
+ /* release the command interface */
+ syna_tcm_del_message_wrap(&tcm_dev->msg_data);
+
+ /* release buffers */
+ syna_tcm_buf_release(&tcm_dev->touch_config);
+ syna_tcm_buf_release(&tcm_dev->external_buf);
+ syna_tcm_buf_release(&tcm_dev->report_buf);
+ syna_tcm_buf_release(&tcm_dev->resp_buf);
+
+ tcm_dev->hw_if = NULL;
+
+ /* release the device handle */
+ syna_pal_mem_free((void *)tcm_dev);
+
+ LOGI("tcm device handle removed\n");
+}
+
+/**
+ * syna_tcm_detect_protocol()
+ *
+ * Helper to distinguish which TouchCom firmware is running.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] data: raw data from device
+ * [ in] data_len: length of input data in bytes
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_detect_protocol(struct tcm_dev *tcm_dev,
+ unsigned char *data, unsigned int data_len)
+{
+ int retval;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = syna_tcm_v2_detect(tcm_dev, data, data_len);
+ if (retval < 0)
+ retval = syna_tcm_v1_detect(tcm_dev, data, data_len);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_detect_device()
+ *
+ * Determine the type of device being connected, and distinguish which
+ * version of TouchCom firmware running on the device.
+ * This function must be called before using this TouchComm core library.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, the current mode running on the device is returned;
+ * otherwise, negative value on error.
+ */
+int syna_tcm_detect_device(struct tcm_dev *tcm_dev)
+{
+ int retval = 0;
+ unsigned char data[4] = { 0 };
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_dev->dev_mode = MODE_UNKNOWN;
+
+ /* get the bare data from the bus directly */
+ data[0] = 0x07;
+ retval = syna_tcm_write(tcm_dev, &data[0], 1);
+ if (retval < 0) {
+ LOGE("Fail to write magic to bus\n");
+ return _EIO;
+ }
+
+ retval = syna_tcm_read(tcm_dev,
+ data, (unsigned int)sizeof(data));
+ if (retval < 0) {
+ LOGE("Fail to retrieve 4-byte data from bus\n");
+ return _EIO;
+ }
+
+ LOGD("bare data: %02x %02x %02x %02x\n",
+ data[0], data[1], data[2], data[3]);
+
+ /* distinguish which tcm version running on the device */
+ retval = syna_tcm_detect_protocol(tcm_dev,
+ data, (unsigned int)sizeof(data));
+ if (retval < 0) {
+ LOGE("Fail to detect TouchCom device, %02x %02x %02x %02x\n",
+ data[0], data[1], data[2], data[3]);
+ return retval;
+ }
+
+ if ((!tcm_dev->write_message) || (!tcm_dev->read_message)) {
+ LOGE("Invalid TouchCom rw operations\n");
+ return _ENODEV;
+ }
+
+ /* check the running mode */
+ switch (tcm_dev->dev_mode) {
+ case MODE_APPLICATION_FIRMWARE:
+ LOGI("Device in Application FW, build id: %d, %s\n",
+ tcm_dev->packrat_number,
+ tcm_dev->id_info.part_number);
+ break;
+ case MODE_BOOTLOADER:
+ case MODE_TDDI_BOOTLOADER:
+ LOGI("Device in Bootloader\n");
+ break;
+ case MODE_ROMBOOTLOADER:
+ LOGI("Device in ROMBoot uBL\n");
+ break;
+ case MODE_MULTICHIP_TDDI_BOOTLOADER:
+ LOGI("Device in multi-chip TDDI Bootloader\n");
+ break;
+ default:
+ LOGW("Found TouchCom device, but unsupported mode: 0x%02x\n",
+ tcm_dev->dev_mode);
+ break;
+ }
+
+ retval = tcm_dev->dev_mode;
+ return retval;
+}
+
+/**
+ * syna_tcm_get_event_data()
+ *
+ * Helper to read TouchComm messages when ATTN signal is asserted.
+ * After returning, the ATTN signal should be no longer asserted.
+ *
+ * The 'code' returned will guide the caller on the next action.
+ * For example, do touch reporting once returned code is equal to REPORT_TOUCH.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] code: received report code
+ * [out] data: a user buffer for data returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_event_data(struct tcm_dev *tcm_dev,
+ unsigned char *code, struct tcm_buffer *data)
+{
+ int retval = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!code) {
+ LOGE("Invalid parameter\n");
+ return _EINVAL;
+ }
+
+ /* retrieve the event data */
+ retval = tcm_dev->read_message(tcm_dev,
+ code);
+ if (retval < 0) {
+ LOGE("Fail to read messages\n");
+ return retval;
+ }
+
+ /* exit if no buffer provided */
+ if (!data)
+ goto exit;
+
+ /* if gathering a report, copy to the user buffer */
+ if ((*code >= REPORT_IDENTIFY) && (*code != STATUS_INVALID)) {
+ if (tcm_dev->report_buf.data_length == 0)
+ goto exit;
+
+ syna_tcm_buf_lock(&tcm_dev->report_buf);
+
+ retval = syna_tcm_buf_copy(data, &tcm_dev->report_buf);
+ if (retval < 0) {
+ LOGE("Fail to copy data, report type: %x\n", *code);
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+ goto exit;
+ }
+
+ syna_tcm_buf_unlock(&tcm_dev->report_buf);
+ }
+
+ /* if gathering a response, copy to the user buffer */
+ if ((*code > STATUS_IDLE) && (*code <= STATUS_ERROR)) {
+ if (tcm_dev->resp_buf.data_length == 0)
+ goto exit;
+
+ syna_tcm_buf_lock(&tcm_dev->resp_buf);
+
+ retval = syna_tcm_buf_copy(data, &tcm_dev->resp_buf);
+ if (retval < 0) {
+ LOGE("Fail to copy data, status code: %x\n", *code);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ goto exit;
+ }
+
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_identify()
+ *
+ * Implement the standard command code, which is used to request
+ * an IDENTIFY report packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] id_info: the identification info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_identify(struct tcm_dev *tcm_dev,
+ struct tcm_identification_info *id_info)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_IDENTIFY,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n", CMD_IDENTIFY);
+ goto exit;
+ }
+
+ tcm_dev->dev_mode = tcm_dev->id_info.mode;
+
+ if (id_info == NULL)
+ goto show_info;
+
+ /* copy identify info to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)id_info,
+ sizeof(struct tcm_identification_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ MIN(sizeof(*id_info), tcm_dev->resp_buf.data_length));
+ if (retval < 0) {
+ LOGE("Fail to copy identify info to caller\n");
+ goto exit;
+ }
+
+show_info:
+ LOGI("TCM Fw mode: 0x%02x, TCM ver.: %d\n",
+ tcm_dev->id_info.mode, tcm_dev->id_info.version);
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_reset()
+ *
+ * Implement the standard command code, which is used to perform a sw reset
+ * immediately. After a successful reset, an IDENTIFY report to indicate that
+ * device is ready.
+ *
+ * Caller shall be aware that the firmware will be reloaded after reset.
+ * Therefore, if expecting that a different firmware version is loaded, please
+ * do app firmware setup after reset.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_reset(struct tcm_dev *tcm_dev)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int board_setting;
+ unsigned int resp_handling;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ resp_handling = tcm_dev->msg_data.default_resp_reading;
+
+ /* gather the board settings of reset delay time */
+ board_setting = tcm_dev->hw_if->bdata_rst.reset_delay_ms;
+ if (board_setting == 0)
+ board_setting = RESET_DELAY_MS;
+
+ /* select the proper period to handle the resp of reset */
+ if (resp_handling != RESP_IN_ATTN) {
+ if (board_setting > resp_handling) {
+ resp_handling = board_setting;
+ LOGI("Use board settings %dms to poll resp of reset\n",
+ resp_handling);
+ }
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_RESET,
+ NULL,
+ 0,
+ &resp_code,
+ resp_handling);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n", CMD_RESET);
+ goto exit;
+ }
+
+ /* current device mode is expected to be updated
+ * because identification report will be received after reset
+ */
+ tcm_dev->dev_mode = tcm_dev->id_info.mode;
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGI("Device mode 0x%02X running after reset\n",
+ tcm_dev->dev_mode);
+ }
+
+ retval = 0;
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_enable_report()
+ *
+ * Implement the application fw command code to enable or disable a report.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] report_code: the requested report code being generated
+ * [ in] en: '1' for enabling; '0' for disabling
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_enable_report(struct tcm_dev *tcm_dev,
+ unsigned char report_code, bool en)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned char command;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ command = (en) ? CMD_ENABLE_REPORT : CMD_DISABLE_REPORT;
+
+ retval = tcm_dev->write_message(tcm_dev,
+ command,
+ &report_code,
+ 1,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x to %s 0x%02x report\n",
+ command, (en)?"enable":"disable", report_code);
+ goto exit;
+ }
+
+ if (resp_code != STATUS_OK) {
+ LOGE("Fail to %s 0x%02x report, resp_code:%x\n",
+ (en) ? "enable" : "disable", report_code, resp_code);
+ } else {
+ LOGD("Report 0x%x %s\n", report_code,
+ (en) ? "enabled" : "disabled");
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_run_rom_bootloader_fw()
+ *
+ * Requests that the rombootloader firmware be run.
+ * Once the rombootloader firmware has finished starting, an IDENTIFY report
+ * to indicate that it is in the new mode.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] fw_switch_delay: delay time for fw mode switching.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_run_rom_bootloader_fw(struct tcm_dev *tcm_dev,
+ unsigned int fw_switch_delay)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_REBOOT_TO_ROM_BOOTLOADER,
+ NULL,
+ 0,
+ &resp_code,
+ fw_switch_delay);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_REBOOT_TO_ROM_BOOTLOADER);
+ goto exit;
+ }
+
+ if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGE("Fail to enter rom bootloader, mode: %x\n",
+ tcm_dev->dev_mode);
+ retval = _ENODEV;
+ goto exit;
+ }
+
+ LOGI("ROM Bootloader (mode 0x%x) activated\n",
+ tcm_dev->dev_mode);
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_run_bootloader_fw()
+ *
+ * Requests that the bootloader firmware be run.
+ * Once the bootloader firmware has finished starting, an IDENTIFY report
+ * to indicate that it is in the new mode.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] fw_switch_delay: delay time for fw mode switching.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_run_bootloader_fw(struct tcm_dev *tcm_dev,
+ unsigned int fw_switch_delay)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_RUN_BOOTLOADER_FIRMWARE,
+ NULL,
+ 0,
+ &resp_code,
+ fw_switch_delay);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_RUN_BOOTLOADER_FIRMWARE);
+ goto exit;
+ }
+
+ if (!IS_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGE("Fail to enter bootloader, mode: %x\n",
+ tcm_dev->dev_mode);
+ retval = _ENODEV;
+ goto exit;
+ }
+
+ LOGI("Bootloader Firmware (mode 0x%x) activated\n",
+ tcm_dev->dev_mode);
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_run_application_fw()
+ *
+ * Requests that the application firmware be run.
+ * Once the application firmware has finished starting, an IDENTIFY report
+ * to indicate that it is in the new mode.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] fw_switch_delay: delay time for fw mode switching.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_run_application_fw(struct tcm_dev *tcm_dev,
+ unsigned int fw_switch_delay)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_RUN_APPLICATION_FIRMWARE,
+ NULL,
+ 0,
+ &resp_code,
+ fw_switch_delay);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_RUN_APPLICATION_FIRMWARE);
+ goto exit;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGW("Fail to enter application fw, mode: %x\n",
+ tcm_dev->dev_mode);
+ retval = _ENODEV;
+ goto exit;
+ }
+
+ LOGI("Application Firmware (mode 0x%x) activated\n",
+ tcm_dev->dev_mode);
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_switch_fw_mode()
+ *
+ * Implement the command code to switch the firmware mode.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] mode: target firmware mode
+ * [ in] fw_switch_delay: delay time for fw mode switching.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_switch_fw_mode(struct tcm_dev *tcm_dev,
+ unsigned char mode, unsigned int fw_switch_delay)
+{
+ int retval = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ switch (mode) {
+ case MODE_APPLICATION_FIRMWARE:
+ retval = syna_tcm_run_application_fw(tcm_dev,
+ fw_switch_delay);
+ if (retval < 0) {
+ LOGE("Fail to switch to application mode\n");
+ goto exit;
+ }
+ break;
+ case MODE_BOOTLOADER:
+ case MODE_TDDI_BOOTLOADER:
+ case MODE_TDDI_HDL_BOOTLOADER:
+ case MODE_MULTICHIP_TDDI_BOOTLOADER:
+ retval = syna_tcm_run_bootloader_fw(tcm_dev,
+ fw_switch_delay);
+ if (retval < 0) {
+ LOGE("Fail to switch to bootloader mode\n");
+ goto exit;
+ }
+ break;
+ case MODE_ROMBOOTLOADER:
+ retval = syna_tcm_run_rom_bootloader_fw(tcm_dev,
+ fw_switch_delay);
+ if (retval < 0) {
+ LOGE("Fail to switch to rom bootloader mode\n");
+ goto exit;
+ }
+ break;
+ default:
+ LOGE("Invalid firmware mode requested\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_get_boot_info()
+ *
+ * Implement the bootloader command code, which is used to request a
+ * boot information packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] boot_info: the boot info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_boot_info(struct tcm_dev *tcm_dev,
+ struct tcm_boot_info *boot_info)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int resp_data_len = 0;
+ unsigned int copy_size;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_BOOT_INFO,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_GET_BOOT_INFO);
+ goto exit;
+ }
+
+ resp_data_len = tcm_dev->resp_buf.data_length;
+ copy_size = MIN(sizeof(struct tcm_boot_info), resp_data_len);
+
+ /* save the boot_info */
+ retval = syna_pal_mem_cpy((unsigned char *)&tcm_dev->boot_info,
+ sizeof(struct tcm_boot_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ copy_size);
+ if (retval < 0) {
+ LOGE("Fail to copy boot info\n");
+ goto exit;
+ }
+
+ if (boot_info == NULL)
+ goto exit;
+
+ /* copy boot_info to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)boot_info,
+ sizeof(struct tcm_boot_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ copy_size);
+ if (retval < 0) {
+ LOGE("Fail to copy boot info to caller\n");
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_get_app_info()
+ *
+ * Implement the application fw command code to request an application
+ * info packet from device.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] app_info: the application info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_app_info(struct tcm_dev *tcm_dev,
+ struct tcm_application_info *app_info)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int app_status;
+ unsigned int resp_data_len = 0;
+ unsigned int copy_size;
+ struct tcm_application_info *info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_APPLICATION_INFO,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_GET_APPLICATION_INFO);
+ goto exit;
+ }
+
+ resp_data_len = tcm_dev->resp_buf.data_length;
+ copy_size = MIN(sizeof(tcm_dev->app_info), resp_data_len);
+
+ info = &tcm_dev->app_info;
+
+ /* save the app_info */
+ retval = syna_pal_mem_cpy((unsigned char *)info,
+ sizeof(struct tcm_application_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ copy_size);
+ if (retval < 0) {
+ LOGE("Fail to copy application info\n");
+ goto exit;
+ }
+
+ if (app_info == NULL)
+ goto show_info;
+
+ /* copy app_info to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)app_info,
+ sizeof(struct tcm_application_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ copy_size);
+ if (retval < 0) {
+ LOGE("Fail to copy application info to caller\n");
+ goto exit;
+ }
+
+show_info:
+ app_status = syna_pal_le2_to_uint(tcm_dev->app_info.status);
+
+ if (app_status == APP_STATUS_BAD_APP_CONFIG) {
+ LOGE("Bad application firmware, status: 0x%x\n", app_status);
+ retval = _ENODEV;
+ goto exit;
+ } else if (app_status != APP_STATUS_OK) {
+ LOGE("Incorrect application status, 0x%x\n", app_status);
+ retval = _ENODEV;
+ goto exit;
+ }
+
+ tcm_dev->max_objects = syna_pal_le2_to_uint(info->max_objects);
+ tcm_dev->max_x = syna_pal_le2_to_uint(info->max_x);
+ tcm_dev->max_y = syna_pal_le2_to_uint(info->max_y);
+
+ tcm_dev->cols = syna_pal_le2_to_uint(info->num_of_image_cols);
+ tcm_dev->rows = syna_pal_le2_to_uint(info->num_of_image_rows);
+ syna_pal_mem_cpy((unsigned char *)tcm_dev->config_id,
+ MAX_SIZE_CONFIG_ID,
+ info->customer_config_id,
+ MAX_SIZE_CONFIG_ID,
+ MAX_SIZE_CONFIG_ID);
+
+ LOGD("App info version: %d, status: %d\n",
+ syna_pal_le2_to_uint(info->version), app_status);
+ LOGD("App info: max_objs: %d, max_x:%d, max_y: %d, img: %dx%d\n",
+ tcm_dev->max_objects, tcm_dev->max_x, tcm_dev->max_y,
+ tcm_dev->rows, tcm_dev->cols);
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_get_static_config()
+ *
+ * Implement the application fw command code to retrieve the contents of
+ * the static configuration.
+ *
+ * The size of static configuration is available in app info packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] buf: buffer stored the static configuration
+ * [ in] buf_size: the size of given buffer
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_static_config(struct tcm_dev *tcm_dev,
+ unsigned char *buf, unsigned int buf_size)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int size;
+ struct tcm_application_info *app_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ app_info = &tcm_dev->app_info;
+
+ size = syna_pal_le2_to_uint(app_info->static_config_size);
+
+ if (size > buf_size) {
+ LOGE("Invalid buffer input, given size: %d (actual: %d)\n",
+ buf_size, size);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_STATIC_CONFIG,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_GET_STATIC_CONFIG);
+ goto exit;
+ }
+
+ if (buf == NULL)
+ goto exit;
+
+ /* copy app_info to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)buf,
+ buf_size,
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ tcm_dev->resp_buf.data_length);
+ if (retval < 0) {
+ LOGE("Fail to copy static config data to caller\n");
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_set_static_config()
+ *
+ * Implement the application fw command code to set the contents of
+ * the static configuration. When the write is completed, the device will
+ * restart touch sensing with the new settings.
+ *
+ * The size of static configuration is available in app info packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] config_data: the data of static configuration
+ * [ in] config_data_size: the size of given data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_static_config(struct tcm_dev *tcm_dev,
+ unsigned char *config_data, unsigned int config_data_size)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int size;
+ struct tcm_application_info *app_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ app_info = &tcm_dev->app_info;
+
+ size = syna_pal_le2_to_uint(app_info->static_config_size);
+
+ if (size != config_data_size) {
+ LOGE("Invalid static config size, given: %d (actual: %d)\n",
+ config_data_size, size);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_SET_STATIC_CONFIG,
+ config_data,
+ config_data_size,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_SET_STATIC_CONFIG);
+ goto exit;
+ }
+
+ retval = 0;
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_get_dynamic_config()
+ *
+ * Implement the application fw command code to get the value from the a single
+ * field of the dynamic configuration.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] id: target field id
+ * [out] value: the value returned
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_dynamic_config(struct tcm_dev *tcm_dev,
+ unsigned char id, unsigned short *value,
+ unsigned int delay_ms_resp)
+{
+ int retval = 0;
+ unsigned char out;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ out = (unsigned char)id;
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_DYNAMIC_CONFIG,
+ &out,
+ sizeof(out),
+ &resp_code,
+ delay_ms_resp);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x to get dynamic field 0x%x\n",
+ CMD_GET_DYNAMIC_CONFIG, (unsigned char)id);
+ goto exit;
+ }
+
+ /* return dynamic config data */
+ if (tcm_dev->resp_buf.data_length < 2) {
+ LOGE("Invalid resp data size, %d\n",
+ tcm_dev->resp_buf.data_length);
+ goto exit;
+ }
+
+ *value = (unsigned short)syna_pal_le2_to_uint(tcm_dev->resp_buf.buf);
+
+ LOGD("Get %d from dynamic field 0x%x\n", *value, id);
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_set_dynamic_config()
+ *
+ * Implement the application fw command code to set the specified value to
+ * the selected field of the dynamic configuration.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] id: target field id
+ * [ in] value: the value to the selected field
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_dynamic_config(struct tcm_dev *tcm_dev,
+ unsigned char id, unsigned short value,
+ unsigned int delay_ms_resp)
+{
+ int retval = 0;
+ unsigned char out[3];
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ LOGD("Set %d to dynamic field 0x%x\n", value, id);
+
+ out[0] = (unsigned char)id;
+ out[1] = (unsigned char)value;
+ out[2] = (unsigned char)(value >> 8);
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_SET_DYNAMIC_CONFIG,
+ out,
+ sizeof(out),
+ &resp_code,
+ delay_ms_resp);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x to set %d to field 0x%x\n",
+ CMD_SET_DYNAMIC_CONFIG, value, (unsigned char)id);
+ goto exit;
+ }
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_rezero()
+ *
+ * Implement the application fw command code to force the device to rezero its
+ * baseline estimate.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_rezero(struct tcm_dev *tcm_dev)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_REZERO,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_REZERO);
+ goto exit;
+ }
+
+ retval = 0;
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_set_config_id()
+ *
+ * Implement the application fw command code to set the 16-byte config id,
+ * which can be read in the app info packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] config_id: config id to be set
+ * [ in] size: size of input data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_config_id(struct tcm_dev *tcm_dev,
+ unsigned char *config_id, unsigned int size)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int config_id_len = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ config_id_len = sizeof(tcm_dev->app_info.customer_config_id);
+
+ if (size != config_id_len) {
+ LOGE("Invalid config id input, given size: %d (%d)\n",
+ size, config_id_len);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_SET_CONFIG_ID,
+ config_id,
+ size,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_SET_CONFIG_ID);
+ goto exit;
+ }
+
+ retval = 0;
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_sleep()
+ *
+ * Implement the application fw command code to put the device into low power
+ * deep sleep mode or set to normal active mode.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] en: '1' to low power deep sleep mode; '0' to active mode
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_sleep(struct tcm_dev *tcm_dev, bool en)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned char command;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ command = (en) ? CMD_ENTER_DEEP_SLEEP : CMD_EXIT_DEEP_SLEEP;
+
+ retval = tcm_dev->write_message(tcm_dev,
+ command,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%x\n", command);
+ goto exit;
+ }
+
+ retval = 0;
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_get_features()
+ *
+ * Implement the application fw command code to query the supported features.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] info: the features description packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_features(struct tcm_dev *tcm_dev,
+ struct tcm_features_info *info)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_FEATURES,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_GET_FEATURES);
+ goto exit;
+ }
+
+ if (info == NULL)
+ goto exit;
+
+ /* copy features_info to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)info,
+ sizeof(struct tcm_features_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ MIN(sizeof(*info), tcm_dev->resp_buf.data_length));
+ if (retval < 0) {
+ LOGE("Fail to copy features_info to caller\n");
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_run_production_test()
+ *
+ * Implement the appplication fw command code to request the device to run
+ * the production test.
+ *
+ * Production tests are listed at enum test_code (PID$).
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] test_item: the requested testing item
+ * [out] tdata: testing data returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_run_production_test(struct tcm_dev *tcm_dev,
+ unsigned char test_item, struct tcm_buffer *tdata)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned char test_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in application fw mode, mode: %x\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ test_code = (unsigned char)test_item;
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_PRODUCTION_TEST,
+ &test_code,
+ 1,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_PRODUCTION_TEST);
+ goto exit;
+ }
+
+ if (tdata == NULL)
+ goto exit;
+
+ /* copy testing data to caller */
+ retval = syna_tcm_buf_copy(tdata, &tcm_dev->resp_buf);
+ if (retval < 0) {
+ LOGE("Fail to copy testing data\n");
+ goto exit;
+ }
+exit:
+ return retval;
+}
+/**
+ * syna_tcm_send_command()
+ *
+ * Helper to forward the custom commnd to the device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: TouchComm command
+ * [ in] payload: data payload, if any
+ * [ in] payload_length: length of data payload, if any
+ * [out] resp_code: response code returned
+ * [out] resp: buffer to store the response data
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_send_command(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_length, unsigned char *resp_code,
+ struct tcm_buffer *resp, unsigned int delay_ms_resp)
+{
+ int retval = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!resp_code) {
+ LOGE("Invalid parameter\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ command,
+ payload,
+ payload_length,
+ resp_code,
+ delay_ms_resp);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ command);
+ goto exit;
+ }
+
+ /* response data returned */
+ if ((resp != NULL) && (tcm_dev->resp_buf.data_length > 0)) {
+ retval = syna_tcm_buf_copy(resp, &tcm_dev->resp_buf);
+ if (retval < 0) {
+ LOGE("Fail to copy resp data\n");
+ goto exit;
+ }
+ }
+
+exit:
+ return retval;
+}
+
diff --git a/tcm/synaptics_touchcom_func_base.h b/tcm/synaptics_touchcom_func_base.h
new file mode 100644
index 0000000..d0a155b
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_base.h
@@ -0,0 +1,418 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_base.h
+ *
+ * This file declares generic and foundational APIs being used to communicate
+ * with Synaptics touch controller through TouchComm communication protocol.
+ */
+
+#ifndef _SYNAPTICS_TOUCHCOM_BASE_FUNCS_H_
+#define _SYNAPTICS_TOUCHCOM_BASE_FUNCS_H_
+
+#include "synaptics_touchcom_core_dev.h"
+
+/**
+ * syna_tcm_allocate_device()
+ *
+ * Create the TouchCom core device handle.
+ * This function must be called in order to allocate the main device handle,
+ * structure syna_tcm_dev, which will be passed to all other operations and
+ * functions within the entire source code.
+ *
+ * Meanwhile, caller has to prepare specific syna_tcm_hw_interface structure,
+ * so that all the implemented functions can access hardware components
+ * through syna_tcm_hw_interface.
+ *
+ * @param
+ * [out] ptcm_dev_ptr: a pointer to the device handle returned
+ * [ in] hw_if: hardware-specific data on target platform
+ * [ in] resp_reading: default resp reading method
+ * set 'RESP_IN_ATTN' to apply ATTN-driven method;
+ * set 'RESP_IN_POLLING' to read in resp by polling
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_allocate_device(struct tcm_dev **ptcm_dev_ptr,
+ struct syna_hw_interface *hw_if, unsigned int resp_reading);
+
+/**
+ * syna_tcm_remove_device()
+ *
+ * Remove the TouchCom core device handle.
+ * This function must be invoked when the device is no longer needed.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * none.
+ */
+void syna_tcm_remove_device(struct tcm_dev *tcm_dev);
+
+/**
+ * syna_tcm_detect_device()
+ *
+ * Determine the type of device being connected and distinguish which
+ * version of TouchCom firmware running on the device.
+ * This function should be called before using this TouchComm core library.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, the current mode running on the device is returned;
+ * otherwise, negative value on error.
+ */
+int syna_tcm_detect_device(struct tcm_dev *tcm_dev);
+
+/**
+ * syna_tcm_get_event_data()
+ *
+ * Helper to read TouchComm messages when ATTN signal is asserted.
+ * After returning, the ATTN signal should be no longer asserted.
+ *
+ * The 'code' returned will guide the caller on the next action.
+ * For example, do touch reporting once returned code is equal to REPORT_TOUCH.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] code: received report code
+ * [out] report: report data returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_event_data(struct tcm_dev *tcm_dev,
+ unsigned char *code,
+ struct tcm_buffer *report);
+
+/**
+ * syna_tcm_change_resp_read()
+ *
+ * Helper to change the default resp reading method, which was previously set
+ * when calling syna_tcm_allocate_device
+ *
+ * @param
+ * [in] tcm_dev: the device handle
+ * [in] request: resp reading method to change
+ * set '0' or 'RESP_IN_ATTN' for ATTN-driven; otherwise,
+ * assign a positive value standing for the polling time
+ * @return
+ * none.
+ */
+void syna_tcm_change_resp_read(struct tcm_dev *tcm_dev, unsigned int request);
+
+/**
+ * syna_tcm_identify()
+ *
+ * Implement the standard command code, which is used to request
+ * an IDENTIFY report packet
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] id_info: the identification info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_identify(struct tcm_dev *tcm_dev,
+ struct tcm_identification_info *id_info);
+
+/**
+ * syna_tcm_reset()
+ *
+ * Implement the standard command code, which is used to perform a sw reset
+ * immediately. After a successful reset, an IDENTIFY report to indicate that
+ * device is ready.
+ *
+ * Caller shall be aware that the firmware will be reloaded after reset.
+ * Therefore, if expecting that a different firmware version is loaded, please
+ * do app firmware setup after reset.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_reset(struct tcm_dev *tcm_dev);
+
+/**
+ * syna_tcm_enable_report()
+ *
+ * Implement the application fw command code to enable or disable a report.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] report_code: the requested report code being generated
+ * [ in] en: '1' for enabling; '0' for disabling
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_enable_report(struct tcm_dev *tcm_dev,
+ unsigned char report_code, bool en);
+
+/**
+ * syna_tcm_switch_fw_mode()
+ *
+ * Implement the command code to switch the firmware mode.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] mode: target firmware mode
+ * [ in] fw_switch_delay: delay time for fw mode switching.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_switch_fw_mode(struct tcm_dev *tcm_dev,
+ unsigned char mode, unsigned int fw_switch_delay);
+
+/**
+ * syna_tcm_get_boot_info()
+ *
+ * Implement the bootloader command code, which is used to request a
+ * boot information packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] boot_info: the boot info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_boot_info(struct tcm_dev *tcm_dev,
+ struct tcm_boot_info *boot_info);
+
+/**
+ * syna_tcm_get_app_info()
+ *
+ * Implement the application fw command code to request an application
+ * info packet from device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] app_info: the application info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_app_info(struct tcm_dev *tcm_dev,
+ struct tcm_application_info *app_info);
+
+/**
+ * syna_tcm_get_static_config()
+ *
+ * Implement the application fw command code to retrieve the contents of
+ * the static configuration.
+ *
+ * The size of static configuration is available in app info packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] buf: buffer stored the static configuration
+ * [ in] buf_size: the size of given buffer
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_static_config(struct tcm_dev *tcm_dev,
+ unsigned char *buf, unsigned int buf_size);
+
+/**
+ * syna_tcm_set_static_config()
+ *
+ * Implement the application fw command code to set the contents of
+ * the static configuration. When the write is completed, the device will
+ * restart touch sensing with the new settings
+ *
+ * The size of static configuration is available in app info packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] config_data: the data of static configuration
+ * [ in] config_data_size: the size of given data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_static_config(struct tcm_dev *tcm_dev,
+ unsigned char *config_data, unsigned int config_data_size);
+
+/**
+ * syna_tcm_get_dynamic_config()
+ *
+ * Implement the application fw command code to get the value from the a single
+ * field of the dynamic configuration
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] id: target field id
+ * [out] value: the value returned
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_dynamic_config(struct tcm_dev *tcm_dev,
+ unsigned char id, unsigned short *value,
+ unsigned int delay_ms_resp);
+
+/**
+ * syna_tcm_set_dynamic_config()
+ *
+ * Implement the application fw command code to set the specified value to
+ * the selected field of the dynamic configuration
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] id: target field id
+ * [ in] value: the value to the selected field
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_dynamic_config(struct tcm_dev *tcm_dev,
+ unsigned char id, unsigned short value,
+ unsigned int delay_ms_resp);
+
+/**
+ * syna_tcm_rezero()
+ *
+ * Implement the application fw command code to force the device to rezero its
+ * baseline estimate.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_rezero(struct tcm_dev *tcm_dev);
+
+/**
+ * syna_tcm_set_config_id()
+ *
+ * Implement the application fw command code to set the 16-byte config id,
+ * which can be read in the app info packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] config_id: config id to be set
+ * [ in] size: size of input data
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_config_id(struct tcm_dev *tcm_dev,
+ unsigned char *config_id, unsigned int size);
+
+/**
+ * syna_tcm_sleep()
+ *
+ * Implement the application fw command code to put the device into low power
+ * deep sleep mode or set to normal active mode
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] en: '1' to low power deep sleep mode; '0' to active mode
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_sleep(struct tcm_dev *tcm_dev, bool en);
+
+/**
+ * syna_tcm_get_features()
+ *
+ * Implement the application fw command code to query the supported features.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] info: the features description packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_features(struct tcm_dev *tcm_dev,
+ struct tcm_features_info *info);
+
+/**
+ * syna_tcm_run_production_test()
+ *
+ * Implement the appplication fw command code to request the device to run
+ * the production test.
+ *
+ * Production tests are listed at enum test_code (PID$).
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] test_item: the requested testing item
+ * [out] tdata: testing data returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_run_production_test(struct tcm_dev *tcm_dev,
+ unsigned char test_item, struct tcm_buffer *tdata);
+
+/**
+ * syna_tcm_send_command()
+ *
+ * Helper to forward the custom commnd to the device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: TouchComm command
+ * [ in] payload: data payload, if any
+ * [ in] payload_length: length of data payload, if any
+ * [out] resp_code: response code returned
+ * [out] resp: buffer to store the response data
+ * [ in] delay_ms_resp: delay time for response reading.
+ * a positive value presents the time for polling;
+ * or, set '0' or 'RESP_IN_ATTN' for ATTN driven
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_send_command(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_length, unsigned char *resp_code,
+ struct tcm_buffer *resp, unsigned int delay_ms_resp);
+
+
+#endif /* end of _SYNAPTICS_TOUCHCOM_BASE_FUNCS_H_ */
diff --git a/tcm/synaptics_touchcom_func_base_flash.h b/tcm/synaptics_touchcom_func_base_flash.h
new file mode 100644
index 0000000..87d0a4e
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_base_flash.h
@@ -0,0 +1,569 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_base_flash.h
+ *
+ * This file declares the common functions and structures being used in relevant
+ * functions of fw update.
+ */
+
+#ifndef _SYNAPTICS_TOUCHCOM_PARSE_FW_FILES_H_
+#define _SYNAPTICS_TOUCHCOM_PARSE_FW_FILES_H_
+
+#include "synaptics_touchcom_core_dev.h"
+
+/**
+ * @section: Some specific definition in the firmware file
+ */
+#define ID_STRING_SIZE (32)
+
+#define SIZE_WORDS (8)
+
+#define IHEX_RECORD_SIZE (14)
+
+#define IHEX_MAX_BLOCKS (64)
+
+#define IMAGE_FILE_MAGIC_VALUE (0x4818472b)
+
+#define FLASH_AREA_MAGIC_VALUE (0x7c05e516)
+
+
+/**
+ * @section: Helper macros for firmware file parsing
+ */
+#define CRC32(data, length) \
+ (syna_pal_crc32(~0, data, length) ^ ~0)
+
+#define VALUE(value) \
+ (syna_pal_le2_to_uint(value))
+
+#define AREA_ID_STR(area) \
+ (syna_tcm_get_flash_area_string(area))
+
+
+/**
+ * @section: Area Partitions in firmware
+ */
+enum flash_area {
+ AREA_NONE = 0,
+ /* please add the declarations below */
+
+ AREA_BOOT_CODE,
+ AREA_BOOT_CONFIG,
+ AREA_APP_CODE,
+ AREA_APP_CODE_COPRO,
+ AREA_APP_CONFIG,
+ AREA_PROD_TEST,
+ AREA_DISP_CONFIG,
+ AREA_F35_APP_CODE,
+ AREA_FORCE_TUNING,
+ AREA_GAMMA_TUNING,
+ AREA_TEMPERATURE_GAMM_TUNING,
+ AREA_CUSTOM_LCM,
+ AREA_LOOKUP,
+ AREA_CUSTOM_OEM,
+ AREA_OPEN_SHORT_TUNING,
+ AREA_CUSTOM_OTP,
+ AREA_PPDT,
+ AREA_ROMBOOT_APP_CODE,
+ AREA_TOOL_BOOT_CONFIG,
+
+ /* please add the declarations above */
+ AREA_MAX,
+};
+/**
+ * @section: String of Area Partitions in firmware
+ */
+static char *flash_area_str[] = {
+ NULL,
+ /* please add the declarations below */
+
+ "BOOT_CODE", /* AREA_BOOT_CODE */
+ "BOOT_CONFIG", /* AREA_BOOT_CONFIG */
+ "APP_CODE", /* AREA_APP_CODE */
+ "APP_CODE_COPRO", /* AREA_APP_CODE_COPRO */
+ "APP_CONFIG", /* AREA_APP_CONFIG */
+ "APP_PROD_TEST", /* AREA_PROD_TEST */
+ "DISPLAY", /* AREA_DISP_CONFIG */
+ "F35_APP_CODE", /* AREA_F35_APP_CODE */
+ "FORCE", /* AREA_FORCE_TUNING */
+ "GAMMA", /* AREA_GAMMA_TUNING */
+ "TEMPERATURE_GAMM",/* AREA_TEMPERATURE_GAMM_TUNING */
+ "LCM", /* AREA_CUSTOM_LCM */
+ "LOOKUP", /* AREA_LOOKUP */
+ "OEM", /* AREA_CUSTOM_OEM */
+ "OPEN_SHORT", /* AREA_OPEN_SHORT_TUNING */
+ "OTP", /* AREA_CUSTOM_OTP */
+ "PPDT", /* AREA_PPDT */
+ "ROMBOOT_APP_CODE",/* AREA_ROMBOOT_APP_CODE */
+ "TOOL_BOOT_CONFIG",/* AREA_TOOL_BOOT_CONFIG */
+
+ /* please add the declarations above */
+ NULL
+};
+/**
+ * @section: Header Content of app config defined
+ * in firmware file
+ */
+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];
+};
+/**
+ * @section: The Partition Descriptor defined
+ * in firmware file
+ */
+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];
+};
+/**
+ * @section: Structure for the Data Block defined
+ * in firmware file
+ */
+struct block_data {
+ bool available;
+ const unsigned char *data;
+ unsigned int size;
+ unsigned int flash_addr;
+ unsigned char id;
+};
+/**
+ * @section: Structure for the Parsed Image File
+ */
+struct image_info {
+ struct block_data data[AREA_MAX];
+};
+/**
+ * @section: Header of Image File
+ *
+ * Define the header of firmware image file
+ */
+struct image_header {
+ unsigned char magic_value[4];
+ unsigned char num_of_areas[4];
+};
+/**
+ * @section: Structure for the Parsed iHex File
+ */
+struct ihex_info {
+ unsigned int records;
+ unsigned char *bin;
+ unsigned int bin_size;
+ struct block_data block[IHEX_MAX_BLOCKS];
+};
+
+/**
+ * syna_tcm_get_flash_area_string()
+ *
+ * Return the string ID of target area in the flash memory
+ *
+ * @param
+ * [ in] area: target flash area
+ *
+ * @return
+ * the string ID
+ */
+static inline char *syna_tcm_get_flash_area_string(enum flash_area area)
+{
+ if (area < AREA_MAX)
+ return (char *)flash_area_str[area];
+ else
+ return "";
+}
+
+/**
+ * syna_tcm_save_flash_block_data()
+ *
+ * Save the block data of flash memory to the corresponding structure.
+ *
+ * @param
+ * [out] image_info: image info used for storing the block data
+ * [ in] area: target area
+ * [ in] content: content of data
+ * [ in] flash_addr: offset of block data
+ * [ in] size: size of block data
+ * [ in] checksum: checksum of block data
+ *
+ * @return
+ * on success, return 0; otherwise, negative value on error.
+ */
+static int syna_tcm_save_flash_block_data(struct image_info *image_info,
+ enum flash_area area, const unsigned char *content,
+ unsigned int offset, unsigned int size, unsigned int checksum)
+{
+ if (!image_info) {
+ LOGE("Invalid image_info\n");
+ return _EINVAL;
+ }
+
+ if (area >= AREA_MAX) {
+ LOGE("Invalid flash area\n");
+ return _EINVAL;
+ }
+
+ if (checksum != CRC32((const char *)content, size)) {
+ LOGE("%s checksum error, in image: 0x%x (0x%x)\n",
+ AREA_ID_STR(area), checksum,
+ CRC32((const char *)content, size));
+ return _EINVAL;
+ }
+ image_info->data[area].size = size;
+ image_info->data[area].data = content;
+ image_info->data[area].flash_addr = offset;
+ image_info->data[area].id = (unsigned char)area;
+ image_info->data[area].available = true;
+
+ LOGI("%s area - address:0x%08x (%d), size:%d\n",
+ AREA_ID_STR(area), offset, offset, size);
+
+ return 0;
+}
+
+/**
+ * syna_tcm_get_flash_area_id()
+ *
+ * Return the corresponding ID of flash area based on the given string
+ *
+ * @param
+ * [ in] str: string to look for
+ *
+ *
+ * @return
+ * if matching, return the corresponding ID; otherwise, return AREA_MAX.
+ */
+static enum flash_area syna_tcm_get_flash_area_id(char *str)
+{
+ int area;
+ char *target;
+ unsigned int len;
+
+ for (area = AREA_MAX - 1; area >= 0; area--) {
+ target = AREA_ID_STR(area);
+ len = syna_pal_str_len(target);
+
+ if (syna_pal_str_cmp(str, target, len) == 0)
+ return area;
+ }
+
+ LOGW("Un-defined area string, %s\n", str);
+ return AREA_MAX;
+}
+
+/**
+ * syna_tcm_parse_fw_image()
+ *
+ * Parse and analyze the information of each areas from the given
+ * firmware image.
+ *
+ * @param
+ * [ in] image: image file given
+ * [ in] image_info: data blob stored the parsed data from an image file
+ *
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static inline int syna_tcm_parse_fw_image(const unsigned char *image,
+ struct image_info *image_info)
+{
+ int retval = 0;
+ 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 area_descriptor *descriptor;
+ const unsigned char *content;
+ enum flash_area target_area;
+
+ if (!image) {
+ LOGE("No image data\n");
+ return _EINVAL;
+ }
+
+ if (!image_info) {
+ LOGE("Invalid image_info blob\n");
+ return _EINVAL;
+ }
+
+ syna_pal_mem_set(image_info, 0x00, sizeof(struct image_info));
+
+ header = (struct image_header *)image;
+
+ magic_value = syna_pal_le4_to_uint(header->magic_value);
+ if (magic_value != IMAGE_FILE_MAGIC_VALUE) {
+ LOGE("Invalid image file magic value\n");
+ return _EINVAL;
+ }
+
+ offset = sizeof(struct image_header);
+ num_of_areas = syna_pal_le4_to_uint(header->num_of_areas);
+
+ for (idx = 0; idx < num_of_areas; idx++) {
+ addr = syna_pal_le4_to_uint(image + offset);
+ descriptor = (struct area_descriptor *)(image + addr);
+ offset += 4;
+
+ magic_value = syna_pal_le4_to_uint(descriptor->magic_value);
+ if (magic_value != FLASH_AREA_MAGIC_VALUE)
+ continue;
+
+ length = syna_pal_le4_to_uint(descriptor->length);
+ content = (unsigned char *)descriptor + sizeof(*descriptor);
+ flash_addr = syna_pal_le4_to_uint(descriptor->flash_addr_words);
+ flash_addr = flash_addr * 2;
+ checksum = syna_pal_le4_to_uint(descriptor->checksum);
+
+ target_area = syna_tcm_get_flash_area_id(
+ (char *)descriptor->id_string);
+
+ retval = syna_tcm_save_flash_block_data(image_info,
+ target_area,
+ content,
+ flash_addr,
+ length,
+ checksum);
+ if (retval < 0)
+ return _EINVAL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * syna_tcm_parse_ihex_line()
+ *
+ * Parse a line in the ihex file and convert into an actual data
+ *
+ * @param
+ * [ in] line: a line of string stored in the ihex file
+ * [out] count: size of actual data
+ * [out] addr: address of data located
+ * [out] type: the type of data belonging
+ * [out] buf: a buffer to store the converted data
+ * [int] buf_size: size of buffer
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static inline int syna_tcm_parse_ihex_line(char *line, unsigned int *count,
+ unsigned int *addr, unsigned int *type, unsigned char *buf,
+ unsigned int buf_size)
+{
+ const int OFFSET_COUNT = 1;
+ const int SIZE_COUNT = 2;
+ const int OFFSET_ADDR = OFFSET_COUNT + SIZE_COUNT;
+ const int SIZE_ADDR = 4;
+ const int OFFSET_TYPE = OFFSET_ADDR + SIZE_ADDR;
+ const int SIZE_TYPE = 2;
+ const int OFFSET_DATA = OFFSET_TYPE + SIZE_TYPE;
+ const int SIZE_DATA = 2;
+ unsigned int pos;
+
+ if (!line) {
+ LOGE("No string line\n");
+ return _EINVAL;
+ }
+
+ if ((!buf) || (buf_size == 0)) {
+ LOGE("Invalid temporary data buffer\n");
+ return _EINVAL;
+ }
+
+ *count = syna_pal_hex_to_uint(
+ line + OFFSET_COUNT, 2);
+ *addr = syna_pal_hex_to_uint(
+ line + OFFSET_ADDR, SIZE_ADDR);
+ *type = syna_pal_hex_to_uint(
+ line + OFFSET_TYPE, SIZE_TYPE);
+
+ if (*count > buf_size) {
+ LOGE("Data size mismatched, required:%d, given:%d\n",
+ *count, buf_size);
+ return _EINVAL;
+ }
+
+ for (pos = 0; pos < *count; pos++)
+ buf[pos] = (unsigned char)syna_pal_hex_to_uint(
+ line + (((int)(pos << 1)) + OFFSET_DATA),
+ SIZE_DATA);
+
+ return 0;
+}
+
+/**
+ * syna_tcm_parse_fw_ihex()
+ *
+ * Based on the firmware ihex file given , parse and convert into a binary
+ * firmware data to update.
+ *
+ * @param
+ * [ in] ihex: original ihex file
+ * [ in] ihex_size: size of given file
+ * [ in] ihex_info: data blob stored the parsed data from an ihex file.
+ * assume the data buffer inside was allocated
+ * [ in] len_per_line: length for a useful data in a line
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static inline int syna_tcm_parse_fw_ihex(const char *ihex, int ihex_size,
+ struct ihex_info *ihex_info, const unsigned int len_per_line)
+{
+ int retval;
+ unsigned int pos;
+ unsigned int record;
+ char *tmp = NULL;
+ unsigned int count;
+ unsigned int type;
+ unsigned char data[32] = { 0 };
+ unsigned int addr;
+ unsigned int offset;
+ unsigned int prev_addr;
+ unsigned int block_idx = 0;
+
+ if (!ihex) {
+ LOGE("No ihex data\n");
+ return _EINVAL;
+ }
+
+ if (!ihex_info) {
+ LOGE("Invalid ihex_info blob\n");
+ return _EINVAL;
+ }
+
+ if ((!ihex_info->bin) || (ihex_info->bin_size == 0)) {
+ LOGE("Invalid ihex_info->data\n");
+ return _EINVAL;
+ }
+
+ tmp = syna_pal_mem_alloc(len_per_line + 1, sizeof(char));
+ if (!tmp) {
+ LOGE("Fail to allocate temporary buffer\n");
+ return _ENOMEM;
+ }
+
+ offset = 0;
+ addr = 0;
+ pos = 0;
+ prev_addr = 0;
+
+ ihex_info->records = ihex_size / len_per_line;
+ LOGD("records = %d\n", ihex_info->records);
+
+ for (record = 0; record < ihex_info->records; record++) {
+ pos = record * len_per_line;
+ if ((char)ihex[pos] != ':') {
+ LOGE("Invalid string maker at pos %d, marker:%c\n",
+ pos, (char)ihex[pos]);
+ goto exit;
+ }
+
+ retval = syna_pal_mem_cpy(tmp, len_per_line,
+ &ihex[pos], ihex_size - pos, len_per_line);
+ if (retval < 0) {
+ LOGE("Fail to copy a line at pos %d\n", pos);
+ goto exit;
+ }
+
+ retval = syna_tcm_parse_ihex_line(tmp, &count, &addr, &type,
+ data, sizeof(data));
+ if (retval < 0) {
+ LOGE("Fail to parse line at pos %d\n", pos);
+ goto exit;
+ }
+
+ if ((((prev_addr + 2) & 0xFFFF) != addr) && (type == 0x00)) {
+ block_idx = (record == 0) ? 0 : block_idx + 1;
+ if (block_idx >= IHEX_MAX_BLOCKS) {
+ LOGE("Invalid block index\n");
+ goto exit;
+ }
+
+ ihex_info->block[block_idx].flash_addr =
+ addr + offset;
+ ihex_info->block[block_idx].data =
+ &ihex_info->bin[addr + offset];
+ ihex_info->block[block_idx].available = true;
+ }
+
+ if (type == 0x00) {
+
+ prev_addr = addr;
+ addr += offset;
+
+ if (addr >= ihex_info->bin_size) {
+ LOGE("No enough size for data0 addr:0x%x(%d)\n",
+ addr, addr);
+ goto exit;
+ }
+ ihex_info->bin[addr++] = data[0];
+
+ if (addr >= ihex_info->bin_size) {
+ LOGE("No enough size for data1 addr:0x%x(%d)\n",
+ addr, addr);
+ goto exit;
+ }
+ ihex_info->bin[addr++] = data[1];
+
+ ihex_info->block[block_idx].size += 2;
+
+ } else if (type == 0x02) {
+ offset = (data[0] << 8) + data[1];
+ offset <<= 4;
+ }
+ }
+
+ ihex_info->bin_size = addr; /* the actual size after data reordering */
+ LOGN("Size of firmware binary data = %d\n", ihex_info->bin_size);
+
+exit:
+ syna_pal_mem_free((void *)tmp);
+
+ return 0;
+}
+
+
+#endif /* end of _SYNAPTICS_TOUCHCOM_PARSE_FW_FILES_H_ */
diff --git a/tcm/synaptics_touchcom_func_reflash.c b/tcm/synaptics_touchcom_func_reflash.c
new file mode 100644
index 0000000..af67b4a
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_reflash.c
@@ -0,0 +1,2074 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_reflash.c
+ *
+ * This file implements the fw reflash related functions of TouchBoot.
+ * The declarations are available in synaptics_touchcom_func_reflash.h.
+ */
+
+#include "synaptics_touchcom_func_base.h"
+#include "synaptics_touchcom_func_reflash.h"
+
+/**
+ * @section: Reflash relevant definitions
+ *
+ */
+#define FLASH_READ_DELAY_MS (10)
+#define FLASH_WRITE_DELAY_MS (20)
+#define FLASH_ERASE_DELAY_MS (500)
+
+#define BOOT_CONFIG_SIZE 8
+#define BOOT_CONFIG_SLOTS 16
+
+#define DO_NONE (0)
+#define DO_UPDATE (1)
+
+/**
+ * syna_tcm_set_up_flash_access()
+ *
+ * Enter the bootloader fw if not in the mode.
+ * Besides, get the necessary parameters in boot info.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] reflash_data: data blob for reflash
+ *
+ * @return
+ * Result of image file comparison
+ */
+static int syna_tcm_set_up_flash_access(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data)
+{
+ int retval;
+ unsigned int temp;
+ struct tcm_identification_info id_info;
+ struct tcm_boot_info *boot_info;
+ unsigned int wr_chunk;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ LOGI("Set up flash access\n");
+
+ retval = syna_tcm_identify(tcm_dev, &id_info);
+ if (retval < 0) {
+ LOGE("Fail to do identification\n");
+ return retval;
+ }
+
+ /* switch to bootloader mode */
+ if (IS_APP_FW_MODE(id_info.mode)) {
+ LOGI("Prepare to enter bootloader mode\n");
+
+ retval = syna_tcm_switch_fw_mode(tcm_dev,
+ MODE_BOOTLOADER,
+ FW_MODE_SWITCH_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to enter bootloader mode\n");
+ return retval;
+ }
+ }
+
+ if (!IS_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGE("Fail to enter bootloader mode (current: 0x%x)\n",
+ tcm_dev->dev_mode);
+ return retval;
+ }
+
+ boot_info = &tcm_dev->boot_info;
+
+ /* get boot info to set up the flash access */
+ retval = syna_tcm_get_boot_info(tcm_dev, boot_info);
+ if (retval < 0) {
+ LOGE("Fail to get boot info at mode 0x%x\n",
+ id_info.mode);
+ return retval;
+ }
+
+ wr_chunk = tcm_dev->max_wr_size;
+
+ temp = boot_info->write_block_size_words;
+ reflash_data->write_block_size = temp * 2;
+
+ LOGI("Write block size: %d (words size: %d)\n",
+ reflash_data->write_block_size, temp);
+
+ temp = syna_pal_le2_to_uint(boot_info->erase_page_size_words);
+ reflash_data->page_size = temp * 2;
+
+ LOGI("Erase page size: %d (words size: %d)\n",
+ reflash_data->page_size, temp);
+
+ temp = syna_pal_le2_to_uint(boot_info->max_write_payload_size);
+ reflash_data->max_write_payload_size = temp;
+
+ LOGI("Max write flash data size: %d\n",
+ reflash_data->max_write_payload_size);
+
+ if (reflash_data->write_block_size > (wr_chunk - 9)) {
+ LOGE("Write block size, %d, greater than chunk space, %d\n",
+ reflash_data->write_block_size, (wr_chunk - 9));
+ return _EINVAL;
+ }
+
+ if (reflash_data->write_block_size == 0) {
+ LOGE("Invalid write block size %d\n",
+ reflash_data->write_block_size);
+ return _EINVAL;
+ }
+
+ if (reflash_data->page_size == 0) {
+ LOGE("Invalid erase page size %d\n",
+ reflash_data->page_size);
+ return _EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_tcm_compare_image_id_info()
+ *
+ * Compare the ID information between device and the image file,
+ * and then determine the area to be updated.
+ * The function should be called after parsing the image file.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ *
+ * @return
+ * Blocks to be updated
+ */
+int syna_tcm_compare_image_id_info(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data)
+{
+ enum update_area result;
+ 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;
+ const unsigned char *app_config_data;
+ struct block_data *app_config;
+
+ result = UPDATE_NONE;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash_data\n");
+ return _EINVAL;
+ }
+
+ app_config = &reflash_data->image_info.data[AREA_APP_CONFIG];
+
+ if (app_config->size < sizeof(struct app_config_header)) {
+ LOGE("Invalid application config in image file\n");
+ return _EINVAL;
+ }
+
+ app_config_data = app_config->data;
+ header = (struct app_config_header *)app_config_data;
+
+ image_fw_id = syna_pal_le4_to_uint(header->build_id);
+ device_fw_id = tcm_dev->packrat_number;
+
+ LOGN("Device firmware ID: %d, image build id: %d\n",
+ device_fw_id, image_fw_id);
+
+ if (image_fw_id > device_fw_id) {
+ LOGN("Image build ID newer than device fw ID\n");
+ result = UPDATE_FIRMWARE_CONFIG;
+ goto exit;
+ } else {
+ result = UPDATE_NONE;
+ goto exit;
+ }
+
+ image_config_id = header->customer_config_id;
+ device_config_id = tcm_dev->app_info.customer_config_id;
+
+ for (idx = 0; idx < MAX_SIZE_CONFIG_ID; idx++) {
+ if (image_config_id[idx] != device_config_id[idx]) {
+ LOGN("Different Config ID\n");
+ result = UPDATE_CONFIG_ONLY;
+ goto exit;
+ }
+ }
+
+ result = UPDATE_NONE;
+
+exit:
+ switch (result) {
+ case UPDATE_FIRMWARE_CONFIG:
+ LOGN("Update firmware and config\n");
+ break;
+ case UPDATE_CONFIG_ONLY:
+ LOGN("Update config only\n");
+ break;
+ case UPDATE_NONE:
+ default:
+ LOGN("No need to do reflash\n");
+ break;
+ }
+
+ return (int)result;
+}
+
+/**
+ * syna_tcm_check_flash_boot_config()
+ *
+ * Check whether the same flash address of boot config in between the device
+ * and the image file.
+ *
+ * @param
+ * [ in] boot_config: block data of boot_config from image file
+ * [ in] boot_info: data of boot info
+ * [ in] block_size: max size of write block
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_boot_config(struct block_data *boot_config,
+ struct tcm_boot_info *boot_info, unsigned int block_size)
+{
+ unsigned int start_block;
+ unsigned int image_addr;
+ unsigned int device_addr;
+
+ if (!boot_config) {
+ LOGE("Invalid boot_config block data\n");
+ return _EINVAL;
+ }
+
+ if (!boot_info) {
+ LOGE("Invalid boot_info\n");
+ return _EINVAL;
+ }
+
+ if (boot_config->size < BOOT_CONFIG_SIZE) {
+ LOGE("No valid BOOT_CONFIG size, %d, in image file\n",
+ boot_config->size);
+ return _EINVAL;
+ }
+
+ image_addr = boot_config->flash_addr;
+
+ LOGD("Boot Config address in image file: 0x%x\n", image_addr);
+
+ start_block = VALUE(boot_info->boot_config_start_block);
+ device_addr = start_block * block_size;
+
+ LOGD("Boot Config address in device: 0x%x\n", device_addr);
+
+ return DO_NONE;
+}
+
+/**
+ * syna_tcm_check_flash_app_config()
+ *
+ * Check whether the same flash address of app config in between the
+ * device and the image file.
+ *
+ * @param
+ * [ in] app_config: block data of app_config from image file
+ * [ in] app_info: data of application info
+ * [ in] block_size: max size of write block
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_app_config(struct block_data *app_config,
+ struct tcm_application_info *app_info, unsigned int block_size)
+{
+ unsigned int temp;
+ unsigned int image_addr;
+ unsigned int image_size;
+ unsigned int device_addr;
+ unsigned int device_size;
+
+ if (!app_config) {
+ LOGE("Invalid app_config block data\n");
+ return _EINVAL;
+ }
+
+ if (!app_info) {
+ LOGE("Invalid app_info\n");
+ return _EINVAL;
+ }
+
+ if (app_config->size == 0) {
+ LOGD("No APP_CONFIG in image file\n");
+ return DO_NONE;
+ }
+
+ image_addr = app_config->flash_addr;
+ image_size = app_config->size;
+
+ LOGD("App Config address in image file: 0x%x, size: %d\n",
+ image_addr, image_size);
+
+ temp = VALUE(app_info->app_config_start_write_block);
+ device_addr = temp * block_size;
+ device_size = VALUE(app_info->app_config_size);
+
+ LOGD("App Config address in device: 0x%x, size: %d\n",
+ device_addr, device_size);
+
+ if (device_addr == 0 && device_size == 0)
+ return DO_UPDATE;
+
+ if (image_addr != device_addr)
+ LOGW("App Config address mismatch, image:0x%x, dev:0x%x\n",
+ image_addr, device_addr);
+
+ if (image_size != device_size)
+ LOGW("App Config address size mismatch, image:%d, dev:%d\n",
+ image_size, device_size);
+
+ return DO_UPDATE;
+}
+
+/**
+ * syna_tcm_check_flash_disp_config()
+ *
+ * Check whether the same flash address of display config in between the
+ * device and the image file.
+ *
+ * @param
+ * [ in] disp_config: block data of disp_config from image file
+ * [ in] boot_info: data of boot info
+ * [ in] block_size: max size of write block
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_disp_config(struct block_data *disp_config,
+ struct tcm_boot_info *boot_info, unsigned int block_size)
+{
+ unsigned int temp;
+ unsigned int image_addr;
+ unsigned int image_size;
+ unsigned int device_addr;
+ unsigned int device_size;
+
+ if (!disp_config) {
+ LOGE("Invalid disp_config block data\n");
+ return _EINVAL;
+ }
+
+ if (!boot_info) {
+ LOGE("Invalid boot_info\n");
+ return _EINVAL;
+ }
+
+ /* disp_config area may not be included in all product */
+ if (disp_config->size == 0) {
+ LOGD("No DISP_CONFIG in image file\n");
+ return DO_NONE;
+ }
+
+ image_addr = disp_config->flash_addr;
+ image_size = disp_config->size;
+
+ LOGD("Disp Config address in image file: 0x%x, size: %d\n",
+ image_addr, image_size);
+
+ temp = VALUE(boot_info->display_config_start_block);
+ device_addr = temp * block_size;
+
+ temp = VALUE(boot_info->display_config_length_blocks);
+ device_size = temp * block_size;
+
+ LOGD("Disp Config address in device: 0x%x, size: %d\n",
+ device_addr, device_size);
+
+ if (image_addr != device_addr)
+ LOGW("Disp Config address mismatch, image:0x%x, dev:0x%x\n",
+ image_addr, device_addr);
+
+ if (image_size != device_size)
+ LOGW("Disp Config address size mismatch, image:%d, dev:%d\n",
+ image_size, device_size);
+
+ return DO_UPDATE;
+}
+
+/**
+ * syna_tcm_check_flash_app_code()
+ *
+ * Check whether the valid size of app firmware in the image file
+ *
+ * @param
+ * [ in] app_code: block data of app_code from image file
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_app_code(struct block_data *app_code)
+{
+ if (!app_code) {
+ LOGE("Invalid app_code block data\n");
+ return _EINVAL;
+ }
+
+ if (app_code->size == 0) {
+ LOGD("No %s in image file\n", AREA_ID_STR(app_code->id));
+ return _EINVAL;
+ }
+
+ return DO_UPDATE;
+}
+
+/**
+ * syna_tcm_check_flash_openshort()
+ *
+ * Check whether the valid size of openshort area in the image file
+ *
+ * @param
+ * [ in] open_short: block data of open_short from image file
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_openshort(struct block_data *open_short)
+{
+ if (!open_short) {
+ LOGE("Invalid open_short block data\n");
+ return _EINVAL;
+ }
+
+ /* open_short area may not be included in all product */
+ if (open_short->size == 0) {
+ LOGD("No %s in image file\n", AREA_ID_STR(open_short->id));
+ return DO_NONE;
+ }
+
+ return DO_UPDATE;
+}
+
+/**
+ * syna_tcm_check_flash_app_prod_test()
+ *
+ * Check whether the valid size of app prod_test area in the image file
+ *
+ * @param
+ * [ in] prod_test: block data of app_prod_test from image file
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_app_prod_test(struct block_data *prod_test)
+{
+ if (!prod_test) {
+ LOGE("Invalid app_prod_test block data\n");
+ return _EINVAL;
+ }
+
+ /* app_prod_test area may not be included in all product */
+ if (prod_test->size == 0) {
+ LOGD("No %s in image file\n", AREA_ID_STR(prod_test->id));
+ return DO_NONE;
+ }
+
+ return DO_UPDATE;
+}
+
+/**
+ * syna_tcm_check_flash_ppdt()
+ *
+ * Check whether the valid size of ppdt area in the image file
+ *
+ * @param
+ * [ in] ppdt: block data of PPDT from image file
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_ppdt(struct block_data *ppdt)
+{
+ if (!ppdt) {
+ LOGE("Invalid ppdt block data\n");
+ return _EINVAL;
+ }
+
+ /* open_short area may not be included in all product */
+ if (ppdt->size == 0) {
+ LOGD("No %s in image file\n", AREA_ID_STR(ppdt->id));
+ return DO_NONE;
+ }
+
+ return DO_UPDATE;
+}
+
+/**
+ * syna_tcm_check_flash_block()
+ *
+ * Dispatch to the proper helper to ensure the data of associated block area
+ * is correct in between the device and the image file.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] block: target block area to check
+ *
+ * @return
+ * '0' represent no need to do reflash;
+ * '1' represent a positive result of checking;
+ * otherwise, negative value on error.
+ */
+static int syna_tcm_check_flash_block(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct block_data *block)
+{
+ int retval = 0;
+ struct tcm_application_info *app_info;
+ struct tcm_boot_info *boot_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return DO_NONE;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return DO_NONE;
+ }
+
+ if (!block) {
+ LOGE("Invalid block data\n");
+ return DO_NONE;
+ }
+
+ app_info = &tcm_dev->app_info;
+ boot_info = &tcm_dev->boot_info;
+
+ switch (block->id) {
+ case AREA_APP_CODE:
+ retval = syna_tcm_check_flash_app_code(block);
+ break;
+ case AREA_APP_CONFIG:
+ retval = syna_tcm_check_flash_app_config(block, app_info,
+ reflash_data->write_block_size);
+ break;
+ case AREA_BOOT_CONFIG:
+ retval = syna_tcm_check_flash_boot_config(block, boot_info,
+ reflash_data->write_block_size);
+ break;
+ case AREA_DISP_CONFIG:
+ retval = syna_tcm_check_flash_disp_config(block, boot_info,
+ reflash_data->write_block_size);
+ break;
+ case AREA_OPEN_SHORT_TUNING:
+ retval = syna_tcm_check_flash_openshort(block);
+ break;
+ case AREA_PROD_TEST:
+ retval = syna_tcm_check_flash_app_prod_test(block);
+ break;
+ case AREA_PPDT:
+ retval = syna_tcm_check_flash_ppdt(block);
+ break;
+ default:
+ retval = DO_NONE;
+ break;
+ }
+
+ return retval;
+}
+
+/**
+ * syna_tcm_get_flash_data_location()
+ *
+ * Return the address and length of the specified data area
+ * in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] area: specified area in flash memory
+ * [out] addr: the flash address of the specified area returned
+ * [out] len: the size of the specified area returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_get_flash_data_location(struct tcm_dev *tcm_dev,
+ enum flash_area area, unsigned int *addr, unsigned int *len)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned char payload;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ switch (area) {
+ case AREA_CUSTOM_LCM:
+ payload = FLASH_LCM_DATA;
+ break;
+ case AREA_CUSTOM_OEM:
+ payload = FLASH_OEM_DATA;
+ break;
+ case AREA_PPDT:
+ payload = FLASH_PPDT_DATA;
+ break;
+ case AREA_FORCE_TUNING:
+ payload = FLASH_FORCE_CALIB_DATA;
+ break;
+ case AREA_OPEN_SHORT_TUNING:
+ payload = FLASH_OPEN_SHORT_TUNING_DATA;
+ break;
+ default:
+ LOGE("Invalid flash area %d\n", area);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_DATA_LOCATION,
+ &payload,
+ sizeof(payload),
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_GET_DATA_LOCATION);
+ goto exit;
+ }
+
+ if (tcm_dev->resp_buf.data_length != 4) {
+ LOGE("Invalid data length %d\n",
+ tcm_dev->resp_buf.data_length);
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ *addr = syna_pal_le2_to_uint(&tcm_dev->resp_buf.buf[0]);
+ *len = syna_pal_le2_to_uint(&tcm_dev->resp_buf.buf[2]);
+
+ retval = 0;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_reflash_send_command()
+ *
+ * Helper to wrap up the write_message() function.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: given command code
+ * [ in] payload: payload data, if any
+ * [ in] payload_len: length of payload data
+ * [ in] delay_ms_resp: delay time to get the response of command
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_reflash_send_command(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *payload,
+ unsigned int payload_len, unsigned int delay_ms_resp)
+{
+ int retval = 0;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!IS_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device is not in BL mode, 0x%x\n", tcm_dev->dev_mode);
+ retval = _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ command,
+ payload,
+ payload_len,
+ &resp_code,
+ delay_ms_resp);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n", command);
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_read_flash()
+ *
+ * Implement the bootloader command to read specified data from flash memory.
+ *
+ * Reads to the protected bootloader code or application code areas will read
+ * as 0. If the number of words requested is too large, it may be truncated to
+ * an defined maximum read size.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] address: the address in flash memory to read
+ * [out] rd_data: data retrieved
+ * [ in] rd_len: length of data to be read
+ * [ in] rd_delay_ms: a short delay after the command executed
+ * set 'DEFAULT_FLASH_READ_DELAY' to use default,
+ * which is 10 us;
+ * set '0' or 'RESP_IN_ATTN' to select ATTN-driven.
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_read_flash(struct tcm_dev *tcm_dev,
+ unsigned int address, unsigned char *rd_data,
+ unsigned int rd_len, unsigned int rd_delay_ms)
+{
+ int retval = 0;
+ unsigned int length_words;
+ unsigned int flash_addr_words;
+ unsigned char out[6] = { 0 };
+ unsigned int delay_ms = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid rd_data buffer\n");
+ return _EINVAL;
+ }
+
+ if (address == 0 || rd_len == 0) {
+ LOGE("Invalid flash address and length\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ length_words = rd_len / 2;
+ flash_addr_words = address / 2;
+
+ LOGD("Flash address: 0x%x (words: 0x%x), size: %d (words: %d)\n",
+ address, flash_addr_words, rd_len, length_words);
+
+ out[0] = (unsigned char)flash_addr_words;
+ out[1] = (unsigned char)(flash_addr_words >> 8);
+ out[2] = (unsigned char)(flash_addr_words >> 16);
+ out[3] = (unsigned char)(flash_addr_words >> 24);
+ out[4] = (unsigned char)length_words;
+ out[5] = (unsigned char)(length_words >> 8);
+
+ if (rd_delay_ms == DEFAULT_FLASH_READ_DELAY)
+ delay_ms = FLASH_READ_DELAY_MS;
+ else
+ delay_ms = rd_delay_ms;
+
+ if (delay_ms == RESP_IN_ATTN) {
+ LOGD("xfer: %d, delay: ATTN-driven\n", length_words);
+ } else {
+ delay_ms = (delay_ms * length_words) / 1000;
+ LOGD("xfer: %d, delay: %d\n", length_words, delay_ms);
+ }
+
+ retval = syna_tcm_reflash_send_command(tcm_dev,
+ CMD_READ_FLASH,
+ out,
+ sizeof(out),
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to read flash data from addr 0x%x, size %d\n",
+ address, rd_len);
+ goto exit;
+ }
+
+ if (tcm_dev->resp_buf.data_length != rd_len) {
+ LOGE("Fail to read requested length %d, rd_len %d\n",
+ tcm_dev->resp_buf.data_length, rd_len);
+ retval = _EIO;
+ goto exit;
+ }
+
+ retval = syna_pal_mem_cpy(rd_data,
+ rd_len,
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ rd_len);
+ if (retval < 0) {
+ LOGE("Fail to copy read data, size %d\n", rd_len);
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_read_flash_boot_config()
+ *
+ * Read the data of boot config area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [out] rd_data: buffer used for storing the retrieved data
+ * [ in] rd_delay_us: delay time to access data in flash memory
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_read_flash_boot_config(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct tcm_buffer *rd_data, unsigned int rd_delay_us)
+{
+ int retval;
+ unsigned int temp;
+ unsigned int addr = 0;
+ unsigned int length = 0;
+ struct tcm_boot_info *boot_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid read data buffer\n");
+ return _EINVAL;
+ }
+
+ boot_info = &tcm_dev->boot_info;
+
+ if (IS_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("BOOT_CONFIG not available in app fw mode %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ temp = VALUE(boot_info->boot_config_start_block);
+ addr = temp * reflash_data->write_block_size;
+ length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS;
+
+ if (addr == 0 || length == 0) {
+ LOGE("BOOT_CONFIG data area unavailable\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ LOGD("BOOT_CONFIG address: 0x%x, length: %d\n", addr, length);
+
+ retval = syna_tcm_buf_alloc(rd_data, length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for rd_data buffer\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf,
+ length, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to read BOOT_CONFIG area (addr: 0x%x, length: %d)\n",
+ addr, length);
+ goto exit;
+ }
+
+ rd_data->data_length = length;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_read_flash_app_config()
+ *
+ * Read the data of app config area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [out] rd_data: buffer used for storing the retrieved data
+ * [ in] rd_delay_us: delay time to access data in flash memory
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_read_flash_app_config(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct tcm_buffer *rd_data, unsigned int rd_delay_us)
+{
+ int retval;
+ unsigned int temp;
+ unsigned int addr = 0;
+ unsigned int length = 0;
+ struct tcm_application_info *app_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid read data buffer\n");
+ return _EINVAL;
+ }
+
+ app_info = &tcm_dev->app_info;
+
+ if (IS_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("APP_CONFIG not available in app fw mode %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ temp = VALUE(app_info->app_config_start_write_block);
+ addr = temp * reflash_data->write_block_size;
+ length = VALUE(app_info->app_config_size);
+
+ if (addr == 0 || length == 0) {
+ LOGE("APP_CONFIG data area unavailable\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ LOGD("APP_CONFIG address: 0x%x, length: %d\n", addr, length);
+
+ retval = syna_tcm_buf_alloc(rd_data, length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for rd_data buffer\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf,
+ length, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to read APP_CONFIG area (addr: 0x%x, length: %d)\n",
+ addr, length);
+ goto exit;
+ }
+
+ rd_data->data_length = length;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_read_flash_disp_config()
+ *
+ * Read the data of display config area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [out] rd_data: buffer used for storing the retrieved data
+ * [ in] rd_delay_us: delay time to access data in flash memory
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_read_flash_disp_config(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct tcm_buffer *rd_data, unsigned int rd_delay_us)
+{
+ int retval;
+ unsigned int temp;
+ unsigned int addr = 0;
+ unsigned int length = 0;
+ struct tcm_boot_info *boot_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid read data buffer\n");
+ return _EINVAL;
+ }
+
+ boot_info = &tcm_dev->boot_info;
+
+ if (IS_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("DISP_CONFIG not available in app fw mode %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ temp = VALUE(boot_info->display_config_start_block);
+ addr = temp * reflash_data->write_block_size;
+ temp = VALUE(boot_info->display_config_length_blocks);
+ length = temp * reflash_data->write_block_size;
+
+ if (addr == 0 || length == 0) {
+ LOGE("DISP_CONFIG data area unavailable\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ LOGD("DISP_CONFIG address: 0x%x, length: %d\n", addr, length);
+
+ retval = syna_tcm_buf_alloc(rd_data, length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for rd_data buffer\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf,
+ length, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to read DISP_CONFIG area (addr: 0x%x, length: %d)\n",
+ addr, length);
+ goto exit;
+ }
+
+ rd_data->data_length = length;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_read_flash_custom_otp()
+ *
+ * Read the data of custom OTP area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [out] rd_data: buffer used for storing the retrieved data
+ * [ in] rd_delay_us: delay time to access data in flash memory
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_read_flash_custom_otp(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct tcm_buffer *rd_data, unsigned int rd_delay_us)
+{
+ int retval;
+ unsigned int temp;
+ unsigned int addr = 0;
+ unsigned int length = 0;
+ struct tcm_boot_info *boot_info;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid read data buffer\n");
+ return _EINVAL;
+ }
+
+ boot_info = &tcm_dev->boot_info;
+
+ if (IS_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("CUSTOM_OTP not available in app fw mode %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ temp = VALUE(boot_info->custom_otp_start_block);
+ addr = temp * reflash_data->write_block_size;
+ temp = VALUE(boot_info->custom_otp_length_blocks);
+ length = temp * reflash_data->write_block_size;
+
+ if (addr == 0 || length == 0) {
+ LOGE("CUSTOM_OTP data area unavailable\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ LOGD("CUSTOM_OTP address: 0x%x, length: %d\n", addr, length);
+
+ retval = syna_tcm_buf_alloc(rd_data, length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for rd_data buffer\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf,
+ length, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to read CUSTOM_OTP area (addr: 0x%x, length: %d)\n",
+ addr, length);
+ goto exit;
+ }
+
+ rd_data->data_length = length;
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_read_flash_custom_data()
+ *
+ * Read the data of custom data in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] address: address generated by get_flash_data_location()
+ * [ in] size: size generated by get_flash_data_location()
+ * [out] rd_data: buffer used for storing the retrieved data
+ * [ in] rd_delay_us: delay time to access data in flash memory
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_read_flash_custom_data(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ unsigned int address, unsigned int size,
+ struct tcm_buffer *rd_data, unsigned int rd_delay_us)
+{
+ int retval;
+ unsigned int addr = 0;
+ unsigned int length = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid read data buffer\n");
+ return _EINVAL;
+ }
+
+ if (IS_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Custom data not available in app fw mode %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ addr = address * reflash_data->write_block_size;
+ length = size * reflash_data->write_block_size;
+
+ if (addr == 0 || length == 0) {
+ LOGE("Custom data area unavailable\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ LOGD("Custom data address: 0x%x, length: %d\n", addr, length);
+
+ retval = syna_tcm_buf_alloc(rd_data, length);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for rd_data buffer\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf,
+ length, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to read custom data (addr: 0x%x, length: %d)\n",
+ addr, length);
+ goto exit;
+ }
+
+ rd_data->data_length = length;
+
+exit:
+ return retval;
+}
+/**
+ * syna_tcm_read_flash_area()
+ *
+ * Entry function to read in the data of specific area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] area: flash area to read
+ * [out] data: buffer storing the retrieved data
+ * [ in] rd_delay_us: delay time in micro-sec to read words data from flash.
+ * set 'DEFAULT_FLASH_READ_DELAY' to use default,
+ * which is 10 us;
+ * set '0' or 'RESP_IN_ATTN' to select ATTN-driven.
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_read_flash_area(struct tcm_dev *tcm_dev,
+ enum flash_area area, struct tcm_buffer *rd_data,
+ unsigned int rd_delay_us)
+{
+ int retval;
+ unsigned int addr = 0;
+ unsigned int length = 0;
+ struct tcm_reflash_data_blob reflash_data;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!rd_data) {
+ LOGE("Invalid data buffer\n");
+ return _EINVAL;
+ }
+
+ switch (area) {
+ case AREA_CUSTOM_LCM:
+ case AREA_CUSTOM_OEM:
+ case AREA_PPDT:
+ case AREA_FORCE_TUNING:
+ case AREA_OPEN_SHORT_TUNING:
+ retval = syna_tcm_get_flash_data_location(tcm_dev,
+ area, &addr, &length);
+ if (retval < 0) {
+ LOGE("Fail to get data location of 0x%x\n", area);
+ return retval;
+ }
+ break;
+ default:
+ break;
+ }
+
+ retval = syna_tcm_set_up_flash_access(tcm_dev,
+ &reflash_data);
+ if (retval < 0) {
+ LOGE("Fail to set up flash access\n");
+ return retval;
+ }
+
+ syna_tcm_buf_init(&reflash_data.out);
+
+ switch (area) {
+ case AREA_BOOT_CONFIG:
+ retval = syna_tcm_read_flash_boot_config(tcm_dev,
+ &reflash_data, rd_data, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to get boot config data\n");
+ goto exit;
+ }
+ break;
+ case AREA_APP_CONFIG:
+ retval = syna_tcm_read_flash_app_config(tcm_dev,
+ &reflash_data, rd_data, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to get app config data\n");
+ goto exit;
+ }
+ break;
+ case AREA_DISP_CONFIG:
+ retval = syna_tcm_read_flash_disp_config(tcm_dev,
+ &reflash_data, rd_data, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to get disp config data\n");
+ goto exit;
+ }
+ break;
+ case AREA_CUSTOM_OTP:
+ retval = syna_tcm_read_flash_custom_otp(tcm_dev,
+ &reflash_data, rd_data, rd_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to get custom otp data\n");
+ goto exit;
+ }
+ break;
+ case AREA_CUSTOM_LCM:
+ case AREA_CUSTOM_OEM:
+ case AREA_PPDT:
+ case AREA_FORCE_TUNING:
+ case AREA_OPEN_SHORT_TUNING:
+ retval = syna_tcm_read_flash_custom_data(tcm_dev,
+ &reflash_data, addr, length, rd_data,
+ rd_delay_us);
+ break;
+ default:
+ LOGE("Invalid data area\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ LOGI("%s read\n", AREA_ID_STR(area));
+
+ retval = 0;
+
+exit:
+ retval = syna_tcm_switch_fw_mode(tcm_dev,
+ MODE_APPLICATION_FIRMWARE,
+ FW_MODE_SWITCH_DELAY_MS);
+ if (retval < 0)
+ LOGE("Fail to go back to application firmware\n");
+
+ syna_tcm_buf_release(&reflash_data.out);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_write_flash()
+ *
+ * Implement the bootloader command to write specified data to flash memory.
+ *
+ * If the length of the data to write is not an integer multiple of words,
+ * the trailing byte will be discarded. If the length of the data to write
+ * is not an integer number of write blocks, it will be zero-padded to the
+ * next write block.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] address: the address in flash memory to write
+ * [ in] wr_data: data to write
+ * [ in] wr_len: length of data to write
+ * [ in] wr_delay_ms: a short delay after the command executed
+ * set 'DEFAULT_FLASH_WRITE_DELAY' to use default,
+ * which is 20 us;
+ * set '0' or 'RESP_IN_ATTN' to select ATTN-driven.
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_write_flash(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ unsigned int address, const unsigned char *wr_data,
+ unsigned int wr_len, unsigned int wr_delay_ms)
+{
+ 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;
+ unsigned int delay_ms;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ w_length = tcm_dev->max_wr_size - 8;
+
+ w_length = w_length - (w_length % reflash_data->write_block_size);
+
+ w_length = MIN(w_length, reflash_data->max_write_payload_size);
+
+ offset = 0;
+
+ remaining_length = wr_len;
+
+ syna_tcm_buf_lock(&reflash_data->out);
+
+ while (remaining_length) {
+ if (remaining_length > w_length)
+ xfer_length = w_length;
+ else
+ xfer_length = remaining_length;
+
+ retval = syna_tcm_buf_alloc(&reflash_data->out,
+ xfer_length + 2);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for buf.out\n");
+ syna_tcm_buf_unlock(&reflash_data->out);
+ return retval;
+ }
+
+ flash_address = address + offset;
+ block_address = flash_address / reflash_data->write_block_size;
+ reflash_data->out.buf[0] = (unsigned char)block_address;
+ reflash_data->out.buf[1] = (unsigned char)(block_address >> 8);
+
+ retval = syna_pal_mem_cpy(&reflash_data->out.buf[2],
+ reflash_data->out.buf_size - 2,
+ &wr_data[offset],
+ wr_len - offset,
+ xfer_length);
+ if (retval < 0) {
+ LOGE("Fail to copy write data ,size: %d\n",
+ xfer_length);
+ syna_tcm_buf_unlock(&reflash_data->out);
+ return retval;
+ }
+
+ if (wr_delay_ms == DEFAULT_FLASH_WRITE_DELAY)
+ delay_ms = FLASH_WRITE_DELAY_MS;
+ else
+ delay_ms = wr_delay_ms;
+
+ if (delay_ms == RESP_IN_ATTN) {
+ LOGD("xfer: %d, delay: ATTN-driven\n", xfer_length);
+ } else {
+ delay_ms = (delay_ms * xfer_length) / 1000;
+ LOGD("xfer: %d, delay: %d\n", xfer_length, delay_ms);
+ }
+
+ retval = syna_tcm_reflash_send_command(tcm_dev,
+ CMD_WRITE_FLASH,
+ reflash_data->out.buf,
+ xfer_length + 2,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to write data to flash addr 0x%x, size %d\n",
+ flash_address, xfer_length + 2);
+ syna_tcm_buf_unlock(&reflash_data->out);
+ return retval;
+ }
+
+ offset += xfer_length;
+ remaining_length -= xfer_length;
+ }
+
+ syna_tcm_buf_unlock(&reflash_data->out);
+
+ return 0;
+}
+
+/**
+ * syna_tcm_write_flash_block()
+ *
+ * Write data to the target block data area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] area: target block area to write
+ * [ in] wr_delay_us: delay time in micro-sec to write block data to flash.
+ * set 'DEFAULT_FLASH_WRITE_DELAY' to use default,
+ * which is 20 ms;
+ * set '0' or 'RESP_IN_ATTN' to select ATTN-driven.
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_write_flash_block(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct block_data *block, unsigned int wr_delay_us)
+{
+ int retval;
+ unsigned int size;
+ unsigned int flash_addr;
+ const unsigned char *data;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!block) {
+ LOGE("Invalid block data\n");
+ return _EINVAL;
+ }
+
+ data = block->data;
+ size = block->size;
+ flash_addr = block->flash_addr;
+
+ LOGD("Write data to %s - address: 0x%x, size: %d\n",
+ AREA_ID_STR(block->id), flash_addr, size);
+
+ if (size == 0) {
+ LOGI("No need to update, size = %d\n", size);
+ goto exit;
+ }
+
+ retval = syna_tcm_write_flash(tcm_dev, reflash_data,
+ flash_addr, data, size, wr_delay_us);
+ if (retval < 0) {
+ LOGE("Fail to write %s to flash (addr: 0x%x, size: %d)\n",
+ AREA_ID_STR(block->id), flash_addr, size);
+ return retval;
+ }
+
+exit:
+ LOGN("%s area written\n", AREA_ID_STR(block->id));
+
+ return 0;
+}
+
+/**
+ * syna_tcm_erase_flash()
+ *
+ * Implement the bootloader command, which is used to erase the specified
+ * blocks of flash memory.
+ *
+ * Until this command completes, the device may be unresponsive.
+ * Therefore, this helper is implemented as a blocked function, and the delay
+ * time is set to 200 ms in default.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] address: the address in flash memory to read
+ * [ in] size: size of data to write
+ * [ in] erase_delay_ms: the delay time to get the resp from mass erase
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_erase_flash(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ unsigned int address, unsigned int size,
+ unsigned int erase_delay_ms)
+{
+ int retval;
+ unsigned int page_start = 0;
+ unsigned int page_count = 0;
+ unsigned char out_buf[4] = {0};
+ int size_erase_cmd;
+
+ page_start = address / reflash_data->page_size;
+
+ page_count = syna_pal_ceil_div(size, reflash_data->page_size);
+
+ LOGD("Page start = %d (0x%04x), Page count = %d (0x%04x)\n",
+ page_start, page_start, page_count, page_count);
+
+ if ((page_start > 0xff) || (page_count > 0xff)) {
+ size_erase_cmd = 4;
+
+ out_buf[0] = (unsigned char)(page_start & 0xff);
+ out_buf[1] = (unsigned char)((page_start >> 8) & 0xff);
+ out_buf[2] = (unsigned char)(page_count & 0xff);
+ out_buf[3] = (unsigned char)((page_count >> 8) & 0xff);
+ } else {
+ size_erase_cmd = 2;
+
+ out_buf[0] = (unsigned char)(page_start & 0xff);
+ out_buf[1] = (unsigned char)(page_count & 0xff);
+ }
+
+ retval = syna_tcm_reflash_send_command(tcm_dev,
+ CMD_ERASE_FLASH,
+ out_buf,
+ size_erase_cmd,
+ erase_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to erase data at flash page 0x%x, count %d\n",
+ page_start, page_count);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_tcm_erase_flash_block()
+ *
+ * Mass erase the target block data area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] block: target block area to erase
+ * [ in] delay_ms: a short delay after the erase command executed
+ * set 'DEFAULT_FLASH_ERASE_DELAY' to use default,
+ * which is 500 ms;
+ * set '0' or 'RESP_IN_ATTN' to select ATTN-driven.
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_erase_flash_block(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct block_data *block, unsigned int delay_ms)
+{
+ int retval;
+ unsigned int size;
+ unsigned int flash_addr;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!block) {
+ LOGE("Invalid block data\n");
+ return _EINVAL;
+ }
+
+ flash_addr = block->flash_addr;
+
+ size = block->size;
+
+ if (delay_ms == DEFAULT_FLASH_ERASE_DELAY)
+ delay_ms = FLASH_ERASE_DELAY_MS;
+
+ LOGD("Erase %s block - address: 0x%x, size: %d\n",
+ AREA_ID_STR(block->id), flash_addr, size);
+
+ if (size == 0) {
+ LOGI("No need to erase, size = %d\n", size);
+ goto exit;
+ }
+
+ retval = syna_tcm_erase_flash(tcm_dev, reflash_data,
+ flash_addr, size, delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to erase %s data (addr: 0x%x, size: %d)\n",
+ AREA_ID_STR(block->id), flash_addr, size);
+ return retval;
+ }
+
+exit:
+ LOGN("%s area erased\n", AREA_ID_STR(block->id));
+
+ return 0;
+}
+
+/**
+ * syna_tcm_update_flash_block()
+ *
+ * Perform the reflash sequence to the target area
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ * [ in] block: target block area to update
+ * [ in] delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * for polling, set a value formatted with
+ * [erase | write];
+ * for ATTN-driven, set a '0' or 'RESP_IN_ATTN'
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_update_flash_block(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ struct block_data *block, unsigned int delay_ms)
+{
+ int retval;
+ unsigned int erase_delay_ms = (delay_ms >> 16) & 0xFFFF;
+ unsigned int wr_blk_delay_ms = delay_ms & 0xFFFF;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash data blob\n");
+ return _EINVAL;
+ }
+
+ if (!block) {
+ LOGE("Invalid block data\n");
+ return _EINVAL;
+ }
+
+ /* reflash is not needed for the partition */
+ retval = syna_tcm_check_flash_block(tcm_dev,
+ reflash_data,
+ block);
+ if (retval < 0) {
+ LOGE("Invalid %s area\n", AREA_ID_STR(block->id));
+ return retval;
+ }
+
+ if (retval == DO_NONE)
+ return 0;
+
+ LOGN("Prepare to erase %s area\n", AREA_ID_STR(block->id));
+
+ retval = syna_tcm_erase_flash_block(tcm_dev,
+ reflash_data,
+ block,
+ erase_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to erase %s area\n", AREA_ID_STR(block->id));
+ return retval;
+ }
+
+ LOGN("Prepare to update %s area\n", AREA_ID_STR(block->id));
+
+ retval = syna_tcm_write_flash_block(tcm_dev,
+ reflash_data,
+ block,
+ wr_blk_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to write %s area\n", AREA_ID_STR(block->id));
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_tcm_do_reflash_tddi()
+ *
+ * Implement the sequence specific for MODE_TDDI_BOOTLOADER.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: misc. data used for fw update
+ * [ in] type: the area to update
+ * [ in] delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * for polling, set a value formatted with
+ * [erase | write];
+ * for ATTN-driven, set a '0' or 'RESP_IN_ATTN'
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_do_reflash_tddi(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ enum update_area type, unsigned int delay_ms)
+{
+ int retval = 0;
+ int idx;
+ unsigned int erase_delay_ms = (delay_ms >> 16) & 0xFFFF;
+ unsigned int wr_blk_delay_ms = delay_ms & 0xFFFF;
+ struct image_info *image_info;
+ struct block_data *block;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash_data blob\n");
+ return _EINVAL;
+ }
+
+ image_info = &reflash_data->image_info;
+
+ if (tcm_dev->dev_mode != MODE_TDDI_BOOTLOADER) {
+ LOGE("Incorrect bootloader mode, 0x%02x, expected: 0x%02x\n",
+ tcm_dev->dev_mode, MODE_TDDI_BOOTLOADER);
+ return _EINVAL;
+ }
+
+ if (type == UPDATE_NONE)
+ goto exit;
+
+ /* Always mass erase all blocks, before writing the data */
+ for (idx = 0; idx < AREA_MAX; idx++) {
+
+ block = &image_info->data[idx];
+
+ if (!block->available)
+ continue;
+
+ retval = syna_tcm_check_flash_block(tcm_dev,
+ reflash_data,
+ block);
+ if (retval == DO_NONE)
+ continue;
+
+ LOGN("Prepare to erase %s area\n", AREA_ID_STR(block->id));
+
+ retval = syna_tcm_erase_flash_block(tcm_dev,
+ reflash_data,
+ block,
+ erase_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to erase %s area\n", AREA_ID_STR(block->id));
+ goto exit;
+ }
+ }
+
+ /* Write all the data to flash */
+ for (idx = 0; idx < AREA_MAX; idx++) {
+
+ block = &image_info->data[idx];
+
+ if (!block->available)
+ continue;
+
+ retval = syna_tcm_check_flash_block(tcm_dev,
+ reflash_data,
+ block);
+ if (retval == DO_NONE)
+ continue;
+
+ LOGN("Prepare to update %s area\n", AREA_ID_STR(block->id));
+
+ retval = syna_tcm_write_flash_block(tcm_dev,
+ reflash_data,
+ block,
+ wr_blk_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to update %s area\n",
+ AREA_ID_STR(block->id));
+ goto exit;
+ }
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_do_reflash_generic()
+ *
+ * Implement the generic sequence of fw update in MODE_BOOTLOADER.
+ *
+ * Typically, it is applied on most of discrete touch controllers
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: misc. data used for fw update
+ * [ in] type: the area to update
+ * [ in] wait_delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * for polling, set a value formatted with
+ * [erase | write];
+ * for ATTN-driven, set a '0' or 'RESP_IN_ATTN'
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_do_reflash_generic(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data,
+ enum update_area type, unsigned int wait_delay_ms)
+{
+ int retval = 0;
+ struct block_data *block;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!reflash_data) {
+ LOGE("Invalid reflash_data blob\n");
+ return _EINVAL;
+ }
+
+ if (tcm_dev->dev_mode != MODE_BOOTLOADER) {
+ LOGE("Incorrect bootloader mode, 0x%02x, expected: 0x%02x\n",
+ tcm_dev->dev_mode, MODE_BOOTLOADER);
+ return _EINVAL;
+ }
+
+ switch (type) {
+ case UPDATE_FIRMWARE_CONFIG:
+ block = &reflash_data->image_info.data[AREA_APP_CODE];
+
+ retval = syna_tcm_update_flash_block(tcm_dev,
+ reflash_data,
+ block,
+ wait_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to update application firmware\n");
+ goto exit;
+ }
+ case UPDATE_CONFIG_ONLY:
+ block = &reflash_data->image_info.data[AREA_APP_CONFIG];
+
+ retval = syna_tcm_update_flash_block(tcm_dev,
+ reflash_data,
+ block,
+ wait_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to update application config\n");
+ goto exit;
+ }
+ break;
+ case UPDATE_NONE:
+ default:
+ break;
+ }
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_do_fw_update()
+ *
+ * The entry function to perform fw update upon TouchBoot.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] image: binary data to write
+ * [ in] image_size: size of data array
+ * [ in] wait_delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * for polling, set a value formatted with
+ * [erase | write];
+ * for ATTN-driven, set a '0' or 'RESP_IN_ATTN'
+ * [ in] force_reflash: '1' to do reflash anyway
+ * '0' to compare ID info before doing reflash.
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_do_fw_update(struct tcm_dev *tcm_dev,
+ const unsigned char *image, unsigned int image_size,
+ unsigned int wait_delay_ms, bool force_reflash)
+{
+ int retval;
+ enum update_area type = UPDATE_NONE;
+ struct tcm_reflash_data_blob reflash_data;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!image) || (image_size == 0)) {
+ LOGE("Invalid image data\n");
+ return _EINVAL;
+ }
+
+ LOGN("Prepare to do reflash\n");
+
+ syna_tcm_buf_init(&reflash_data.out);
+
+ reflash_data.image = image;
+ reflash_data.image_size = image_size;
+ syna_pal_mem_set(&reflash_data.image_info, 0x00,
+ sizeof(struct image_info));
+
+ retval = syna_tcm_parse_fw_image(image, &reflash_data.image_info);
+ if (retval < 0) {
+ LOGE("Fail to parse firmware image\n");
+ return retval;
+ }
+
+ LOGN("Start of reflash\n");
+
+ ATOMIC_SET(tcm_dev->firmware_flashing, 1);
+
+ if (force_reflash) {
+ type = UPDATE_FIRMWARE_CONFIG;
+ goto reflash;
+ }
+
+ type = (enum update_area)syna_tcm_compare_image_id_info(tcm_dev,
+ &reflash_data);
+
+ if (type == UPDATE_NONE)
+ goto exit;
+
+reflash:
+ syna_tcm_buf_init(&reflash_data.out);
+
+ /* set up flash access, and enter the bootloader mode */
+ retval = syna_tcm_set_up_flash_access(tcm_dev, &reflash_data);
+ if (retval < 0) {
+ LOGE("Fail to set up flash access\n");
+ goto exit;
+ }
+
+ /* perform the fw update */
+ if (tcm_dev->dev_mode == MODE_BOOTLOADER) {
+ retval = syna_tcm_do_reflash_generic(tcm_dev,
+ &reflash_data,
+ type,
+ wait_delay_ms);
+ } else if (tcm_dev->dev_mode == MODE_TDDI_BOOTLOADER) {
+ retval = syna_tcm_do_reflash_tddi(tcm_dev,
+ &reflash_data,
+ type,
+ wait_delay_ms);
+ } else {
+ LOGE("Incorrect bootloader mode, 0x%02x\n",
+ tcm_dev->dev_mode);
+ goto reset;
+ }
+
+ if (retval < 0) {
+ LOGE("Fail to do firmware update\n");
+ goto reset;
+ }
+
+ LOGN("End of reflash\n");
+
+ retval = 0;
+reset:
+ retval = syna_tcm_reset(tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to do reset\n");
+ goto exit;
+ }
+
+exit:
+ ATOMIC_SET(tcm_dev->firmware_flashing, 0);
+
+ syna_tcm_buf_release(&reflash_data.out);
+
+ return retval;
+}
+
diff --git a/tcm/synaptics_touchcom_func_reflash.h b/tcm/synaptics_touchcom_func_reflash.h
new file mode 100644
index 0000000..8e90b80
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_reflash.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_reflash.h
+ *
+ * This file declares relevant functions and structures for TouchBoot.
+ */
+
+#ifndef _SYNAPTICS_TOUCHCOM_REFLASH_FUNCS_H_
+#define _SYNAPTICS_TOUCHCOM_REFLASH_FUNCS_H_
+
+#include "synaptics_touchcom_core_dev.h"
+#include "synaptics_touchcom_func_base_flash.h"
+
+/**
+ * @section: Blocks to be updated
+ */
+enum update_area {
+ UPDATE_NONE = 0,
+ UPDATE_FIRMWARE_CONFIG,
+ UPDATE_CONFIG_ONLY,
+ UPDATE_ALL_BLOCKS,
+};
+
+/**
+ * @section: Data Type in flash memory
+ */
+enum flash_data {
+ FLASH_LCM_DATA = 1,
+ FLASH_OEM_DATA,
+ FLASH_PPDT_DATA,
+ FLASH_FORCE_CALIB_DATA,
+ FLASH_OPEN_SHORT_TUNING_DATA,
+};
+
+/**
+ * @section: Specific data blob for reflash
+ *
+ * The structure contains various parameters being used in reflash
+ */
+struct tcm_reflash_data_blob {
+ /* binary data of an image file */
+ const unsigned char *image;
+ unsigned int image_size;
+ /* parsed data based on given image file */
+ struct image_info image_info;
+ /* standard information for flash access */
+ unsigned int page_size;
+ unsigned int write_block_size;
+ unsigned int max_write_payload_size;
+ /* temporary buffer during the reflash */
+ struct tcm_buffer out;
+};
+
+/**
+ * syna_tcm_compare_image_id_info()
+ *
+ * Compare the ID information between device and the image file,
+ * and determine the area to be updated.
+ * The function should be called after parsing the image file.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] reflash_data: data blob for reflash
+ *
+ * @return
+ * Comparison result
+ */
+int syna_tcm_compare_image_id_info(struct tcm_dev *tcm_dev,
+ struct tcm_reflash_data_blob *reflash_data);
+
+/**
+ * syna_tcm_read_flash_area()
+ *
+ * Entry function to read in the data of specific area in the flash memory.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] area: flash area to read
+ * [out] data: buffer storing the retrieved data
+ * [ in] rd_delay_us: delay time in micro-sec to read words data from flash.
+ * set '0' to use default time, which is 10 us;
+ * set 'FORCE_ATTN_DRIVEN' to adopt ATTN-driven.
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_read_flash_area(struct tcm_dev *tcm_dev,
+ enum flash_area area, struct tcm_buffer *rd_data,
+ unsigned int rd_delay_us);
+
+/**
+ * syna_tcm_do_fw_update()
+ *
+ * The entry function to perform fw update upon TouchBoot.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] image: image file given
+ * [ in] image_size: size of data array
+ * [ in] wait_delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * for polling, set a value formatted with
+ * [erase | write];
+ * for ATTN-driven, set a '0' or 'RESP_IN_ATTN'
+ * [ in] force_reflash: '1' to do reflash anyway
+ * '0' to compare ID info before doing reflash.
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_do_fw_update(struct tcm_dev *tcm_dev,
+ const unsigned char *image, unsigned int image_size,
+ unsigned int wait_delay_ms, bool force_reflash);
+
+#endif /* end of _SYNAPTICS_TOUCHCOM_REFLASH_FUNCS_H_ */
diff --git a/tcm/synaptics_touchcom_func_romboot.c b/tcm/synaptics_touchcom_func_romboot.c
new file mode 100644
index 0000000..e144fa3
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_romboot.c
@@ -0,0 +1,1586 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_romboot.c
+ *
+ * This file implements the ROM boot-loader related functions.
+ * The declarations are available in synaptics_touchcom_func_romboot.h.
+ */
+
+#include "synaptics_touchcom_func_base.h"
+#include "synaptics_touchcom_func_romboot.h"
+
+#define JEDEC_STATUS_CHECK_US_MIN 5000
+#define JEDEC_STATUS_CHECK_US_MAX 10000
+
+#define BINARY_FILE_MAGIC_VALUE 0xaa55
+
+#define ROMBOOT_FLASH_PAGE_SIZE 256
+
+/**
+ * @section: JEDEC flash command set
+ */
+enum flash_command {
+ JEDEC_PAGE_PROGRAM = 0x02,
+ JEDEC_READ_STATUS = 0x05,
+ JEDEC_WRITE_ENABLE = 0x06,
+ JEDEC_CHIP_ERASE = 0xc7,
+};
+
+
+/**
+ * syna_tcm_romboot_send_command()
+ *
+ * Helper to send a packet to ROM bootloader.
+ *
+ * Please be noted that the given packet must be formatted into the
+ * specific structure in order to communicate with flash.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] out: data sent out, if any
+ * [ in] out_size: size of data sent out
+ * [ in] in: buffer to store the data read in
+ * [ in] in_size: size of data read in
+ * [ in] delay_ms_resp: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_send_command(struct tcm_dev *tcm_dev,
+ unsigned char *out, unsigned int out_size, unsigned char *in,
+ unsigned int in_size, unsigned int delay_ms_resp)
+{
+ int retval;
+ unsigned char resp_code;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (out_size < sizeof(struct flash_param)) {
+ LOGE("Invalid size of out data, %d, min. size:%d\n",
+ out_size, (int)sizeof(struct flash_param));
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED,
+ out,
+ out_size,
+ &resp_code,
+ delay_ms_resp);
+ if (retval < 0) {
+ LOGE("Fail to send romboot flash command 0x%02x\n",
+ CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED);
+ goto exit;
+ }
+
+ LOGD("resp_code: 0x%x, resp length: %d\n",
+ resp_code, tcm_dev->resp_buf.data_length);
+
+ if ((in == NULL) || (in_size < tcm_dev->resp_buf.data_length))
+ goto exit;
+
+ /* copy resp data to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)in,
+ in_size,
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ tcm_dev->resp_buf.data_length);
+ if (retval < 0) {
+ LOGE("Fail to copy resp data to caller\n");
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+
+/**
+ * syna_tcm_romboot_multichip_send_command()
+ *
+ * Send a command code to the ROM bootloader inside the multi-chip device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] command: command to send
+ * [ in] out: additional data sent out, if any
+ * [ in] out_size: size of data sent out
+ * [ in] in: buffer to store the data read in
+ * [ in] in_size: size of data read in
+ * [ in] delay_ms: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_multichip_send_command(struct tcm_dev *tcm_dev,
+ unsigned char command, unsigned char *out,
+ unsigned int out_size, unsigned char *in,
+ unsigned int in_size, unsigned int delay_ms)
+{
+ int retval;
+ unsigned char *payld_buf = NULL;
+ unsigned int payld_size;
+ struct flash_param flash_param;
+ unsigned int offset = (int)sizeof(struct flash_param);
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ syna_pal_mem_set((void *)&flash_param, 0x00, sizeof(flash_param));
+
+ flash_param.read_size[0] = (unsigned char)(in_size & 0xff);
+ flash_param.read_size[1] = (unsigned char)(in_size >> 8) & 0xff;
+
+ flash_param.command = command;
+
+ payld_size = offset + out_size;
+ if (flash_param.command != 0x00)
+ payld_size += 2;
+
+ LOGD("Command: 0x%02x, packet size: %d, wr:%d, rd:%d\n",
+ command, payld_size, out_size, in_size);
+
+ payld_buf = syna_pal_mem_alloc(payld_size, sizeof(unsigned char));
+ if (!payld_buf) {
+ LOGE("Fail to allocate buffer to store flash command\n");
+ return _ENOMEM;
+ }
+
+ if (flash_param.command != 0x00) {
+ payld_buf[offset] = (unsigned char)out_size;
+ payld_buf[offset + 1] = (unsigned char)(out_size >> 8);
+
+ if (out_size > 0) {
+ retval = syna_pal_mem_cpy(&payld_buf[offset + 2],
+ payld_size - offset - 2,
+ out,
+ out_size,
+ out_size);
+ if (retval < 0) {
+ LOGE("Fail to copy payload to payld_buf\n");
+ goto exit;
+ }
+ }
+
+ LOGD("Packet: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ flash_param.byte0, flash_param.byte1, flash_param.byte2,
+ flash_param.read_size[0], flash_param.read_size[1],
+ flash_param.command, payld_buf[offset],
+ payld_buf[offset + 1]);
+ }
+
+ retval = syna_pal_mem_cpy(payld_buf, payld_size,
+ &flash_param, sizeof(flash_param), sizeof(flash_param));
+ if (retval < 0) {
+ LOGE("Fail to copy flash_param header to payld_buf\n");
+ goto exit;
+ }
+
+ retval = syna_tcm_romboot_send_command(tcm_dev,
+ payld_buf,
+ payld_size,
+ in,
+ in_size,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to write command 0x%x\n", flash_param.command);
+ goto exit;
+ }
+
+exit:
+ syna_pal_mem_free((void *)payld_buf);
+
+ return retval;
+}
+/**
+ * syna_tcm_romboot_multichip_get_resp()
+ *
+ * To get the response data from ROM bootloader inside the multi-chip device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] length: size to read
+ * [ in] resp: buffer to store the resp data
+ * [ in] resp_size: size of resp data
+ * [ in] delay_ms: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_multichip_get_resp(struct tcm_dev *tcm_dev,
+ unsigned int length, unsigned char *resp,
+ unsigned int resp_size, unsigned int delay_ms)
+{
+ int retval;
+ unsigned char *tmp_buf = NULL;
+ unsigned int xfer_len;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (resp && (resp_size < length)) {
+ LOGE("Invalid buffer size, len:%d, size:%d\n",
+ length, resp_size);
+ return _EINVAL;
+ }
+
+ xfer_len = length + 2;
+
+ tmp_buf = syna_pal_mem_alloc(xfer_len, sizeof(unsigned char));
+ if (!tmp_buf) {
+ LOGE("Fail to allocate tmp_buf\n");
+ return _ENOMEM;
+ }
+
+ retval = syna_tcm_romboot_multichip_send_command(tcm_dev,
+ CMD_NONE, NULL, 0,
+ tmp_buf, xfer_len, delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to get resp, size: %d\n", xfer_len);
+ goto exit;
+ }
+
+ if (resp) {
+ retval = syna_pal_mem_cpy(resp, resp_size,
+ &tmp_buf[1], xfer_len - 1, length);
+ if (retval < 0) {
+ LOGE("Fail to copy resp data\n");
+ goto exit;
+ }
+ }
+
+exit:
+ syna_pal_mem_free((void *)tmp_buf);
+
+ return retval;
+}
+/**
+ * syna_tcm_romboot_multichip_get_status()
+ *
+ * To poll the status until the completion
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] resp_status: response status returned
+ * [ in] resp_length: response length returned
+ * [ in] delay_ms: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_multichip_get_status(struct tcm_dev *tcm_dev,
+ unsigned char *resp_status, unsigned int *resp_length,
+ unsigned int delay_ms)
+{
+ int retval;
+ unsigned char resp[4] = { 0 };
+ int timeout = 0;
+ int MAX_TIMEOUT = 1000;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ syna_pal_sleep_ms(delay_ms);
+
+ do {
+ retval = syna_tcm_romboot_multichip_send_command(tcm_dev,
+ CMD_NONE, NULL, 0,
+ resp, 3, delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to poll the resp\n");
+ goto exit;
+ }
+
+ LOGD("status: %02x %02x %02x\n", resp[0], resp[1], resp[2]);
+
+ if (resp[0] == 0xff) {
+ syna_pal_sleep_ms(100);
+ timeout += 100;
+ continue;
+ } else if (resp[0] == 0x01) {
+ *resp_status = resp[0];
+ *resp_length = syna_pal_le2_to_uint(&resp[1]);
+ goto exit;
+ } else {
+ LOGE("Invalid resp, %02x %02x %02x\n",
+ resp[0], resp[1], resp[2]);
+ retval = _EIO;
+ goto exit;
+ }
+
+ } while (timeout < MAX_TIMEOUT);
+
+ if (timeout >= 500) {
+ LOGE("Timeout to get the status\n");
+ retval = _EIO;
+ }
+exit:
+ return retval;
+}
+/**
+ * syna_tcm_romboot_multichip_write_flash()
+ *
+ * Write the given binary data to the flash through the ROM bootloader
+ * inside the multi-chip device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] romboot_data: data blob for romboot
+ * [ in] address: the address in flash memory to write
+ * [ in] wr_data: binary data to write
+ * [ in] wr_len: length of data to write
+ * [ in] wr_delay_ms: a short delay after the command executed
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_multichip_write_flash(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_data_blob *romboot_data,
+ unsigned int address, const unsigned char *wr_data,
+ unsigned int wr_len, unsigned int wr_delay_ms)
+{
+ 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;
+ unsigned char resp_code = 0;
+ unsigned int resp_length = 0;
+ unsigned int delay_ms;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ w_length = tcm_dev->max_wr_size - 16;
+
+ w_length = w_length - (w_length % romboot_data->write_block_size);
+
+ w_length = MIN(w_length, romboot_data->max_write_payload_size);
+
+ offset = 0;
+
+ remaining_length = wr_len;
+
+ syna_tcm_buf_lock(&romboot_data->out);
+
+ while (remaining_length) {
+ if (remaining_length > w_length)
+ xfer_length = w_length;
+ else
+ xfer_length = remaining_length;
+
+ retval = syna_tcm_buf_alloc(&romboot_data->out,
+ xfer_length + 2);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for buf.out\n");
+ goto exit;
+ }
+
+ flash_address = address + offset;
+ block_address = flash_address / romboot_data->write_block_size;
+
+ romboot_data->out.buf[0] = (unsigned char)block_address & 0xff;
+ romboot_data->out.buf[1] = (unsigned char)(block_address >> 8);
+
+ retval = syna_pal_mem_cpy(&romboot_data->out.buf[2],
+ romboot_data->out.buf_size - 2,
+ &wr_data[offset],
+ wr_len - offset,
+ xfer_length);
+ if (retval < 0) {
+ LOGE("Fail to copy write data ,size: %d\n",
+ xfer_length);
+ goto exit;
+ }
+
+ if (wr_delay_ms == 0)
+ delay_ms = ROMBOOT_DELAY_MS;
+ else
+ delay_ms = wr_delay_ms;
+
+ LOGD("write xfer: %d (remaining: %d)\n",
+ xfer_length, remaining_length);
+
+ retval = syna_tcm_romboot_multichip_send_command(tcm_dev,
+ CMD_WRITE_FLASH,
+ romboot_data->out.buf,
+ xfer_length + 2,
+ NULL,
+ 0,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to write data to flash addr 0x%x, size %d\n",
+ flash_address, xfer_length + 2);
+ goto exit;
+ }
+
+ retval = syna_tcm_romboot_multichip_get_status(tcm_dev,
+ &resp_code, &resp_length, ROMBOOT_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to get the response of command 0x%x\n",
+ CMD_WRITE_FLASH);
+ goto exit;
+ }
+
+ LOGD("status:%02x, data_length:%d\n", resp_code, resp_length);
+
+ if (resp_code != STATUS_OK) {
+ LOGE("Invalid response of command %x\n",
+ CMD_WRITE_FLASH);
+ retval = _EIO;
+ goto exit;
+ }
+
+ retval = syna_tcm_romboot_multichip_get_resp(tcm_dev,
+ resp_length, NULL, 0, ROMBOOT_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to get the boot info packet\n");
+ goto exit;
+ }
+
+ offset += xfer_length;
+ remaining_length -= xfer_length;
+ }
+
+ retval = 0;
+
+exit:
+ syna_tcm_buf_unlock(&romboot_data->out);
+
+ return retval;
+}
+/**
+ * syna_tcm_romboot_multichip_erase_flash()
+ *
+ * Ask the ROM bootloader to erase the flash inside the multi-chip device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] romboot_data: data blob for remboot
+ * [ in] address: the address in flash memory to read
+ * [ in] size: size of data to write
+ * [ in] erase_delay_ms: the delay time to get the resp from mass erase
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_multichip_erase_flash(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_data_blob *romboot_data,
+ unsigned int address, unsigned int size,
+ unsigned int erase_delay_ms)
+{
+ int retval;
+ unsigned int page_start = 0;
+ unsigned int page_count = 0;
+ unsigned char out_buf[4] = { 0 };
+ unsigned char resp_code = 0;
+ unsigned int resp_length = 0;
+ int size_erase_cmd;
+
+ page_start = address / romboot_data->page_size;
+
+ page_count = syna_pal_ceil_div(size, romboot_data->page_size);
+
+ LOGD("Page start = %d (0x%04x), Page count = %d (0x%04x)\n",
+ page_start, page_start, page_count, page_count);
+
+ if ((page_start > 0xff) || (page_count > 0xff)) {
+ size_erase_cmd = 4;
+
+ out_buf[0] = (unsigned char)(page_start & 0xff);
+ out_buf[1] = (unsigned char)((page_start >> 8) & 0xff);
+ out_buf[2] = (unsigned char)(page_count & 0xff);
+ out_buf[3] = (unsigned char)((page_count >> 8) & 0xff);
+ } else {
+ size_erase_cmd = 2;
+
+ out_buf[0] = (unsigned char)(page_start & 0xff);
+ out_buf[1] = (unsigned char)(page_count & 0xff);
+ }
+
+ retval = syna_tcm_romboot_multichip_send_command(tcm_dev,
+ CMD_ERASE_FLASH,
+ out_buf,
+ size_erase_cmd,
+ NULL,
+ 0,
+ erase_delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to erase data at 0x%x (page:0x%x, count:%d)\n",
+ address, page_start, page_count);
+ return retval;
+ }
+
+ retval = syna_tcm_romboot_multichip_get_status(tcm_dev,
+ &resp_code, &resp_length, ROMBOOT_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to get the response of command 0x%x\n",
+ CMD_ERASE_FLASH);
+ return retval;
+ }
+
+ LOGD("status:%02x, data_length:%d\n", resp_code, resp_length);
+
+ if (resp_code != STATUS_OK) {
+ LOGE("Invalid response of command %x\n", CMD_WRITE_FLASH);
+ retval = _EIO;
+ return retval;
+ }
+
+ return retval;
+}
+
+/**
+ * syna_tcm_romboot_multichip_get_boot_info()
+ *
+ * To request a boot information packet from ROM bootloader inside
+ * the multi-chip device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] boot_info: the boot info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_multichip_get_boot_info(struct tcm_dev *tcm_dev,
+ struct tcm_boot_info *boot_info)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int resp_data_len = 0;
+ unsigned int copy_size;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = syna_tcm_romboot_multichip_send_command(tcm_dev,
+ CMD_GET_BOOT_INFO, NULL, 0, NULL, 0, ROMBOOT_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to run command 0x%x\n", CMD_GET_BOOT_INFO);
+ goto exit;
+ }
+
+ retval = syna_tcm_romboot_multichip_get_status(tcm_dev,
+ &resp_code, &resp_data_len, ROMBOOT_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to get the response of command 0x%x\n",
+ CMD_GET_BOOT_INFO);
+ return retval;
+ }
+
+ LOGD("status:%02x, data_length:%d\n", resp_code, resp_data_len);
+
+ if (resp_code != STATUS_OK) {
+ LOGE("Invalid response of command %x\n", CMD_GET_BOOT_INFO);
+ retval = _EIO;
+ return retval;
+ }
+
+ if (boot_info == NULL)
+ goto exit;
+
+ copy_size = MIN(sizeof(struct tcm_boot_info), resp_data_len);
+
+ retval = syna_tcm_romboot_multichip_get_resp(tcm_dev,
+ copy_size, (unsigned char *)boot_info,
+ sizeof(struct tcm_boot_info), ROMBOOT_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to get the boot info packet\n");
+ return retval;
+ }
+
+exit:
+ return retval;
+}
+/**
+ * syna_tcm_romboot_preparation()
+ *
+ * Perform the preparation before doing firmware update of multi-chip device
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] romboot_data: data blob for romboot access
+ * [ in] is_multichip: flag to indicate a multichip DUT
+ *
+ * @return
+ * Result of image file comparison
+ */
+static int syna_tcm_romboot_preparation(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_data_blob *romboot_data, bool is_multichip)
+{
+ int retval;
+ unsigned int temp;
+ struct tcm_boot_info *boot_info;
+ unsigned int wr_chunk;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!romboot_data) {
+ LOGE("Invalid romboot data blob\n");
+ return _EINVAL;
+ }
+
+ LOGI("Set up preparation, multi-chip: %s\n",
+ (is_multichip)?"yes":"no");
+
+ retval = syna_tcm_identify(tcm_dev, NULL);
+ if (retval < 0) {
+ LOGE("Fail to do identification\n");
+ return retval;
+ }
+
+ /* switch to bootloader mode */
+ if (IS_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGI("Prepare to enter bootloader mode\n");
+ if (is_multichip)
+ retval = syna_tcm_switch_fw_mode(tcm_dev,
+ MODE_MULTICHIP_TDDI_BOOTLOADER,
+ FW_MODE_SWITCH_DELAY_MS);
+ else
+ retval = syna_tcm_switch_fw_mode(tcm_dev,
+ MODE_TDDI_BOOTLOADER,
+ FW_MODE_SWITCH_DELAY_MS);
+
+ if (retval < 0) {
+ LOGE("Fail to enter bootloader mode\n");
+ return retval;
+ }
+ }
+ /* switch to rom boot mode */
+ if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGI("Prepare to enter rom boot mode\n");
+
+ retval = syna_tcm_switch_fw_mode(tcm_dev,
+ MODE_ROMBOOTLOADER,
+ FW_MODE_SWITCH_DELAY_MS);
+ if (retval < 0) {
+ LOGE("Fail to enter rom boot mode\n");
+ return retval;
+ }
+ }
+
+ if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGE("Device not in romboot mode\n");
+ return _EINVAL;
+ }
+
+ if (!is_multichip)
+ return 0;
+
+ boot_info = &tcm_dev->boot_info;
+
+ retval = syna_tcm_romboot_multichip_get_boot_info(tcm_dev,
+ boot_info);
+ if (retval < 0) {
+ LOGE("Fail to get boot info\n");
+ return retval;
+ }
+
+ wr_chunk = tcm_dev->max_wr_size;
+
+ temp = boot_info->write_block_size_words;
+ romboot_data->write_block_size = temp * 2;
+
+ LOGI("Write block size: %d (words size: %d)\n",
+ romboot_data->write_block_size, temp);
+
+ temp = syna_pal_le2_to_uint(boot_info->erase_page_size_words);
+ romboot_data->page_size = temp * 2;
+
+ LOGI("Erase page size: %d (words size: %d)\n",
+ romboot_data->page_size, temp);
+
+ temp = syna_pal_le2_to_uint(boot_info->max_write_payload_size);
+ romboot_data->max_write_payload_size = temp;
+
+ LOGI("Max write flash data size: %d\n",
+ romboot_data->max_write_payload_size);
+
+ if (romboot_data->write_block_size > (wr_chunk - 9)) {
+ LOGE("Write block size, %d, greater than chunk space, %d\n",
+ romboot_data->write_block_size, (wr_chunk - 9));
+ return _EINVAL;
+ }
+
+ if (romboot_data->write_block_size == 0) {
+ LOGE("Invalid write block size %d\n",
+ romboot_data->write_block_size);
+ return _EINVAL;
+ }
+
+ if (romboot_data->page_size == 0) {
+ LOGE("Invalid erase page size %d\n",
+ romboot_data->page_size);
+ return _EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_tcm_romboot_jedec_send_command()
+ *
+ * Send a jedec flash commend to the ROM bootloader
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] flash_command: flash command to send
+ * [ in] out: additional data sent out, if any
+ * [ in] out_size: size of data sent out
+ * [ in] in: buffer to store the data read in
+ * [ in] in_size: size of data read in
+ * [ in] delay_ms: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_jedec_send_command(struct tcm_dev *tcm_dev,
+ unsigned char flash_command, unsigned char *out,
+ unsigned int out_size, unsigned char *in, unsigned int in_size,
+ unsigned int delay_ms)
+{
+ int retval;
+ unsigned char *payld_buf = NULL;
+ unsigned int payld_size;
+ struct flash_param flash_param;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ syna_pal_mem_set((void *)&flash_param, 0x00, sizeof(flash_param));
+
+ flash_param.spi_param = 1;
+ flash_param.clk_div = 0x19;
+
+ flash_param.read_size[0] = (unsigned char)(in_size & 0xff);
+ flash_param.read_size[1] = (unsigned char)(in_size >> 8) & 0xff;
+
+ flash_param.command = flash_command;
+
+ payld_size = sizeof(struct flash_param) + out_size;
+
+ LOGD("Flash command: 0x%02x, total size: %d, wr: %d, rd: %d\n",
+ flash_command, payld_size, out_size, in_size);
+ LOGD("Packet: %02x %02x %02x %02x %02x %02x\n",
+ flash_param.byte0, flash_param.byte1, flash_param.byte2,
+ flash_param.read_size[0], flash_param.read_size[1],
+ flash_param.command);
+
+ payld_buf = syna_pal_mem_alloc(payld_size, sizeof(unsigned char));
+ if (!payld_buf) {
+ LOGE("Fail to allocate buffer to store flash command\n");
+ return _ENOMEM;
+ }
+
+ retval = syna_pal_mem_cpy(payld_buf, payld_size,
+ &flash_param, sizeof(flash_param), sizeof(flash_param));
+ if (retval < 0) {
+ LOGE("Fail to copy flash_param header to payld_buf\n");
+ goto exit;
+ }
+
+ if (out && (out_size > 0)) {
+ retval = syna_pal_mem_cpy(payld_buf + sizeof(flash_param),
+ payld_size - sizeof(flash_param),
+ out, out_size, out_size);
+ if (retval < 0) {
+ LOGE("Fail to copy data to payld_buf\n");
+ goto exit;
+ }
+ }
+
+ retval = syna_tcm_romboot_send_command(tcm_dev,
+ payld_buf,
+ payld_size,
+ in,
+ in_size,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to write flash command 0x%x\n", flash_command);
+ goto exit;
+ }
+
+exit:
+ syna_pal_mem_free((void *)payld_buf);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_romboot_jedec_get_status()
+ *
+ * Use jedec command to poll the flash status until the completion
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] delay_ms: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_jedec_get_status(struct tcm_dev *tcm_dev,
+ unsigned int delay_ms)
+{
+ int retval;
+ int idx;
+ unsigned char status;
+ int STATUS_CHECK_RETRY = 50;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ for (idx = 0; idx < STATUS_CHECK_RETRY; idx++) {
+ retval = syna_tcm_romboot_jedec_send_command(tcm_dev,
+ JEDEC_READ_STATUS,
+ NULL,
+ 0,
+ &status,
+ sizeof(status),
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Failed to write JEDEC_READ_STATUS\n");
+ return retval;
+ }
+
+ syna_pal_sleep_us(JEDEC_STATUS_CHECK_US_MIN,
+ JEDEC_STATUS_CHECK_US_MAX);
+ /* once completed, status = 0 */
+ if (!status)
+ break;
+ }
+
+ if (status)
+ retval = _EIO;
+ else
+ retval = status;
+
+ return retval;
+}
+
+/**
+ * syna_tcm_romboot_jedec_erase_flash()
+ *
+ * Ask the ROM bootloader to erase the flash by using the jedec command
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] delay_ms: delay time to get the response
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_jedec_erase_flash(struct tcm_dev *tcm_dev,
+ unsigned int delay_ms)
+{
+ int retval;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = syna_tcm_romboot_jedec_send_command(tcm_dev,
+ JEDEC_WRITE_ENABLE,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Failed to write JEDEC_WRITE_ENABLE\n");
+ return retval;
+ }
+
+ retval = syna_tcm_romboot_jedec_send_command(tcm_dev,
+ JEDEC_CHIP_ERASE,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Failed to write JEDEC_WRITE_ENABLE\n");
+ return retval;
+ }
+
+ retval = syna_tcm_romboot_jedec_get_status(tcm_dev, delay_ms);
+ if (retval < 0)
+ LOGE("Fail to get correct status, retval = %d\n", retval);
+
+ return retval;
+}
+
+ /**
+ * syna_tcm_romboot_jedec_write_flash()
+ *
+ * Write the given binary data to the flash through the ROM bootloader
+ * by using the jedec command
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] address: the address in flash memory to write
+ * [ in] data: binary data to write
+ * [ in] data_size: size of binary data
+ * [ in] delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_jedec_write_flash(struct tcm_dev *tcm_dev,
+ unsigned int address, const unsigned char *data,
+ unsigned int data_size, unsigned int delay_ms)
+{
+ int retval = 0;
+ unsigned int offset;
+ unsigned char buf[ROMBOOT_FLASH_PAGE_SIZE + 3];
+ unsigned int remaining_length;
+ unsigned int xfer_length;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!data) || (data_size == 0)) {
+ LOGE("Invalid image data, no data available\n");
+ return _EINVAL;
+ }
+
+ remaining_length = data_size;
+
+ offset = 0;
+
+ while (remaining_length) {
+ if (remaining_length > ROMBOOT_FLASH_PAGE_SIZE)
+ xfer_length = ROMBOOT_FLASH_PAGE_SIZE;
+ else
+ xfer_length = remaining_length;
+
+ syna_pal_mem_set(buf, 0x00, sizeof(buf));
+
+ retval = syna_tcm_romboot_jedec_send_command(tcm_dev,
+ JEDEC_WRITE_ENABLE,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Failed to write JEDEC_WRITE_ENABLE\n");
+ goto exit;
+ }
+
+ buf[0] = (unsigned char)((address + offset) >> 16);
+ buf[1] = (unsigned char)((address + offset) >> 8);
+ buf[2] = (unsigned char)(address + offset);
+
+ retval = syna_pal_mem_cpy(&buf[3],
+ sizeof(buf) - 3,
+ &data[offset],
+ data_size - offset,
+ xfer_length);
+ if (retval < 0) {
+ LOGE("Fail to copy data to write, size: %d\n",
+ xfer_length);
+ goto exit;
+ }
+
+ retval = syna_tcm_romboot_jedec_send_command(tcm_dev,
+ JEDEC_PAGE_PROGRAM,
+ buf,
+ sizeof(buf),
+ NULL,
+ 0,
+ delay_ms);
+ if (retval < 0) {
+ LOGE("Failed to write data to addr 0x%x (offset: %x)\n",
+ address + offset, offset);
+ LOGE("Remaining data %d\n",
+ remaining_length);
+ goto exit;
+ }
+
+ retval = syna_tcm_romboot_jedec_get_status(tcm_dev, delay_ms);
+ if (retval < 0) {
+ LOGE("Fail to get correct status, retval = %d\n",
+ retval);
+ goto exit;
+ }
+ offset += xfer_length;
+ remaining_length -= xfer_length;
+ }
+
+exit:
+ return retval;
+}
+
+
+/**
+ * syna_tcm_romboot_erase_flash()
+ *
+ * The entry function to perform mass erase
+ *
+ * @param
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] romboot_data: data blob for remboot
+ * [ in] blk: the block in flash memory to erase
+ * [ in] delay_ms: delay time to get the response
+ * [ in] is_multichip: use multi-chip command packet instead
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_erase_flash(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_data_blob *romboot_data,
+ struct block_data *blk, unsigned int delay_ms,
+ bool is_multichip)
+{
+ int retval;
+
+ if (!tcm_dev || !romboot_data || !blk)
+ return _EINVAL;
+
+ if (is_multichip)
+ retval = syna_tcm_romboot_multichip_erase_flash(tcm_dev,
+ romboot_data, blk->flash_addr, blk->size,
+ delay_ms);
+ else
+ retval = syna_tcm_romboot_jedec_erase_flash(tcm_dev,
+ delay_ms);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_romboot_write_flash()
+ *
+ * The entry function to write hex data to flash
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] romboot_data: data blob for remboot
+ * [ in] blk: the block in flash memory to update
+ * [ in] delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * [ in] is_multichip: use multi-chip command packet instead
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_romboot_write_flash(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_data_blob *romboot_data,
+ struct block_data *blk, unsigned int delay_ms,
+ bool is_multichip)
+{
+ int retval;
+
+ if (!tcm_dev || !romboot_data || !blk)
+ return _EINVAL;
+
+ if (is_multichip)
+ retval = syna_tcm_romboot_multichip_write_flash(tcm_dev,
+ romboot_data, blk->flash_addr, blk->data,
+ blk->size, delay_ms);
+ else
+ retval = syna_tcm_romboot_jedec_write_flash(tcm_dev,
+ blk->flash_addr, blk->data, blk->size,
+ delay_ms);
+
+ return retval;
+}
+
+
+/**
+ * syna_tcm_romboot_do_ihex_update()
+ *
+ * The entry function to perform ihex update upon ROM Boot.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] ihex: ihex data to write
+ * [ in] ihex_size: size of ihex data
+ * [ in] flash_size: size for temporary buffer allocation, which used
+ * to re-order the flash data.
+ * in general, (ihex_size + 4K) is preferred size
+ * [ in] len_per_line: length per line in the ihex file
+ * [ in] delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * [ in] is_multichip: flag to indicate a multi-chip product used
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_romboot_do_ihex_update(struct tcm_dev *tcm_dev,
+ const unsigned char *ihex, unsigned int ihex_size,
+ unsigned int flash_size, unsigned int len_per_line,
+ unsigned int delay_ms, bool is_multichip)
+{
+ int retval;
+ struct tcm_romboot_data_blob romboot_data;
+ struct ihex_info *ihex_info = NULL;
+ struct block_data *block;
+ unsigned int erase_delay_ms = (delay_ms >> 16) & 0xFFFF;
+ unsigned int wr_delay_ms = delay_ms & 0xFFFF;
+ unsigned short *header;
+ int idx;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!ihex) || (ihex_size == 0)) {
+ LOGE("Invalid ihex data\n");
+ return _EINVAL;
+ }
+
+ if (flash_size == 0)
+ flash_size = ihex_size + 4096;
+
+ romboot_data.bdata = ihex;
+ romboot_data.bdata_size = ihex_size;
+ syna_pal_mem_set(&romboot_data.ihex_info, 0x00,
+ sizeof(struct ihex_info));
+
+ ihex_info = &romboot_data.ihex_info;
+
+ ihex_info->bin = syna_pal_mem_alloc(flash_size,
+ sizeof(unsigned char));
+ if (!ihex_info->bin) {
+ LOGE("Fail to allocate buffer for ihex data\n");
+
+ return _ENOMEM;
+ }
+
+ ihex_info->bin_size = flash_size;
+
+ syna_tcm_buf_init(&romboot_data.out);
+
+ LOGN("Parse ihex file\n");
+
+ /* parse ihex file */
+ retval = syna_tcm_parse_fw_ihex((const char *)ihex,
+ ihex_size, ihex_info, len_per_line);
+ if (retval < 0) {
+ LOGE("Fail to parse firmware ihex file\n");
+ goto exit;
+ }
+
+ if (!is_multichip) {
+ header = (unsigned short *)ihex_info->block[0].data;
+ if (*header != BINARY_FILE_MAGIC_VALUE) {
+ LOGE("Incorrect image header 0x%04X\n", *header);
+ goto exit;
+ }
+ }
+
+ /* set up flash access, and enter the bootloader mode */
+ retval = syna_tcm_romboot_preparation(tcm_dev,
+ &romboot_data,
+ is_multichip);
+ if (retval < 0) {
+ LOGE("Fail to do preparation\n");
+ goto reset;
+ }
+
+ LOGN("Start of ihex update\n");
+
+ ATOMIC_SET(tcm_dev->firmware_flashing, 1);
+
+ for (idx = 0; idx < IHEX_MAX_BLOCKS; idx++) {
+
+ block = &ihex_info->block[idx];
+
+ if (!block->available)
+ continue;
+
+ if (block->size == 0)
+ continue;
+
+ /* for single-chip, mass erase will affect the
+ * entire flash memory, so it just takes once
+ */
+ if ((idx != 0) && (!is_multichip))
+ break;
+
+ retval = syna_tcm_romboot_erase_flash(tcm_dev,
+ &romboot_data,
+ block,
+ erase_delay_ms,
+ is_multichip);
+ if (retval < 0) {
+ LOGE("Fail to erase flash\n");
+ goto reset;
+ }
+ }
+
+ LOGN("Flash erased\n");
+ LOGN("Start to write all data to flash\n");
+
+ for (idx = 0; idx < IHEX_MAX_BLOCKS; idx++) {
+
+ block = &ihex_info->block[idx];
+
+ if (!block->available)
+ continue;
+
+ LOGD("block:%d, addr:0x%x, size:%d\n",
+ idx, block->flash_addr, block->size);
+
+ if (block->size == 0)
+ continue;
+
+ retval = syna_tcm_romboot_write_flash(tcm_dev,
+ &romboot_data,
+ block,
+ wr_delay_ms,
+ is_multichip);
+ if (retval < 0) {
+ LOGE("Fail to write data to addr 0x%x, size:%d\n",
+ block->flash_addr, block->size);
+ goto reset;
+ }
+
+ LOGI("Data written, size:%d\n", block->size);
+ }
+
+ LOGN("End of ihex update\n");
+
+ retval = 0;
+
+reset:
+ retval = syna_tcm_reset(tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to do reset\n");
+ goto exit;
+ }
+
+exit:
+ syna_pal_mem_free((void *)ihex_info->bin);
+
+ ATOMIC_SET(tcm_dev->firmware_flashing, 0);
+
+ syna_tcm_buf_release(&romboot_data.out);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_romboot_do_multichip_reflash()
+ *
+ * The entry function to perform fw update in multi-chip product.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] image: image file given
+ * [ in] image_size: size of data array
+ * [ in] wait_delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * for polling, set a value formatted with
+ * [erase | write];
+ * for ATTN-driven, set a '0' or 'RESP_IN_ATTN'
+ * [ in] force_reflash: '1' to do reflash anyway
+ * '0' to compare ID info before doing reflash.
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_romboot_do_multichip_reflash(struct tcm_dev *tcm_dev,
+ const unsigned char *image, unsigned int image_size,
+ unsigned int wait_delay_ms, bool force_reflash)
+{
+ int retval;
+ int idx;
+ struct tcm_romboot_data_blob romboot_data;
+ struct block_data *block;
+ struct app_config_header *header;
+ unsigned int image_fw_id;
+ unsigned int erase_delay_ms = (wait_delay_ms >> 16) & 0xFFFF;
+ unsigned int wr_delay_ms = wait_delay_ms & 0xFFFF;
+ bool has_tool_boot_cfg = false;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!image) || (image_size == 0)) {
+ LOGE("Invalid image data\n");
+ return _EINVAL;
+ }
+
+ LOGN("Prepare to do reflash\n");
+
+ syna_pal_mem_set(&romboot_data, 0x00,
+ sizeof(struct tcm_romboot_data_blob));
+
+ syna_tcm_buf_init(&romboot_data.out);
+
+ romboot_data.bdata = image;
+ romboot_data.bdata_size = image_size;
+
+ syna_tcm_buf_init(&romboot_data.out);
+
+ retval = syna_tcm_parse_fw_image(image, &romboot_data.image_info);
+ if (retval < 0) {
+ LOGE("Fail to parse firmware image\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+
+ block = &romboot_data.image_info.data[AREA_APP_CONFIG];
+ if (block->size < sizeof(struct app_config_header)) {
+ LOGE("Invalid application config in image file\n");
+ retval = _EINVAL;
+ goto exit;
+ }
+ header = (struct app_config_header *)block->data;
+
+ image_fw_id = syna_pal_le4_to_uint(header->build_id);
+
+ LOGN("Device firmware ID: %d, image build id: %d\n",
+ tcm_dev->packrat_number, image_fw_id);
+
+ if ((image_fw_id <= tcm_dev->packrat_number) && !force_reflash) {
+ LOGN("No need to do reflash\n");
+ retval = 0;
+ goto exit;
+ }
+
+ block = &romboot_data.image_info.data[AREA_TOOL_BOOT_CONFIG];
+ has_tool_boot_cfg = block->available;
+
+ /* set up flash access, and enter the bootloader mode */
+ retval = syna_tcm_romboot_preparation(tcm_dev, &romboot_data, true);
+ if (retval < 0) {
+ LOGE("Fail to do preparation\n");
+ goto reset;
+ }
+
+ if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) {
+ LOGE("Incorrect device mode 0x%02x, expected:0x%02x\n",
+ tcm_dev->dev_mode, MODE_ROMBOOTLOADER);
+ retval = _EINVAL;
+ goto reset;
+ }
+
+ LOGN("Start of reflash\n");
+
+ ATOMIC_SET(tcm_dev->firmware_flashing, 1);
+
+ /* Traverse through all blocks in the image file,
+ * then erase the corresponding block area
+ */
+ for (idx = 0; idx < AREA_MAX; idx++) {
+
+ block = &romboot_data.image_info.data[idx];
+
+ if (!block->available)
+ continue;
+
+ if (idx == AREA_ROMBOOT_APP_CODE)
+ continue;
+
+ if ((idx == AREA_BOOT_CONFIG) && has_tool_boot_cfg)
+ continue;
+
+ LOGD("Erase %s block - address: 0x%x (%d), size: %d\n",
+ AREA_ID_STR(block->id), block->flash_addr,
+ block->flash_addr, block->size);
+
+ if (block->size == 0)
+ continue;
+
+ if (erase_delay_ms == DEFAULT_FLASH_ERASE_DELAY)
+ erase_delay_ms = ROMBOOT_DELAY_MS;
+
+ retval = syna_tcm_romboot_erase_flash(tcm_dev,
+ &romboot_data,
+ block,
+ erase_delay_ms,
+ true);
+ if (retval < 0) {
+ LOGE("Fail to erase %s area\n", AREA_ID_STR(block->id));
+ goto reset;
+ }
+
+ LOGN("%s partition erased\n", AREA_ID_STR(block->id));
+ }
+
+ /* Traverse through all blocks in the image file,
+ * and then update the corresponding block area
+ */
+ for (idx = 0; idx < AREA_MAX; idx++) {
+
+ LOGD("Prepare to update %s partition\n", AREA_ID_STR(idx));
+
+ block = &romboot_data.image_info.data[idx];
+
+ if (!block->available)
+ continue;
+
+ if (idx == AREA_ROMBOOT_APP_CODE)
+ continue;
+
+ if ((idx == AREA_BOOT_CONFIG) && has_tool_boot_cfg)
+ continue;
+
+ LOGD("Write data to %s - address: 0x%x (%d), size: %d\n",
+ AREA_ID_STR(block->id), block->flash_addr,
+ block->flash_addr, block->size);
+
+ if (block->size == 0)
+ continue;
+
+ if (wr_delay_ms == DEFAULT_FLASH_WRITE_DELAY)
+ wr_delay_ms = ROMBOOT_DELAY_MS;
+
+ retval = syna_tcm_romboot_write_flash(tcm_dev,
+ &romboot_data,
+ block,
+ wr_delay_ms,
+ true);
+
+ if (retval < 0) {
+ LOGE("Fail to update %s partition, size: %d\n",
+ AREA_ID_STR(block->id), block->size);
+ goto reset;
+ }
+
+ LOGN("%s written\n", AREA_ID_STR(block->id));
+ }
+
+ LOGN("End of reflash\n");
+
+ retval = 0;
+reset:
+ retval = syna_tcm_reset(tcm_dev);
+ if (retval < 0) {
+ LOGE("Fail to do reset\n");
+ goto exit;
+ }
+
+exit:
+ ATOMIC_SET(tcm_dev->firmware_flashing, 0);
+
+ syna_tcm_buf_release(&romboot_data.out);
+
+ return retval;
+}
+
+
+/**
+ * syna_tcm_get_romboot_info()
+ *
+ * Implement the bootloader command code, which is used to request a
+ * RomBoot information packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] rom_boot_info: the romboot info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_romboot_info(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_info *rom_boot_info)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int copy_size = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_ROMBOOT_INFO,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to send command 0x%02x\n",
+ CMD_GET_ROMBOOT_INFO);
+ goto exit;
+ }
+
+ if (rom_boot_info == NULL)
+ goto exit;
+
+ copy_size = MIN(sizeof(struct tcm_romboot_info),
+ tcm_dev->resp_buf.data_length);
+
+ /* copy romboot_info to caller */
+ retval = syna_pal_mem_cpy((unsigned char *)rom_boot_info,
+ sizeof(struct tcm_romboot_info),
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ copy_size);
+ if (retval < 0) {
+ LOGE("Fail to copy romboot info to caller\n");
+ goto exit;
+ }
+
+exit:
+ return retval;
+}
+
+
+
diff --git a/tcm/synaptics_touchcom_func_romboot.h b/tcm/synaptics_touchcom_func_romboot.h
new file mode 100644
index 0000000..812caf9
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_romboot.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_touchcom_func_romboot.h
+ *
+ * This file declares relevant functions and structures for ROM boot-loader.
+ */
+
+#ifndef _SYNAPTICS_TOUCHCOM_ROMBOOT_FUNCS_H_
+#define _SYNAPTICS_TOUCHCOM_ROMBOOT_FUNCS_H_
+
+#include "synaptics_touchcom_core_dev.h"
+#include "synaptics_touchcom_func_base_flash.h"
+
+
+#define ROMBOOT_DELAY_MS (20)
+
+/**
+ * @section: Structure to assemble flash command
+ */
+struct flash_param {
+ union {
+ struct {
+ unsigned char byte0;
+ unsigned char byte1;
+ unsigned char byte2;
+ };
+ struct {
+ unsigned char spi_param;
+ unsigned char clk_div;
+ unsigned char mode;
+ };
+ };
+ unsigned char read_size[2];
+ unsigned char command;
+};
+
+/**
+ * @section: Specific data blob for romboot
+ *
+ * The structure contains various parameters being used in
+ * ROM boot control
+ */
+struct tcm_romboot_data_blob {
+ /* binary data to write */
+ const unsigned char *bdata;
+ unsigned int bdata_size;
+ /* parsed data based on given binary data */
+ struct ihex_info ihex_info;
+ struct image_info image_info;
+ /* standard information for flash access */
+ unsigned int page_size;
+ unsigned int write_block_size;
+ unsigned int max_write_payload_size;
+ /* temporary buffer during the reflash */
+ struct tcm_buffer out;
+};
+
+
+/**
+ * syna_tcm_romboot_do_ihex_update()
+ *
+ * The entry function to perform ihex update upon ROM Boot.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] ihex: ihex data to write
+ * [ in] ihex_size: size of ihex data
+ * [ in] flash_size: size for temporary buffer allocation, which used
+ * to re-order the flash data.
+ * in general, (ihex_size + 4K) is preferred size
+ * [ in] len_per_line: length per line in the ihex file
+ * [ in] delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * [ in] is_multichip: flag to indicate a multi-chip product used
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_romboot_do_ihex_update(struct tcm_dev *tcm_dev,
+ const unsigned char *ihex, unsigned int ihex_size,
+ unsigned int flash_size, unsigned int len_per_line,
+ unsigned int delay_ms, bool is_multichip);
+
+/**
+ * syna_tcm_romboot_do_multichip_reflash()
+ *
+ * The entry function to perform fw update with multi-chip product.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] image: image file given
+ * [ in] image_size: size of data array
+ * [ in] wait_delay_ms: a short delay time in millisecond to wait for
+ * the completion of flash access
+ * set [erase_delay_ms | write_delay_ms] for setup;
+ * set '0' to use default time;
+ * set 'FORCE_ATTN_DRIVEN' to adopt ATTN-driven.
+ * [ in] force_reflash: '1' to do reflash anyway
+ * '0' to compare ID info before doing reflash.
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_romboot_do_multichip_reflash(struct tcm_dev *tcm_dev,
+ const unsigned char *image, unsigned int image_size,
+ unsigned int wait_delay_ms, bool force_reflash);
+
+/**
+ * syna_tcm_get_romboot_info()
+ *
+ * Implement the bootloader command code, which is used to request a
+ * RomBoot information packet.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [out] rom_boot_info: the romboot info packet returned
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_romboot_info(struct tcm_dev *tcm_dev,
+ struct tcm_romboot_info *rom_boot_info);
+
+
+#endif /* end of _SYNAPTICS_TOUCHCOM_ROMBOOT_FUNCS_H_ */
diff --git a/tcm/synaptics_touchcom_func_touch.c b/tcm/synaptics_touchcom_func_touch.c
new file mode 100644
index 0000000..f390d0c
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_touch.c
@@ -0,0 +1,877 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synaptics TCM touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_tcm2_func_touch.c
+ *
+ * This file implements the touch report handling functions.
+ * The declarations are available in synaptics_touchcom_func_touch.h.
+ */
+
+#include "synaptics_touchcom_func_touch.h"
+
+/**
+ * syna_tcm_get_touch_data()
+ *
+ * Get data entity from the received report according to bit offset and bit
+ * length defined in the touch report configuration.
+ *
+ * @param
+ * [ in] report: touch report generated by TouchComm device
+ * [ in] report_size: size of given report
+ * [ in] offset: bit offset in the report
+ * [ in] bits: number of bits representing the data
+ * [out] data: data parsed
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_touch_data(const unsigned char *report,
+ unsigned int report_size, 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;
+
+ if (bits == 0 || bits > 32) {
+ LOGE("Invalid number of bits %d\n", bits);
+ return _EINVAL;
+ }
+
+ if (!report) {
+ LOGE("Invalid report data\n");
+ return _EINVAL;
+ }
+
+ if (offset + bits > report_size * 8) {
+ *data = 0;
+ return 0;
+ }
+
+ output_data = 0;
+ remaining_bits = bits;
+
+ bit_offset = offset % 8;
+ byte_offset = offset / 8;
+
+ while (remaining_bits) {
+ byte_data = 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;
+}
+
+/**
+ * syna_tcm_get_gesture_data()
+ *
+ * The contents of the gesture data entity depend on which gesture
+ * is detected. The default size of data is defined in 16-64 bits natively.
+ *
+ * @param
+ * [ in] report: touch report generated by TouchComm device
+ * [ in] report_size: size of given report
+ * [ in] offset: bit offset in the report
+ * [ in] bits: total bits representing the gesture data
+ * [out] gesture_data: gesture data parsed
+ * [ in] gesture_id: gesture id retrieved
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+static int syna_tcm_get_gesture_data(const unsigned char *report,
+ unsigned int report_size, unsigned int offset,
+ unsigned int bits, struct tcm_gesture_data_blob *gesture_data,
+ unsigned int gesture_id)
+{
+ int retval;
+ unsigned int idx;
+ unsigned int data;
+ unsigned int size;
+ unsigned int data_end;
+
+ if (!report) {
+ LOGE("Invalid report data\n");
+ return _EINVAL;
+ }
+
+ if (offset + bits > report_size * 8)
+ return 0;
+
+ data_end = offset + bits;
+
+ size = (sizeof(gesture_data->data) / sizeof(unsigned char));
+
+ idx = 0;
+ while ((offset < data_end) && (idx < size)) {
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, 16, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object index\n");
+ return retval;
+ }
+ gesture_data->data[idx++] = (unsigned char)(data & 0xff);
+ gesture_data->data[idx++] = (unsigned char)((data >> 8) & 0xff);
+ offset += 16;
+ }
+
+ switch (gesture_id) {
+ case GESTURE_ID_DOUBLE_TAP:
+ case GESTURE_ID_ACTIVE_TAP_AND_HOLD:
+ LOGD("Tap info: (%d, %d)\n",
+ syna_pal_le2_to_uint(gesture_data->tap_x),
+ syna_pal_le2_to_uint(gesture_data->tap_y));
+ break;
+ case GESTURE_ID_SWIPE:
+ LOGD("Swipe info: direction:%x (%d, %d)\n",
+ syna_pal_le2_to_uint(gesture_data->swipe_direction),
+ syna_pal_le2_to_uint(gesture_data->tap_x),
+ syna_pal_le2_to_uint(gesture_data->tap_y));
+ break;
+ default:
+ LOGW("Unknown gesture_id:%d\n", gesture_id);
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * syna_tcm_parse_touch_report()
+ *
+ * Traverse through touch report configuration and parse the contents of
+ * report packet to get the exactly touched data entity from touch reports.
+ *
+ * At the end of function, the touched data will be parsed and stored at the
+ * associated position in structure touch_data_blob.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] report: touch report generated by TouchComm device
+ * [ in] report_size: size of given report
+ * [out] touch_data: touch data generated
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_parse_touch_report(struct tcm_dev *tcm_dev,
+ unsigned char *report, unsigned int report_size,
+ struct tcm_touch_data_blob *touch_data)
+{
+ 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 config_size;
+ unsigned char *config_data;
+ struct tcm_objects_data_blob *object_data;
+ static unsigned int end_of_foreach;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (!report) {
+ LOGE("Invalid report data\n");
+ return _EINVAL;
+ }
+
+ if (report_size <= 0) {
+ LOGE("Invalid report data length\n");
+ return _EINVAL;
+ }
+
+ if (!touch_data) {
+ LOGE("Invalid touch data structure\n");
+ return _EINVAL;
+ }
+
+ if (tcm_dev->max_objects == 0) {
+ LOGE("Invalid max_objects supported\n");
+ return _EINVAL;
+ }
+
+ object_data = touch_data->object_data;
+
+ if (!object_data) {
+ LOGE("Invalid object_data\n");
+ return _EINVAL;
+ }
+
+ config_data = tcm_dev->touch_config.buf;
+ config_size = tcm_dev->touch_config.data_length;
+
+ if ((!config_data) || (config_size == 0)) {
+ LOGE("Invalid config_data\n");
+ return _EINVAL;
+ }
+
+ size = sizeof(touch_data->object_data);
+ syna_pal_mem_set(touch_data->object_data, 0x00, size);
+
+ num_of_active_objects = false;
+
+ idx = 0;
+ offset = 0;
+ objects = 0;
+ active_objects = 0;
+ active_only = false;
+ obj = 0;
+ next = 0;
+
+ while (idx < config_size) {
+ code = config_data[idx++];
+ switch (code) {
+ case TOUCH_REPORT_END:
+ goto exit;
+ case TOUCH_REPORT_FOREACH_ACTIVE_OBJECT:
+ obj = 0;
+ next = idx;
+ active_only = true;
+ break;
+ case TOUCH_REPORT_FOREACH_OBJECT:
+ obj = 0;
+ next = idx;
+ active_only = false;
+ break;
+ case TOUCH_REPORT_FOREACH_END:
+ end_of_foreach = idx;
+ if (active_only) {
+ if (num_of_active_objects) {
+ objects++;
+ obj++;
+ if (objects < active_objects)
+ idx = next;
+ } else if (offset < report_size * 8) {
+ obj++;
+ idx = next;
+ }
+ } else {
+ obj++;
+ if (obj < tcm_dev->max_objects)
+ idx = next;
+ }
+ break;
+ case TOUCH_REPORT_PAD_TO_NEXT_BYTE:
+ offset = syna_pal_ceil_div(offset, 8) * 8;
+ break;
+ case TOUCH_REPORT_TIMESTAMP:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get time-stamp\n");
+ return retval;
+ }
+ touch_data->timestamp = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_INDEX:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object index\n");
+ return retval;
+ }
+ obj = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_CLASSIFICATION:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object classification\n");
+ return retval;
+ }
+ object_data[obj].status = (unsigned char)data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_X_POSITION:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object x position\n");
+ return retval;
+ }
+ object_data[obj].x_pos = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_Y_POSITION:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object y position\n");
+ return retval;
+ }
+ object_data[obj].y_pos = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_Z:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object z\n");
+ return retval;
+ }
+ object_data[obj].z = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_X_WIDTH:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object x width\n");
+ return retval;
+ }
+ object_data[obj].x_width = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_Y_WIDTH:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object y width\n");
+ return retval;
+ }
+ object_data[obj].y_width = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_TX_POSITION_TIXELS:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object tx position\n");
+ return retval;
+ }
+ object_data[obj].tx_pos = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_OBJECT_N_RX_POSITION_TIXELS:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get object rx position\n");
+ return retval;
+ }
+ object_data[obj].rx_pos = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get number of active 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) {
+ if (end_of_foreach == 0) {
+ LOGE("Invalid end_foreach\n");
+ return 0;
+ }
+ idx = end_of_foreach;
+ }
+ break;
+ case TOUCH_REPORT_0D_BUTTONS_STATE:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get 0D buttons state\n");
+ return retval;
+ }
+ touch_data->buttons_state = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_GESTURE_ID:
+ if (tcm_dev->custom_gesture_parse_func) {
+ retval = tcm_dev->custom_gesture_parse_func(
+ TOUCH_REPORT_GESTURE_ID, config_data,
+ &idx, report, &offset, report_size,
+ tcm_dev->cbdata_gesture_parse);
+ } else {
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report,
+ report_size, offset, bits, &data);
+ touch_data->gesture_id = data;
+ offset += bits;
+ }
+ if (retval < 0) {
+ LOGE("Fail to get gesture id\n");
+ return retval;
+ }
+ break;
+ case TOUCH_REPORT_GESTURE_DATA:
+ if (tcm_dev->custom_gesture_parse_func) {
+ retval = tcm_dev->custom_gesture_parse_func(
+ TOUCH_REPORT_GESTURE_DATA, config_data,
+ &idx, report, &offset, report_size,
+ tcm_dev->cbdata_gesture_parse);
+ } else {
+ bits = config_data[idx++];
+ retval = syna_tcm_get_gesture_data(report,
+ report_size,
+ offset, bits,
+ &touch_data->gesture_data,
+ touch_data->gesture_id);
+ offset += bits;
+ }
+ if (retval < 0) {
+ LOGE("Fail to get gesture data\n");
+ return retval;
+ }
+ break;
+ case TOUCH_REPORT_FRAME_RATE:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get frame rate\n");
+ return retval;
+ }
+ touch_data->frame_rate = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_FORCE_MEASUREMENT:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get force measurement data\n");
+ return retval;
+ }
+ touch_data->force_data = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_FINGERPRINT_AREA_MEET:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get data for fingerprint area\n");
+ return retval;
+ }
+ touch_data->fingerprint_area_meet = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_POWER_IM:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get power IM\n");
+ return retval;
+ }
+ touch_data->power_im = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_CID_IM:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get CID IM\n");
+ return retval;
+ }
+ touch_data->cid_im = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_RAIL_IM:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get rail IM\n");
+ return retval;
+ }
+ touch_data->rail_im = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_CID_VARIANCE_IM:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get CID variance IM\n");
+ return retval;
+ }
+ touch_data->cid_variance_im = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_NSM_FREQUENCY_INDEX:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get NSM frequency\n");
+ return retval;
+ }
+ touch_data->nsm_frequency = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_NSM_STATE:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get NSM state\n");
+ return retval;
+ }
+ touch_data->nsm_state = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_CPU_CYCLES_USED_SINCE_LAST_FRAME:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get cpu cycles info\n");
+ return retval;
+ }
+ touch_data->num_of_cpu_cycles = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_FACE_DETECT:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to detect face\n");
+ return retval;
+ }
+ touch_data->fd_data = data;
+ offset += bits;
+ break;
+ case TOUCH_REPORT_SENSING_MODE:
+ bits = config_data[idx++];
+ retval = syna_tcm_get_touch_data(report, report_size,
+ offset, bits, &data);
+ if (retval < 0) {
+ LOGE("Fail to get sensing mode\n");
+ return retval;
+ }
+ touch_data->sensing_mode = data;
+ offset += bits;
+ break;
+ default:
+ /* use custom parsing method, if registered */
+ if (tcm_dev->custom_touch_data_parse_func) {
+ retval = tcm_dev->custom_touch_data_parse_func(
+ code, config_data, &idx, report,
+ &offset, report_size,
+ tcm_dev->cbdata_touch_data_parse);
+ if (retval >= 0)
+ continue;
+ }
+
+ LOGW("Unknown touch config code:0x%02x (length:%d)\n",
+ code, config_data[idx]);
+ bits = config_data[idx++];
+ offset += bits;
+ break;
+ }
+ }
+
+exit:
+ return 0;
+}
+
+/**
+ * syna_tcm_set_touch_report_config()
+ *
+ * Setup the format and content of touch report if needed.
+ *
+ * TouchComm allows to set how touch reports are formatted and what items get
+ * reported each time a touch report is generated.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] config: the customized report configuration
+ * [ in] config_size: size of given config
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_touch_report_config(struct tcm_dev *tcm_dev,
+ unsigned char *config, unsigned int config_size)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int size;
+ struct tcm_application_info *app_info;
+ unsigned char *data;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if ((!config) || (config_size == 0)) {
+ LOGE("Invalid given config data\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Not in application fw mode, mode: %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ app_info = &tcm_dev->app_info;
+ size = syna_pal_le2_to_uint(app_info->max_touch_report_config_size);
+
+ if (config_size > size) {
+ LOGE("Invalid config size: %d (max: %d)\n", config_size, size);
+ return _EINVAL;
+ }
+
+ data = syna_pal_mem_alloc(size, sizeof(unsigned char));
+ if (!data) {
+ LOGE("Fail to allocate memory for touch config setting\n");
+ return _ENOMEM;
+ }
+
+ retval = syna_pal_mem_cpy(data,
+ size,
+ config,
+ config_size,
+ config_size);
+ if (retval < 0) {
+ LOGE("Fail to copy custom touch config\n");
+ goto exit;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_SET_TOUCH_REPORT_CONFIG,
+ data,
+ size,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to write command CMD_SET_TOUCH_REPORT_CONFIG\n");
+ goto exit;
+ }
+
+ LOGI("Set touch config done\n");
+
+exit:
+ if (data)
+ syna_pal_mem_free((void *)data);
+
+ return retval;
+}
+
+/**
+ * syna_tcm_preserve_touch_report_config()
+ *
+ * Retrieve and preserve the current touch report configuration.
+ *
+ * The retrieved configuration is stored in touch_config buffer defined
+ * in structure syna_tcm_dev for later using of touch position parsing.
+ *
+ * The touch_config buffer will be allocated internally and its size will
+ * be updated accordingly.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_preserve_touch_report_config(struct tcm_dev *tcm_dev)
+{
+ int retval = 0;
+ unsigned char resp_code;
+ unsigned int size = 0;
+
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) {
+ LOGE("Not in application fw mode, mode: %d\n",
+ tcm_dev->dev_mode);
+ return _EINVAL;
+ }
+
+ retval = tcm_dev->write_message(tcm_dev,
+ CMD_GET_TOUCH_REPORT_CONFIG,
+ NULL,
+ 0,
+ &resp_code,
+ tcm_dev->msg_data.default_resp_reading);
+ if (retval < 0) {
+ LOGE("Fail to write command CMD_GET_TOUCH_REPORT_CONFIG\n");
+ goto exit;
+ }
+
+ syna_tcm_buf_lock(&tcm_dev->resp_buf);
+
+ size = tcm_dev->resp_buf.data_length;
+ retval = syna_tcm_buf_alloc(&tcm_dev->touch_config,
+ size);
+ if (retval < 0) {
+ LOGE("Fail to allocate memory for internal touch_config\n");
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ goto exit;
+ }
+
+ syna_tcm_buf_lock(&tcm_dev->touch_config);
+
+ retval = syna_pal_mem_cpy(tcm_dev->touch_config.buf,
+ tcm_dev->touch_config.buf_size,
+ tcm_dev->resp_buf.buf,
+ tcm_dev->resp_buf.buf_size,
+ size);
+ if (retval < 0) {
+ LOGE("Fail to clone touch config\n");
+ syna_tcm_buf_unlock(&tcm_dev->touch_config);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+ goto exit;
+ }
+
+ tcm_dev->touch_config.data_length = size;
+
+ syna_tcm_buf_unlock(&tcm_dev->touch_config);
+ syna_tcm_buf_unlock(&tcm_dev->resp_buf);
+
+exit:
+ return retval;
+}
+
+/**
+ * syna_tcm_set_custom_touch_data_parsing_callback()
+ *
+ * Set up callback function to handle custom touch data.
+ *
+ * Once finding the "new" custom entity in touch report, the core library will
+ * invoke the custom parsing method to handle this "new" code entity.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] p_cb: the callback function pointer
+ * [ in] p_cbdata: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_custom_touch_data_parsing_callback(struct tcm_dev *tcm_dev,
+ tcm_touch_data_parse_callback_t p_cb, void *p_cbdata)
+{
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_dev->custom_touch_data_parse_func = p_cb;
+ tcm_dev->cbdata_touch_data_parse = p_cbdata;
+
+ LOGI("enabled\n");
+
+ return 0;
+}
+
+/**
+ * syna_tcm_set_custom_gesture_parsing_callback()
+ *
+ * Set up callback function to handle the gesture data defined as the following
+ * code entities
+ * - TOUCH_REPORT_GESTURE_ID
+ * - TOUCH_REPORT_GESTURE_DATA
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] p_cb: the callback function pointer
+ * [ in] p_cbdata: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_custom_gesture_parsing_callback(struct tcm_dev *tcm_dev,
+ tcm_gesture_parse_callback_t p_cb, void *p_cbdata)
+{
+ if (!tcm_dev) {
+ LOGE("Invalid tcm device handle\n");
+ return _EINVAL;
+ }
+
+ tcm_dev->custom_gesture_parse_func = p_cb;
+ tcm_dev->cbdata_gesture_parse = p_cbdata;
+
+ LOGI("enabled\n");
+
+ return 0;
+}
+
diff --git a/tcm/synaptics_touchcom_func_touch.h b/tcm/synaptics_touchcom_func_touch.h
new file mode 100644
index 0000000..eb63c05
--- /dev/null
+++ b/tcm/synaptics_touchcom_func_touch.h
@@ -0,0 +1,235 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Synaptics TouchCom touchscreen driver
+ *
+ * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file synaptics_tcm2_func_touch.h
+ *
+ * This file declares related APIs and definitions for touch report handling.
+ */
+
+#ifndef _SYNAPTICS_TOUCHCOM_TOUCH_FUNCS_H_
+#define _SYNAPTICS_TOUCHCOM_TOUCH_FUNCS_H_
+
+#include "synaptics_touchcom_core_dev.h"
+
+/**
+ * @section: Types of Object Reported
+ *
+ * List the object classifications
+ */
+enum object_classification {
+ LIFT = 0,
+ FINGER = 1,
+ GLOVED_OBJECT = 2,
+ STYLUS = 3,
+ ERASER = 4,
+ SMALL_OBJECT = 5,
+ PALM = 6,
+ EDGE_TOUCHED = 8,
+ HOVER_OBJECT = 9,
+ NOP = -1,
+};
+
+/**
+ * @section: Types of Gesture ID
+ *
+ * List the gesture ID assigned
+ */
+enum gesture_classification {
+ GESTURE_ID_NONE = 0,
+ GESTURE_ID_DOUBLE_TAP = 1,
+ GESTURE_ID_SWIPE = 2,
+ GESTURE_ID_ACTIVE_TAP_AND_HOLD = 7,
+};
+
+/**
+ * @section: Codes for Touch Report Configuration
+ *
+ * Define the 8-bit codes for the touch report configuration
+ */
+enum touch_report_code {
+ /* control flow codes */
+ TOUCH_REPORT_END = 0x00,
+ TOUCH_REPORT_FOREACH_ACTIVE_OBJECT = 0x01,
+ TOUCH_REPORT_FOREACH_OBJECT = 0x02,
+ TOUCH_REPORT_FOREACH_END = 0x03,
+ TOUCH_REPORT_PAD_TO_NEXT_BYTE = 0x04,
+ /* entity codes */
+ TOUCH_REPORT_TIMESTAMP = 0x05,
+ TOUCH_REPORT_OBJECT_N_INDEX = 0x06,
+ TOUCH_REPORT_OBJECT_N_CLASSIFICATION = 0x07,
+ TOUCH_REPORT_OBJECT_N_X_POSITION = 0x08,
+ TOUCH_REPORT_OBJECT_N_Y_POSITION = 0x09,
+ TOUCH_REPORT_OBJECT_N_Z = 0x0a,
+ TOUCH_REPORT_OBJECT_N_X_WIDTH = 0x0b,
+ TOUCH_REPORT_OBJECT_N_Y_WIDTH = 0x0c,
+ TOUCH_REPORT_OBJECT_N_TX_POSITION_TIXELS = 0x0d,
+ TOUCH_REPORT_OBJECT_N_RX_POSITION_TIXELS = 0x0e,
+ TOUCH_REPORT_0D_BUTTONS_STATE = 0x0f,
+ TOUCH_REPORT_GESTURE_ID = 0x10,
+ TOUCH_REPORT_FRAME_RATE = 0x11,
+ TOUCH_REPORT_POWER_IM = 0x12,
+ TOUCH_REPORT_CID_IM = 0x13,
+ TOUCH_REPORT_RAIL_IM = 0x14,
+ TOUCH_REPORT_CID_VARIANCE_IM = 0x15,
+ TOUCH_REPORT_NSM_FREQUENCY_INDEX = 0x16,
+ TOUCH_REPORT_NSM_STATE = 0x17,
+ TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS = 0x18,
+ TOUCH_REPORT_CPU_CYCLES_USED_SINCE_LAST_FRAME = 0x19,
+ TOUCH_REPORT_FACE_DETECT = 0x1a,
+ TOUCH_REPORT_GESTURE_DATA = 0x1b,
+ TOUCH_REPORT_FORCE_MEASUREMENT = 0x1c,
+ TOUCH_REPORT_FINGERPRINT_AREA_MEET = 0x1d,
+ TOUCH_REPORT_SENSING_MODE = 0x1e,
+ TOUCH_REPORT_KNOB_DATA = 0x24,
+};
+
+/**
+ * syna_tcm_parse_touch_report()
+ *
+ * Traverse through touch report configuration and parse the contents of
+ * report packet to get the exactly touched data entity from touch reports.
+ *
+ * At the end of function, the touched data will be parsed and stored at the
+ * associated position in struct touch_data_blob.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] report: touch report generated by TouchComm device
+ * [ in] report_size: size of given report
+ * [out] touch_data: touch data generated
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_parse_touch_report(struct tcm_dev *tcm_dev,
+ unsigned char *report, unsigned int report_size,
+ struct tcm_touch_data_blob *touch_data);
+
+/**
+ * syna_tcm_set_touch_report_config()
+ *
+ * Setup the format and content of touch report if needed
+ *
+ * TouchComm allows to set how touch reports are formatted and what items get
+ * reported each time a touch report is generated.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] config: the customized report configuration
+ * [ in] config_size: size of given config
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_touch_report_config(struct tcm_dev *tcm_dev,
+ unsigned char *config, unsigned int config_size);
+
+/**
+ * syna_tcm_preserve_touch_report_config()
+ *
+ * Retrieve and preserve the current touch report configuration.
+ *
+ * The retrieved configuration is stored in touch_config buffer defined
+ * in struct syna_tcm_dev for later using of touch position parsing.
+ *
+ * The touch_config buffer will be allocated internally and its size will
+ * be updated accordingly.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_preserve_touch_report_config(struct tcm_dev *tcm_dev);
+
+
+/**
+ * syna_tcm_get_touch_data()
+ *
+ * Get data entity from the received report according to bit offset and bit
+ * length defined in the touch report configuration.
+ *
+ * @param
+ * [ in] report: touch report generated by TouchComm device
+ * [ in] report_size: size of given report
+ * [ in] offset: bit offset in the report
+ * [ in] bits: number of bits representing the data
+ * [out] data: data parsed
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_get_touch_data(const unsigned char *report,
+ unsigned int report_size, unsigned int offset,
+ unsigned int bits, unsigned int *data);
+
+/**
+ * syna_tcm_set_custom_touch_data_parsing_callback()
+ *
+ * Set up callback function to handle custom touch data.
+ *
+ * Once finding the "new" custom entity in touch report, the core library will
+ * invoke the custom parsing method to handle this "new" code entity.
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] p_cb: the callback function pointer
+ * [ in] p_cbdata: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_custom_touch_data_parsing_callback(struct tcm_dev *tcm_dev,
+ tcm_touch_data_parse_callback_t p_cb, void *p_cbdata);
+
+/**
+ * syna_tcm_set_custom_gesture_parsing_callback()
+ *
+ * Set up callback function to handle the gesture data defined as the following
+ * code entities
+ * - TOUCH_REPORT_GESTURE_ID
+ * - TOUCH_REPORT_GESTURE_DATA
+ *
+ * @param
+ * [ in] tcm_dev: the device handle
+ * [ in] p_cb: the callback function pointer
+ * [ in] p_cbdata: pointer to caller data passed to callback function
+ *
+ * @return
+ * on success, 0 or positive value; otherwise, negative value on error.
+ */
+int syna_tcm_set_custom_gesture_parsing_callback(struct tcm_dev *tcm_dev,
+ tcm_gesture_parse_callback_t p_cb, void *p_cbdata);
+
+
+#endif /* end of _SYNAPTICS_TOUCHCOM_TOUCH_FUNCS_H_ */