Update kernel to ab/13436148

Change-Id: I24495aba1924fd3b6574d2d95d42d05239886b5f
diff --git a/ft3658/BUILD.bazel b/ft3658/BUILD.bazel
new file mode 100644
index 0000000..3829d85
--- /dev/null
+++ b/ft3658/BUILD.bazel
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+load("//build/kernel/kleaf:kernel.bzl", "kernel_module")
+
+kernel_module(
+    name = "ft3658",
+    srcs = glob([
+        "**/*.c",
+        "**/*.h",
+    ]) + [
+        "Kbuild",
+        "include/firmware/fw_sample.i",
+        "//private/google-modules/display/samsung:headers",
+        "//private/google-modules/display/samsung/include:headers",
+        "//private/google-modules/soc/gs:gs_soc_headers",
+        "//private/google-modules/touch/common:headers",
+    ],
+    outs = [
+        "focal_touch.ko",
+    ],
+    kernel_build = "//private/devices/google/common:kernel",
+    visibility = [
+        "//private/devices/google:__subpackages__",
+        "//private/google-modules/soc/gs:__pkg__",
+    ],
+    deps = [
+        "//private/google-modules/soc/gs:gs_soc_module",
+        "//private/google-modules/touch/common:touch.common",
+    ],
+)
diff --git a/ft3658/Kbuild b/ft3658/Kbuild
new file mode 100644
index 0000000..ef6847c
--- /dev/null
+++ b/ft3658/Kbuild
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+ccflags-y += -I$(srctree)/../private/google-modules/display
+ccflags-y += -I$(srctree)/../private/google-modules/display/samsung/include/uapi
+ccflags-y += -I$(srctree)/../private/google-modules/touch/common
+ccflags-y += -I$(srctree)/../private/google-modules/touch/common/include
+
+obj-$(CONFIG_TOUCHSCREEN_FTS) = focal_touch.o
+focal_touch-objs += focaltech_core.o focaltech_ex_fun.o focaltech_ex_mode.o \
+	focaltech_gesture.o focaltech_esdcheck.o focaltech_point_report_check.o \
+	focaltech_test/focaltech_test.o focaltech_test/focaltech_test_ini.o \
+	focaltech_test/supported_ic/focaltech_test_ft3658u.o \
+	focaltech_flash.o \
+	focaltech_flash/focaltech_upgrade_ft3658u.o \
+	focaltech_spi.o
diff --git a/ft3658/Kconfig b/ft3658/Kconfig
new file mode 100644
index 0000000..27ea952
--- /dev/null
+++ b/ft3658/Kconfig
@@ -0,0 +1,16 @@
+#
+# Focaltech Touchscreen driver configuration
+#
+
+config TOUCHSCREEN_FTS
+    bool "Focaltech Touchscreen"
+    default n
+    help
+      Say Y here if you have Focaltech touch panel.
+      If unsure, say N.
+      
+config TOUCHSCREEN_FTS_DIRECTORY
+    string "Focaltech ts directory name"
+    default "focaltech_touch"
+    depends on TOUCHSCREEN_FTS
+    
\ No newline at end of file
diff --git a/ft3658/Makefile b/ft3658/Makefile
new file mode 100644
index 0000000..f35a5db
--- /dev/null
+++ b/ft3658/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Makefile for the focaltech touchscreen drivers.
+
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+KBUILD_OPTIONS	+= CONFIG_TOUCHSCREEN_FTS=m
+EXTRA_CFLAGS	+= -DDYNAMIC_DEBUG_MODULE
+EXTRA_CFLAGS	+= -DCONFIG_TOUCHSCREEN_PANEL_BRIDGE
+EXTRA_CFLAGS	+= -DCONFIG_TOUCHSCREEN_TBN
+EXTRA_CFLAGS	+= -DCONFIG_TOUCHSCREEN_HEATMAP
+EXTRA_CFLAGS	+= -DCONFIG_TOUCHSCREEN_OFFLOAD
+EXTRA_SYMBOLS	+= $(OUT_DIR)/../private/google-modules/touch/common/Module.symvers
+
+include $(KERNEL_SRC)/../private/google-modules/soc/gs/Makefile.include
+
+modules modules_install clean:
+	$(MAKE) -C $(KERNEL_SRC) M=$(M) \
+	$(KBUILD_OPTIONS) \
+	EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
+	KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)" \
+	$(@)
+
diff --git a/ft3658/focaltech_common.h b/ft3658/focaltech_common.h
new file mode 100644
index 0000000..712d437
--- /dev/null
+++ b/ft3658/focaltech_common.h
@@ -0,0 +1,284 @@
+/*
+ *
+ * FocalTech fts TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/*****************************************************************************
+*
+* File Name: focaltech_common.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-16
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+#ifndef __LINUX_FOCALTECH_COMMON_H__
+#define __LINUX_FOCALTECH_COMMON_H__
+
+#include "focaltech_config.h"
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+#include <drm/drm_bridge.h>
+#include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <samsung/exynos_drm_connector.h>
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+#include <touch_bus_negotiator.h>
+#endif
+
+/*****************************************************************************
+* Macro definitions using #define
+*****************************************************************************/
+#define FTS_DRIVER_VERSION                  "Focaltech V3.3 20201229"
+
+#define BYTE_OFF_0(x)           (u8)((x) & 0xFF)
+#define BYTE_OFF_8(x)           (u8)(((x) >> 8) & 0xFF)
+#define BYTE_OFF_16(x)          (u8)(((x) >> 16) & 0xFF)
+#define BYTE_OFF_24(x)          (u8)(((x) >> 24) & 0xFF)
+#define FLAGBIT(x)              (0x00000001 << (x))
+#define FLAGBITS(x, y)          ((0xFFFFFFFF >> (32 - (y) - 1)) & (0xFFFFFFFF << (x)))
+
+#define FLAG_ICSERIALS_LEN      8
+#define FLAG_HID_BIT            10
+#define FLAG_IDC_BIT            11
+
+#define IC_SERIALS              (FTS_CHIP_TYPE & FLAGBITS(0, FLAG_ICSERIALS_LEN-1))
+#define IC_TO_SERIALS(x)        ((x) & FLAGBITS(0, FLAG_ICSERIALS_LEN-1))
+#define FTS_CHIP_IDC            ((FTS_CHIP_TYPE & FLAGBIT(FLAG_IDC_BIT)) == FLAGBIT(FLAG_IDC_BIT))
+#define FTS_HID_SUPPORTTED      ((FTS_CHIP_TYPE & FLAGBIT(FLAG_HID_BIT)) == FLAGBIT(FLAG_HID_BIT))
+
+#define FTS_MAX_CHIP_IDS        8
+#define FTS_RESET_INTERVAL      200
+
+#define FTS_CHIP_TYPE_MAPPING   {{0x88, 0x56, 0x52, 0x00, 0x00, 0x00, 0x00, 0x56, 0xB2}}
+
+#define FTS_STTW_E3_BUF_LEN                 13
+#define FTS_LPTW_E2_BUF_LEN                 13
+#define FTS_LPTW_E1_BUF_LEN                 12
+#define FTS_LPTW_BUF_LEN                    (max(FTS_LPTW_E1_BUF_LEN, FTS_LPTW_E2_BUF_LEN))
+
+#define FILE_NAME_LENGTH                    128
+#define FTS_MESSAGE_LENGTH                  128
+#define ENABLE                              1
+#define DISABLE                             0
+#define VALID                               1
+#define INVALID                             0
+#define MAX_RETRY_CNT                       3
+#define FTS_CMD_START1                      0x55
+#define FTS_CMD_START2                      0xAA
+#define FTS_CMD_START_DELAY                 12
+#define FTS_CMD_READ_ID                     0x90
+#define FTS_CMD_READ_ID_LEN                 4
+#define FTS_CMD_READ_ID_LEN_INCELL          1
+#define FTS_CMD_READ_FW_CONF                0xA8
+#define FTS_CMD_READ_TOUCH_DATA             0x01
+/*register address*/
+#define FTS_REG_INT_CNT                     0x8F
+#define FTS_REG_FLOW_WORK_CNT               0x91
+#define FTS_REG_WORKMODE                    0x00
+#define FTS_REG_WORKMODE_FACTORY_VALUE      0x40
+#define FTS_REG_WORKMODE_WORK_VALUE         0x00
+#define FTS_REG_ESDCHECK_DISABLE            0x8D
+#define FTS_REG_CHIP_ID                     0xA3
+#define FTS_REG_CHIP_ID2                    0x9F
+#define FTS_REG_POWER_MODE                  0xA5
+#define FTS_REG_POWER_MODE_SLEEP            0x03
+#define FTS_REG_FW_MAJOR_VER                0xA6
+#define FTS_REG_FW_MINOR_VER                0xAD
+#define FTS_REG_VENDOR_ID                   0xA8
+#define FTS_REG_LCD_BUSY_NUM                0xAB
+#define FTS_REG_FACE_DEC_MODE_EN            0xB0
+#define FTS_REG_FACTORY_MODE_DETACH_FLAG    0xB4
+#define FTS_REG_FACE_DEC_MODE_STATUS        0x01
+#define FTS_REG_IDE_PARA_VER_ID             0xB5
+#define FTS_REG_IDE_PARA_STATUS             0xB6
+#define FTS_REG_GLOVE_MODE_EN               0xC0
+#define FTS_REG_COVER_MODE_EN               0xC1
+#define FTS_REG_PALM_EN                     0xC5
+#define FTS_REG_CHARGER_MODE_EN             0x8B
+#define FTS_REG_EDGE_MODE_EN                0x8C
+#define FTS_REG_GESTURE_EN                  0xD0
+#define FTS_REG_GESTURE_OUTPUT_ADDRESS      0xD3
+#define FTS_REG_MODULE_ID                   0xE3
+#define FTS_REG_LIC_VER                     0xE4
+#define FTS_REG_ESD_SATURATE                0xED
+#define FTS_REG_GESTURE_SWITCH              0xCF
+#define FTS_REG_MONITOR_CTRL                0x86
+#define FTS_REG_SENSE_ONOFF                 0xEA
+#define FTS_REG_IRQ_ONOFF                   0xEB
+#define FTS_REG_CLR_RESET                   0xEC
+
+#define FTS_REG_HEATMAP_1E                  0x1E
+#define FTS_REG_HEATMAP_ED                  0xED
+#define FTS_REG_HEATMAP_9E                  0x9E
+
+#define FTS_LPTW_REG_SET_E1                 0xE1
+#define FTS_LPTW_REG_SET_E2                 0xE2
+#define FTS_STTW_REG_SET_E3                 0xE3
+#define FTS_GESTURE_MAJOR_MINOR             0xE5
+#define FTS_REG_CONTINUOUS_EN               0xE7
+
+#define FTS_REG_CUSTOMER_STATUS             0xB2    // follow FTS_CUSTOMER_STATUS.
+                                                    // bit 0~1 : HOPPING
+                                                    // bit 2   : PALM
+                                                    // bit 3   : WATER
+                                                    // bit 4   : GRIP
+                                                    // bit 5   : GLOVE
+                                                    // bit 6   : STTW
+                                                    // bit 7   : LPWG
+#define FTS_CAP_DATA_LEN                    91
+#define FTS_SELF_DATA_LEN                   68
+#define FTS_FULL_TOUCH_RAW_SIZE(tx_num, rx_num) \
+    (FTS_CAP_DATA_LEN + ((tx_num) * (rx_num) + FTS_SELF_DATA_LEN * 2) * sizeof(u16))
+#define FTS_PRESSURE_SCALE                  85      // 255 / 3
+#define FTS_CUSTOMER_STATUS_LEN             4
+#define FTS_CUSTOMER_STATUS1_MASK           0x0F
+#define FTS_ORIENTATION_SCALE               45
+#define FTS_GESTURE_ID_STTW                 0x25
+#define FTS_GESTURE_ID_LPTW_DOWN            0x26
+#define FTS_GESTURE_ID_LPTW_UP              0x27
+
+#define FTS_SYSFS_ECHO_ON(buf)      (buf[0] == '1')
+#define FTS_SYSFS_ECHO_OFF(buf)     (buf[0] == '0')
+
+#define kfree_safe(pbuf) do {\
+    if (pbuf) {\
+        kfree(pbuf);\
+        pbuf = NULL;\
+    }\
+} while(0)
+
+/*****************************************************************************
+*  Alternative mode (When something goes wrong, the modules may be able to solve the problem.)
+*****************************************************************************/
+/*
+ * point report check
+ * default: disable
+ */
+#define FTS_POINT_REPORT_CHECK_EN               0
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+struct ft_chip_t {
+    u16 type;
+    u8 chip_idh;
+    u8 chip_idl;
+    u8 rom_idh;
+    u8 rom_idl;
+    u8 pb_idh;
+    u8 pb_idl;
+    u8 bl_idh;
+    u8 bl_idl;
+};
+
+struct ft_chip_id_t {
+    u16 type;
+    u16 chip_ids[FTS_MAX_CHIP_IDS];
+};
+
+struct ts_ic_info {
+    bool is_incell;
+    bool hid_supported;
+    struct ft_chip_t ids;
+    struct ft_chip_id_t cid;
+};
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+enum {
+    FTS_TS_BUS_REF_SCREEN_ON    = 0x01,
+    FTS_TS_BUS_REF_IRQ          = 0x02,
+    FTS_TS_BUS_REF_FW_UPDATE    = 0x04,
+    FTS_TS_BUS_REF_SYSFS        = 0x0008,
+    FTS_TS_BUS_REF_FORCE_ACTIVE = 0x0010,
+    FTS_TS_BUS_REF_BUGREPORT    = 0x0020,
+};
+
+enum TOUCH_POWER_MODE {
+    FTS_TS_STATE_POWER_ON = 0,
+    FTS_TS_STATE_SUSPEND,
+};
+#endif
+
+/* Firmware Grip suppression mode.
+ * 0 - Disable fw grip suppression.
+ * 1 - Enable fw grip suppression.
+ * 2 - Force disable fw grip suppression.
+ * 3 - Force enable fw grip suppression.
+ */
+enum FW_GRIP_MODE {
+    FW_GRIP_DISABLE,
+    FW_GRIP_ENABLE,
+    FW_GRIP_FORCE_DISABLE,
+    FW_GRIP_FORCE_ENABLE,
+};
+
+/* Firmware Heatmap mode.
+ * 0 - Disable fw heatmap.
+ * 1 - Enable fw compressed heatmap.
+ * 2 - Enable fw uncompressed heatmap.
+ */
+enum FW_HEATMAP_MODE {
+    FW_HEATMAP_MODE_DISABLE,
+    FW_HEATMAP_MODE_COMPRESSED,
+    FW_HEATMAP_MODE_UNCOMPRESSED,
+};
+
+/* Firmware Palm rejection mode.
+ * 0 - Disable fw palm rejection.
+ * 1 - Enable fw palm rejection.
+ * 2 - Force disable fw palm rejection.
+ * 3 - Force enable fw palm rejection.
+ */
+enum FW_PALM_MODE {
+    FW_PALM_DISABLE,
+    FW_PALM_ENABLE,
+    FW_PALM_FORCE_DISABLE,
+    FW_PALM_FORCE_ENABLE,
+};
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+enum TBN_OWRER {
+    TBN_AP,
+    TBN_AOC,
+};
+#endif
+
+/*****************************************************************************
+* DEBUG function define here
+*****************************************************************************/
+#undef pr_fmt
+#define pr_fmt(fmt) "gtd: FTS_TS: " fmt
+#if FTS_DEBUG_EN
+#define FTS_DEBUG(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
+#define FTS_FUNC_ENTER() pr_debug("%s: Enter\n", __func__)
+#define FTS_FUNC_EXIT() pr_debug("%s: Exit(%d)\n", __func__, __LINE__)
+#else /* #if FTS_DEBUG_EN*/
+#define FTS_DEBUG(fmt, ...)
+#define FTS_FUNC_ENTER()
+#define FTS_FUNC_EXIT()
+#endif
+
+#define FTS_INFO(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
+#define FTS_ERROR(fmt, ...) pr_err(fmt, ##__VA_ARGS__)
+#define PR_LOGD(fmt, ...) pr_debug(fmt, ##__VA_ARGS__)
+
+#endif /* __LINUX_FOCALTECH_COMMON_H__ */
diff --git a/ft3658/focaltech_config.h b/ft3658/focaltech_config.h
new file mode 100644
index 0000000..ca6e272
--- /dev/null
+++ b/ft3658/focaltech_config.h
@@ -0,0 +1,338 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/************************************************************************
+*
+* File Name: focaltech_config.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract: global configurations
+*
+* Version: v1.0
+*
+************************************************************************/
+#ifndef _LINUX_FOCLATECH_CONFIG_H_
+#define _LINUX_FOCLATECH_CONFIG_H_
+
+/*
+ * TODO:
+ * 1. b/196923176: WHI panel bridge porting for suspend/resume.
+ *
+ */
+#ifdef CONFIG_SOC_GOOGLE
+#undef FTS_DRM_BRIDGE
+#undef FTS_VFS_EN
+#undef CONFIG_FB
+#undef CONFIG_DRM_PANEL
+#undef CONFIG_HAS_EARLYSUSPEND
+#undef CONFIG_ARCH_QCOM
+#undef CONFIG_ARCH_MSM
+#endif
+
+/**************************************************/
+/****** G: A, I: B, S: C, U: D  ******************/
+/****** chip type defines, do not modify *********/
+#define _FT8716             0x87160805
+#define _FT8736             0x87360806
+#define _FT8607             0x86070809
+#define _FT8006U            0x8006D80B
+#define _FT8006S            0x8006A80B
+#define _FT8613             0x8613080C
+#define _FT8719             0x8719080D
+#define _FT8739             0x8739080E
+#define _FT8615             0x8615080F
+#define _FT8201             0x82010810
+#define _FT8201AA           0x8201A810
+#define _FT8006P            0x86220811
+#define _FT7251             0x72510812
+#define _FT7252             0x72520813
+#define _FT8613S            0x8613C814
+#define _FT8756             0x87560815
+#define _FT8302             0x83020816
+#define _FT8009             0x80090817
+#define _FT8656             0x86560818
+#define _FT8006S_AA         0x86320819
+#define _FT7250             0x7250081A
+#define _FT7120             0x7120081B
+#define _FT8720             0x8720081C
+#define _FT8726             0x8726081C
+#define _FT8720H            0x8720E81C
+#define _FT8720M            0x8720F81C
+#define _FT8016             0x8016081D
+#define _FT2388             0x2388081E
+#define _FT8006S_AB         0x8642081F
+#define _FT8722             0x87220820
+#define _FT8201AB           0x8201B821
+#define _FT8203             0x82030821
+
+
+#define _FT5416             0x54160402
+#define _FT5426             0x54260402
+#define _FT5435             0x54350402
+#define _FT5436             0x54360402
+#define _FT5526             0x55260402
+#define _FT5526I            0x5526B402
+#define _FT5446             0x54460402
+#define _FT5346             0x53460402
+#define _FT5446I            0x5446B402
+#define _FT5346I            0x5346B402
+#define _FT7661             0x76610402
+#define _FT7511             0x75110402
+#define _FT7421             0x74210402
+#define _FT7681             0x76810402
+#define _FT3C47U            0x3C47D402
+#define _FT3417             0x34170402
+#define _FT3517             0x35170402
+#define _FT3327             0x33270402
+#define _FT3427             0x34270402
+#define _FT7311             0x73110402
+#define _FT5526_V00         0x5526C402
+
+#define _FT5626             0x56260401
+#define _FT5726             0x57260401
+#define _FT5826B            0x5826B401
+#define _FT5826S            0x5826C401
+#define _FT7811             0x78110401
+#define _FT3D47             0x3D470401
+#define _FT3617             0x36170401
+#define _FT3717             0x37170401
+#define _FT3817B            0x3817B401
+#define _FT3517U            0x3517D401
+
+#define _FT6236U            0x6236D003
+#define _FT6336G            0x6336A003
+#define _FT6336U            0x6336D003
+#define _FT6436U            0x6436D003
+#define _FT6436T            0x6436E003
+
+#define _FT3267             0x32670004
+#define _FT3367             0x33670004
+
+#define _FT3327G_003        0x3327A482
+#define _FT3427_003         0x3427D482
+#define _FT3427G_003        0x3427A482
+#define _FT5446_003         0x5446D482
+#define _FT5446_Q03         0x5446C482
+#define _FT5446_P03         0x5446A481
+#define _FT5446_N03         0x5446A489
+#define _FT5426_003         0x5426D482
+#define _FT5526_003         0x5526D482
+
+#define _FT3518             0x35180481
+#define _FT3518U            0x3518D481
+#define _FT3558             0x35580481
+#define _FT3528             0x35280481
+#define _FT5536             0x55360481
+#define _FT5536L            0x5536E481
+#define _FT3418             0x34180481
+
+#define _FT3519             0x35190489
+
+#define _FT5446U            0x5446D083
+#define _FT5456U            0x5456D083
+#define _FT3417U            0x3417D083
+#define _FT5426U            0x5426D083
+#define _FT3428             0x34280083
+#define _FT3437U            0x3437D083
+
+#define _FT7302             0x73020084
+#define _FT7202             0x72020084
+#define _FT3308             0x33080084
+#define _FT6446             0x64460084
+
+#define _FT6346U            0x6346D085
+#define _FT6346G            0x6346A085
+#define _FT3067             0x30670085
+#define _FT3068             0x30680085
+#define _FT3168             0x31680085
+#define _FT3268             0x32680085
+#define _FT6146             0x61460085
+
+#define _FT5726_003         0x5726D486
+#define _FT5726_V03         0x5726C486
+
+#define _FT3618             0x36180487
+#define _FT5646             0x56460487
+#define _FT3A58             0x3A580487
+#define _FT3B58             0x3B580487
+#define _FT3D58             0x3D580487
+#define _FT5936             0x59360487
+#define _FT5A36             0x5A360487
+#define _FT5B36             0x5B360487
+#define _FT5D36             0x5D360487
+#define _FT5946             0x59460487
+#define _FT5A46             0x5A460487
+#define _FT5B46             0x5B460487
+#define _FT5D46             0x5D460487
+
+#define _FT3658U            0x3658D488
+#define _FT3658G            0x3658A488
+
+/*************************************************/
+
+/*
+ * choose your ic chip type of focaltech
+ */
+#define FTS_CHIP_TYPE   _FT3658U
+
+/******************* Enables *********************/
+/*********** 1 to enable, 0 to disable ***********/
+
+/*
+ * show function flag info for GOOGLE debug
+ */
+#define GOOGLE_REPORT_MODE                      1
+
+/*
+ * show debug log info for heatmap
+ */
+#define GOOGLE_HEATMAP_DEBUG                    0
+/*
+ * show debug log info
+ * enable it for debug, disable it for release
+ */
+#define FTS_DEBUG_EN                            1
+
+/*
+ * Log level of touch key info
+ * 0: Do not show key info
+ * 1: Show single key event
+ * 2: Show continuous key event
+ * 3: Show continuous key event and buffer info
+ */
+#define FTS_KEY_LOG_LEVEL                       0
+
+/*
+ * Linux MultiTouch Protocol
+ * 1: Protocol B(default), 0: Protocol A
+ */
+#define FTS_MT_PROTOCOL_B_EN                    1
+
+/*
+ * Report Pressure in multitouch
+ * 1:enable(default),0:disable
+*/
+#define FTS_REPORT_PRESSURE_EN                  1
+
+/*
+ * Stylus PEN enable
+ * 1:enable(default),0:disable
+*/
+#define FTS_PEN_EN                              0
+
+/*
+ * Gesture function enable
+ * default: disable
+ */
+#define FTS_GESTURE_EN                          0
+
+/*
+ * AOC Gesture function enable
+ * 1:enable(default),0:disable
+ */
+#define FTS_AOC_GESTURE_EN                      1
+
+/*
+ * ESD check & protection
+ * default: disable
+ */
+#define FTS_ESDCHECK_EN                         0
+
+/*
+ * Production test enable
+ * 1: enable, 0:disable(default)
+ */
+#define FTS_TEST_EN                             1
+
+/*
+ * Pinctrl enable
+ * default: disable
+ */
+#define FTS_PINCTRL_EN                          1
+
+/*
+ * Customer power enable
+ * enable it when customer need control TP power
+ * default: disable
+ */
+#define FTS_POWER_SOURCE_CUST_EN                1
+
+/****************************************************/
+
+/********************** Upgrade ****************************/
+/*
+ * auto upgrade
+ */
+#define FTS_AUTO_UPGRADE_EN                     1
+
+/*
+ * auto upgrade for lcd cfg
+ */
+#define FTS_AUTO_LIC_UPGRADE_EN                 0
+
+/*
+ * Numbers of modules support
+ */
+#define FTS_GET_MODULE_NUM                      0
+
+/*
+ * module_id: mean vendor_id generally, also maybe gpio or lcm_id...
+ * If means vendor_id, the FTS_MODULE_ID = PANEL_ID << 8 + VENDOR_ID
+ * FTS_GET_MODULE_NUM == 0/1, no check module id, you may ignore them
+ * FTS_GET_MODULE_NUM >= 2, compatible with FTS_MODULE2_ID
+ * FTS_GET_MODULE_NUM >= 3, compatible with FTS_MODULE3_ID
+ */
+#define FTS_MODULE_ID                          0x0000
+#define FTS_MODULE2_ID                         0x0000
+#define FTS_MODULE3_ID                         0x0000
+
+/*
+ * Need set the following when get firmware via firmware_request()
+ * For example: if module'vendor is tianma,
+ * #define FTS_MODULE_NAME                        "tianma"
+ * then file_name will be "focaltech_ts_fw_tianma"
+ * You should rename fw to "focaltech_ts_fw_tianma", and push it into
+ * etc/firmware or by customers
+ */
+#define FTS_MODULE_NAME                        ""
+#define FTS_MODULE2_NAME                       ""
+#define FTS_MODULE3_NAME                       ""
+
+/*
+ * FW.i file for auto upgrade, you must replace it with your own
+ * define your own fw_file, the sample one to be replaced is invalid
+ * NOTE: if FTS_GET_MODULE_NUM > 1, it's the fw corresponding with FTS_VENDOR_ID
+ */
+#define FTS_UPGRADE_FW_FILE                      "include/firmware/fw_sample.i"
+
+/*
+ * if FTS_GET_MODULE_NUM >= 2, fw corrsponding with FTS_VENDOR_ID2
+ * define your own fw_file, the sample one is invalid
+ */
+#define FTS_UPGRADE_FW2_FILE                     "include/firmware/fw_sample.i"
+
+/*
+ * if FTS_GET_MODULE_NUM >= 3, fw corrsponding with FTS_VENDOR_ID3
+ * define your own fw_file, the sample one is invalid
+ */
+#define FTS_UPGRADE_FW3_FILE                     "include/firmware/fw_sample.i"
+
+/*********************************************************/
+
+#endif /* _LINUX_FOCLATECH_CONFIG_H_ */
diff --git a/ft3658/focaltech_core.c b/ft3658/focaltech_core.c
new file mode 100644
index 0000000..e03d449
--- /dev/null
+++ b/ft3658/focaltech_core.c
@@ -0,0 +1,3888 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/*****************************************************************************
+*
+* File Name: focaltech_core.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract: entrance for focaltech ts driver
+*
+* Version: V1.0
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#if defined(CONFIG_FB)
+#include <linux/notifier.h>
+#include <linux/fb.h>
+#elif defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+#include <drm/drm_panel.h>
+#elif defined(CONFIG_ARCH_MSM)
+#include <linux/msm_drm_notify.h>
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+#include <linux/earlysuspend.h>
+#define FTS_SUSPEND_LEVEL 1     /* Early-suspend level */
+#endif
+#include <linux/types.h>
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_DRIVER_NAME                     "fts_ts"
+#define FTS_DRIVER_PEN_NAME                 "fts_ts,pen"
+#define INTERVAL_READ_REG                   200  /* unit:ms */
+#define TIMEOUT_READ_REG                    1000 /* unit:ms */
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+struct fts_ts_data *fts_data;
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+static int register_panel_bridge(struct fts_ts_data *ts);
+static void unregister_panel_bridge(struct drm_bridge *bridge);
+#endif
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+static void fts_offload_set_running(struct fts_ts_data *ts_data, bool running);
+static void fts_populate_frame(struct fts_ts_data *ts_data, int populate_channel_types);
+static void fts_offload_push_coord_frame(struct fts_ts_data *ts);
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+static int fts_get_heatmap(struct fts_ts_data *ts_data);
+#endif
+static int fts_ts_suspend(struct device *dev);
+static int fts_ts_resume(struct device *dev);
+static void fts_update_motion_filter(struct fts_ts_data *ts, u8 touches);
+
+static char *status_list_str[STATUS_CNT_END] = {
+    "Baseline refreshed",
+    "Baseline refreshed",
+    "Palm",
+    "Water",
+    "Grip",
+    "Glove",
+    "Edge palm",
+    "RESET",
+};
+
+static char *feature_list_str[FW_CNT_END] = {
+    "FW_GLOVE",
+    "FW_GRIP",
+    "FW_PALM",
+    "FW_HEATMAP",
+    "FW_CONTINUOUS",
+};
+
+static char *status_baseline_refresh_str[4] = {
+    "Baseline refreshed: none",
+    "Baseline refreshed: removing touch",
+    "Baseline refreshed: removing water",
+    "Baseline refreshed: removing shell iron",
+};
+
+int fts_check_cid(struct fts_ts_data *ts_data, u8 id_h)
+{
+    int i = 0;
+    struct ft_chip_id_t *cid = &ts_data->ic_info.cid;
+    u8 cid_h = 0x0;
+
+    if (cid->type == 0)
+        return -ENODATA;
+
+    for (i = 0; i < FTS_MAX_CHIP_IDS; i++) {
+        cid_h = ((cid->chip_ids[i] >> 8) & 0x00FF);
+        if (cid_h && (id_h == cid_h)) {
+            return 0;
+        }
+    }
+
+    return -ENODATA;
+}
+
+/*****************************************************************************
+*  Name: fts_wait_tp_to_valid
+*  Brief: Read chip id until TP FW become valid(Timeout: TIMEOUT_READ_REG),
+*         need call when reset/power on/resume...
+*  Input:
+*  Output:
+*  Return: return 0 if tp valid, otherwise return error code
+*****************************************************************************/
+int fts_wait_tp_to_valid(void)
+{
+    int ret = 0;
+    int cnt = 0;
+    u8 idh = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 chip_idh = ts_data->ic_info.ids.chip_idh;
+    u16 retry_duration = 0;
+
+    do {
+        ret = fts_read_reg(FTS_REG_CHIP_ID, &idh);
+
+        if (ret == 0 && ((idh == chip_idh) || (fts_check_cid(ts_data, idh) == 0))) {
+            FTS_INFO("TP Ready,Device ID:0x%02x, retry:%d", idh, cnt);
+            return 0;
+        }
+
+        cnt++;
+        if (ret == -EIO) {
+            fts_reset_proc(FTS_RESET_INTERVAL);
+            retry_duration += FTS_RESET_INTERVAL;
+        } else {
+            msleep(INTERVAL_READ_REG);
+            retry_duration += INTERVAL_READ_REG;
+        }
+
+    } while (retry_duration < TIMEOUT_READ_REG);
+
+    FTS_ERROR("Wait tp timeout");
+    return -ETIMEDOUT;
+}
+
+/*****************************************************************************
+*  Name: fts_tp_state_recovery
+*  Brief: Need execute this function when reset
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+void fts_tp_state_recovery(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    /* wait tp stable */
+    fts_wait_tp_to_valid();
+    /* recover all firmware modes based on the settings of driver side. */
+    fts_ex_mode_recovery(ts_data);
+    /* recover TP gesture state 0xD0 */
+    fts_gesture_recovery(ts_data);
+    FTS_FUNC_EXIT();
+}
+
+int fts_reset_proc(int hdelayms)
+{
+    FTS_DEBUG("tp reset");
+    gpio_direction_output(fts_data->pdata->reset_gpio, 0);
+    /* The minimum reset duration is 1 ms. */
+    msleep(1);
+    gpio_direction_output(fts_data->pdata->reset_gpio, 1);
+    if (hdelayms) {
+        msleep(hdelayms);
+    }
+
+    return 0;
+}
+
+void fts_irq_disable(void)
+{
+    unsigned long irqflags;
+
+    FTS_FUNC_ENTER();
+    spin_lock_irqsave(&fts_data->irq_lock, irqflags);
+
+    if (!fts_data->irq_disabled) {
+        disable_irq_nosync(fts_data->irq);
+        fts_data->irq_disabled = true;
+    }
+
+    spin_unlock_irqrestore(&fts_data->irq_lock, irqflags);
+    FTS_FUNC_EXIT();
+}
+
+void fts_irq_enable(void)
+{
+    unsigned long irqflags = 0;
+
+    FTS_FUNC_ENTER();
+    spin_lock_irqsave(&fts_data->irq_lock, irqflags);
+
+    if (fts_data->irq_disabled) {
+        enable_irq(fts_data->irq);
+        fts_data->irq_disabled = false;
+    }
+
+    spin_unlock_irqrestore(&fts_data->irq_lock, irqflags);
+    FTS_FUNC_EXIT();
+}
+
+void fts_hid2std(void)
+{
+    int ret = 0;
+    u8 buf[3] = {0xEB, 0xAA, 0x09};
+
+    if (fts_data->bus_type != FTS_BUS_TYPE_I2C)
+        return;
+
+    ret = fts_write(buf, 3);
+    if (ret < 0) {
+        FTS_ERROR("hid2std cmd write fail");
+    } else {
+        msleep(10);
+        buf[0] = buf[1] = buf[2] = 0;
+        ret = fts_read(NULL, 0, buf, 3);
+        if (ret < 0) {
+            FTS_ERROR("hid2std cmd read fail");
+        } else if ((0xEB == buf[0]) && (0xAA == buf[1]) && (0x08 == buf[2])) {
+            FTS_DEBUG("hidi2c change to stdi2c successful");
+        } else {
+            FTS_DEBUG("hidi2c change to stdi2c not support or fail");
+        }
+    }
+}
+
+static int fts_match_cid(struct fts_ts_data *ts_data,
+                         u16 type, u8 id_h, u8 id_l, bool force)
+{
+#ifdef FTS_CHIP_ID_MAPPING
+    u32 i = 0;
+    u32 j = 0;
+    struct ft_chip_id_t chip_id_list[] = FTS_CHIP_ID_MAPPING;
+    u32 cid_entries = sizeof(chip_id_list) / sizeof(struct ft_chip_id_t);
+    u16 id = (id_h << 8) + id_l;
+
+    memset(&ts_data->ic_info.cid, 0, sizeof(struct ft_chip_id_t));
+    for (i = 0; i < cid_entries; i++) {
+        if (!force && (type == chip_id_list[i].type)) {
+            break;
+        } else if (force && (type == chip_id_list[i].type)) {
+            FTS_INFO("match cid,type:0x%x", (int)chip_id_list[i].type);
+            ts_data->ic_info.cid = chip_id_list[i];
+            return 0;
+        }
+    }
+
+    if (i >= cid_entries) {
+        return -ENODATA;
+    }
+
+    for (j = 0; j < FTS_MAX_CHIP_IDS; j++) {
+        if (id == chip_id_list[i].chip_ids[j]) {
+            FTS_DEBUG("cid:%x==%x", id, chip_id_list[i].chip_ids[j]);
+            FTS_INFO("match cid,type:0x%x", (int)chip_id_list[i].type);
+            ts_data->ic_info.cid = chip_id_list[i];
+            return 0;
+        }
+    }
+
+    return -ENODATA;
+#else
+    return -EINVAL;
+#endif
+}
+
+static int fts_get_chip_types(
+    struct fts_ts_data *ts_data,
+    u8 id_h, u8 id_l, bool fw_valid)
+{
+    u32 i = 0;
+    struct ft_chip_t ctype[] = FTS_CHIP_TYPE_MAPPING;
+    u32 ctype_entries = sizeof(ctype) / sizeof(struct ft_chip_t);
+
+    if ((0x0 == id_h) || (0x0 == id_l)) {
+        FTS_ERROR("id_h/id_l is 0");
+        return -EINVAL;
+    }
+
+    FTS_DEBUG("verify id:0x%02x%02x", id_h, id_l);
+    for (i = 0; i < ctype_entries; i++) {
+        if (VALID == fw_valid) {
+            if (((id_h == ctype[i].chip_idh) && (id_l == ctype[i].chip_idl))
+                || (!fts_match_cid(ts_data, ctype[i].type, id_h, id_l, 0)))
+                break;
+        } else {
+            if (((id_h == ctype[i].rom_idh) && (id_l == ctype[i].rom_idl))
+                || ((id_h == ctype[i].pb_idh) && (id_l == ctype[i].pb_idl))
+                || ((id_h == ctype[i].bl_idh) && (id_l == ctype[i].bl_idl))) {
+                break;
+            }
+        }
+    }
+
+    if (i >= ctype_entries) {
+        return -ENODATA;
+    }
+
+    fts_match_cid(ts_data, ctype[i].type, id_h, id_l, 1);
+    ts_data->ic_info.ids = ctype[i];
+    return 0;
+}
+
+static int fts_read_bootid(struct fts_ts_data *ts_data, u8 *id)
+{
+    int ret = 0;
+    u8 chip_id[2] = { 0 };
+    u8 id_cmd[4] = { 0 };
+    u32 id_cmd_len = 0;
+
+    id_cmd[0] = FTS_CMD_START1;
+    id_cmd[1] = FTS_CMD_START2;
+    ret = fts_write(id_cmd, 2);
+    if (ret < 0) {
+        FTS_ERROR("start cmd write fail");
+        return ret;
+    }
+
+    msleep(FTS_CMD_START_DELAY);
+    id_cmd[0] = FTS_CMD_READ_ID;
+    id_cmd[1] = id_cmd[2] = id_cmd[3] = 0x00;
+    if (ts_data->ic_info.is_incell)
+        id_cmd_len = FTS_CMD_READ_ID_LEN_INCELL;
+    else
+        id_cmd_len = FTS_CMD_READ_ID_LEN;
+    ret = fts_read(id_cmd, id_cmd_len, chip_id, 2);
+    if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) {
+        FTS_ERROR("read boot id fail,read:0x%02x%02x", chip_id[0], chip_id[1]);
+        return -EIO;
+    }
+
+    id[0] = chip_id[0];
+    id[1] = chip_id[1];
+    return 0;
+}
+
+/*****************************************************************************
+* Name: fts_get_ic_information
+* Brief: read chip id to get ic information, after run the function, driver w-
+*        ill know which IC is it.
+*        If cant get the ic information, maybe not focaltech's touch IC, need
+*        unregister the driver
+* Input:
+* Output:
+* Return: return 0 if get correct ic information, otherwise return error code
+*****************************************************************************/
+static int fts_get_ic_information(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int cnt = 0;
+    u8 chip_id[2] = { 0 };
+
+    ts_data->ic_info.is_incell = FTS_CHIP_IDC;
+    ts_data->ic_info.hid_supported = FTS_HID_SUPPORTTED;
+
+    do {
+        ret = fts_read_reg(FTS_REG_CHIP_ID, &chip_id[0]);
+        ret = fts_read_reg(FTS_REG_CHIP_ID2, &chip_id[1]);
+        if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) {
+            FTS_DEBUG("chip id read invalid, read:0x%02x%02x",
+                      chip_id[0], chip_id[1]);
+        } else {
+            ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], VALID);
+            if (!ret)
+                break;
+            else
+                FTS_DEBUG("TP not ready, read:0x%02x%02x",
+                          chip_id[0], chip_id[1]);
+        }
+
+        cnt++;
+        msleep(INTERVAL_READ_REG);
+    } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG);
+
+    if ((cnt * INTERVAL_READ_REG) >= TIMEOUT_READ_REG) {
+        FTS_INFO("fw is invalid, need read boot id");
+        if (ts_data->ic_info.hid_supported) {
+            fts_hid2std();
+        }
+
+        ret = fts_read_bootid(ts_data, &chip_id[0]);
+        if (ret <  0) {
+            FTS_ERROR("read boot id fail");
+            return ret;
+        }
+
+        ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], INVALID);
+        if (ret < 0) {
+            FTS_ERROR("can't get ic informaton");
+            return ret;
+        }
+    }
+
+    FTS_INFO("get ic information, chip id = 0x%02x%02x(cid type=0x%x)",
+             ts_data->ic_info.ids.chip_idh, ts_data->ic_info.ids.chip_idl,
+             ts_data->ic_info.cid.type);
+
+    return 0;
+}
+
+/*****************************************************************************
+*  Reprot related
+*****************************************************************************/
+static void fts_show_touch_buffer(u8 *data, int datalen)
+{
+    int i = 0;
+    int count = 0;
+    char *tmpbuf = NULL;
+
+    tmpbuf = kzalloc(1024, GFP_KERNEL);
+    if (!tmpbuf) {
+        FTS_ERROR("tmpbuf zalloc fail");
+        return;
+    }
+
+    for (i = 0; i < datalen; i++) {
+        count += scnprintf(tmpbuf + count, 1024 - count, "%02X,", data[i]);
+        if (count >= 1024)
+            break;
+    }
+    FTS_DEBUG("point buffer:%s", tmpbuf);
+
+    if (tmpbuf) {
+        kfree(tmpbuf);
+        tmpbuf = NULL;
+    }
+}
+
+void fts_release_all_finger(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+#if FTS_MT_PROTOCOL_B_EN
+    u32 finger_count = 0;
+    u32 max_touches = ts_data->pdata->max_touch_number;
+#endif
+
+    mutex_lock(&ts_data->report_mutex);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    for (finger_count = 0; finger_count < max_touches; finger_count++) {
+        ts_data->offload.coords[finger_count].status = COORD_STATUS_INACTIVE;
+        ts_data->offload.coords[finger_count].major = 0;
+        ts_data->offload.coords[finger_count].minor = 0;
+        ts_data->offload.coords[finger_count].pressure = 0;
+        ts_data->offload.coords[finger_count].rotation = 0;
+    }
+
+    if (ts_data->touch_offload_active_coords && ts_data->offload.offload_running) {
+        fts_offload_push_coord_frame(ts_data);
+    } else {
+#endif
+#if FTS_MT_PROTOCOL_B_EN
+    for (finger_count = 0; finger_count < max_touches; finger_count++) {
+        input_mt_slot(input_dev, finger_count);
+        input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false);
+    }
+#else
+    input_mt_sync(input_dev);
+#endif
+    input_report_key(input_dev, BTN_TOUCH, 0);
+    input_sync(input_dev);
+
+#if FTS_PEN_EN
+    input_report_key(ts_data->pen_dev, BTN_TOOL_PEN, 0);
+    input_report_key(ts_data->pen_dev, BTN_TOUCH, 0);
+    input_sync(ts_data->pen_dev);
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    }
+#endif
+
+    ts_data->touchs = 0;
+    ts_data->key_state = 0;
+    mutex_unlock(&ts_data->report_mutex);
+}
+
+/*****************************************************************************
+* Name: fts_input_report_key
+* Brief: process key events,need report key-event if key enable.
+*        if point's coordinate is in (x_dim-50,y_dim-50) ~ (x_dim+50,y_dim+50),
+*        need report it to key event.
+*        x_dim: parse from dts, means key x_coordinate, dimension:+-50
+*        y_dim: parse from dts, means key y_coordinate, dimension:+-50
+* Input:
+* Output:
+* Return: return 0 if it's key event, otherwise return error code
+*****************************************************************************/
+static int fts_input_report_key(struct fts_ts_data *data, int index)
+{
+    int i = 0;
+    int x = data->events[index].x;
+    int y = data->events[index].y;
+    int *x_dim = &data->pdata->key_x_coords[0];
+    int *y_dim = &data->pdata->key_y_coords[0];
+
+    if (!data->pdata->have_key) {
+        return -EINVAL;
+    }
+    for (i = 0; i < data->pdata->key_number; i++) {
+        if ((x >= x_dim[i] - FTS_KEY_DIM) && (x <= x_dim[i] + FTS_KEY_DIM) &&
+            (y >= y_dim[i] - FTS_KEY_DIM) && (y <= y_dim[i] + FTS_KEY_DIM)) {
+            if (EVENT_DOWN(data->events[index].flag)
+                && !(data->key_state & (1 << i))) {
+                input_report_key(data->input_dev, data->pdata->keys[i], 1);
+                data->key_state |= (1 << i);
+                FTS_DEBUG("Key%d(%d,%d) DOWN!", i, x, y);
+            } else if (EVENT_UP(data->events[index].flag)
+                       && (data->key_state & (1 << i))) {
+                input_report_key(data->input_dev, data->pdata->keys[i], 0);
+                data->key_state &= ~(1 << i);
+                FTS_DEBUG("Key%d(%d,%d) Up!", i, x, y);
+            }
+            return 0;
+        }
+    }
+    return -EINVAL;
+}
+
+#if FTS_MT_PROTOCOL_B_EN
+static int fts_input_report_b(struct fts_ts_data *data)
+{
+    int i = 0;
+    int touchs = 0;
+    bool va_reported = false;
+    u32 max_touch_num = data->pdata->max_touch_number;
+    struct ts_event *events = data->events;
+
+    for (i = 0; i < data->touch_point; i++) {
+        if (fts_input_report_key(data, i) == 0) {
+            continue;
+        }
+
+        va_reported = true;
+
+        if (EVENT_DOWN(events[i].flag)) {
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+            data->offload.coords[events[i].id].status = COORD_STATUS_FINGER;
+            data->offload.coords[events[i].id].x = events[i].x;
+            data->offload.coords[events[i].id].y = events[i].y;
+            data->offload.coords[events[i].id].pressure = events[i].p;
+            data->offload.coords[events[i].id].major = events[i].major;
+            data->offload.coords[events[i].id].minor = events[i].minor;
+            /* Rotation is not supported by firmware */
+            data->offload.coords[events[i].id].rotation = 0;
+            if (!data->offload.offload_running) {
+#endif
+            input_mt_slot(data->input_dev, events[i].id);
+            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true);
+
+#if FTS_REPORT_PRESSURE_EN
+            if (events[i].p <= 0) {
+                events[i].p = 0x00;
+            }
+            input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p);
+#endif
+            if (events[i].area <= 0) {
+                events[i].area = 0x00;
+            }
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+            }
+#endif
+            touchs |= BIT(events[i].id);
+            data->touchs |= BIT(events[i].id);
+            if ((data->log_level >= 2) ||
+                ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
+                FTS_DEBUG("[B]P%d(%d, %d)[ma:%d,mi:%d,p:%d] DOWN!",
+                          events[i].id,
+                          events[i].x,
+                          events[i].y,
+                          events[i].major,
+                          events[i].minor,
+                          events[i].p);
+            }
+        } else {  //EVENT_UP
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+            data->offload.coords[events[i].id].status = COORD_STATUS_INACTIVE;
+            if (!data->offload.offload_running) {
+#endif
+                input_mt_slot(data->input_dev, events[i].id);
+                input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+            }
+#endif
+            data->touchs &= ~BIT(events[i].id);
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[B1]P%d UP!", events[i].id);
+            }
+        }
+    }
+
+    if (unlikely(data->touchs ^ touchs)) {
+        for (i = 0; i < max_touch_num; i++)  {
+            if (BIT(i) & (data->touchs ^ touchs)) {
+                if (data->log_level >= 1) {
+                    FTS_DEBUG("[B2]P%d UP!", i);
+                }
+                va_reported = true;
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+                data->offload.coords[i].status = COORD_STATUS_INACTIVE;
+                if (!data->offload.offload_running) {
+#endif
+                input_mt_slot(data->input_dev, i);
+                input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+                }
+#endif
+            }
+        }
+    }
+    data->touchs = touchs;
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    if (!data->offload.offload_running) {
+#endif
+    if (va_reported) {
+        /* touchs==0, there's no point but key */
+        if (EVENT_NO_DOWN(data) || (!touchs)) {
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[B]Points All Up!");
+            }
+            input_report_key(data->input_dev, BTN_TOUCH, 0);
+        } else {
+            input_report_key(data->input_dev, BTN_TOUCH, 1);
+        }
+    }
+    input_set_timestamp(data->input_dev, data->coords_timestamp);
+    input_sync(data->input_dev);
+    fts_update_motion_filter(data, data->point_num);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    }
+#endif
+    return 0;
+}
+
+#else
+static int fts_input_report_a(struct fts_ts_data *data)
+{
+    int i = 0;
+    int touchs = 0;
+    bool va_reported = false;
+    struct ts_event *events = data->events;
+
+    for (i = 0; i < data->touch_point; i++) {
+        if (fts_input_report_key(data, i) == 0) {
+            continue;
+        }
+
+        va_reported = true;
+        if (EVENT_DOWN(events[i].flag)) {
+            input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, events[i].id);
+#if FTS_REPORT_PRESSURE_EN
+            if (events[i].p <= 0) {
+                events[i].p = 0x00;
+            }
+            input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p);
+#endif
+            if (events[i].area <= 0) {
+                events[i].area = 0x00;
+            }
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y);
+
+            input_mt_sync(data->input_dev);
+
+            if ((data->log_level >= 2) ||
+                ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
+                FTS_DEBUG("[A]P%d(%d, %d)[ma:%d,mi:%d,p:%d] DOWN!",
+                          events[i].id,
+                          events[i].x,
+                          events[i].y,
+                          events[i].major,
+                          events[i].minor,
+                          events[i].p);
+            }
+            touchs++;
+        }
+    }
+
+    /* last point down, current no point but key */
+    if (data->touchs && !touchs) {
+        va_reported = true;
+    }
+    data->touchs = touchs;
+
+    if (va_reported) {
+        if (EVENT_NO_DOWN(data)) {
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[A]Points All Up!");
+            }
+            input_report_key(data->input_dev, BTN_TOUCH, 0);
+            input_mt_sync(data->input_dev);
+        } else {
+            input_report_key(data->input_dev, BTN_TOUCH, 1);
+        }
+    }
+    input_set_timestamp(data->input_dev, data->timestamp);
+    input_sync(data->input_dev);
+    return 0;
+}
+#endif
+
+#if FTS_PEN_EN
+static int fts_input_pen_report(struct fts_ts_data *data)
+{
+    struct input_dev *pen_dev = data->pen_dev;
+    struct pen_event *pevt = &data->pevent;
+    u8 *buf = data->point_buf;
+
+
+    if (buf[3] & 0x08)
+        input_report_key(pen_dev, BTN_STYLUS, 1);
+    else
+        input_report_key(pen_dev, BTN_STYLUS, 0);
+
+    if (buf[3] & 0x02)
+        input_report_key(pen_dev, BTN_STYLUS2, 1);
+    else
+        input_report_key(pen_dev, BTN_STYLUS2, 0);
+
+    pevt->inrange = (buf[3] & 0x20) ? 1 : 0;
+    pevt->tip = (buf[3] & 0x01) ? 1 : 0;
+    pevt->x = ((buf[4] & 0x0F) << 8) + buf[5];
+    pevt->y = ((buf[6] & 0x0F) << 8) + buf[7];
+    pevt->p = ((buf[8] & 0x0F) << 8) + buf[9];
+    pevt->id = buf[6] >> 4;
+    pevt->flag = buf[4] >> 6;
+    pevt->tilt_x = (buf[10] << 8) + buf[11];
+    pevt->tilt_y = (buf[12] << 8) + buf[13];
+    pevt->tool_type = BTN_TOOL_PEN;
+
+    if (data->log_level >= 2  ||
+        ((1 == data->log_level) && (FTS_TOUCH_DOWN == pevt->flag))) {
+        FTS_DEBUG("[PEN]x:%d,y:%d,p:%d,inrange:%d,tip:%d,flag:%d DOWN!",
+                  pevt->x, pevt->y, pevt->p, pevt->inrange,
+                  pevt->tip, pevt->flag);
+    }
+
+    if ( (data->log_level >= 1) && (!pevt->inrange)) {
+        FTS_DEBUG("[PEN]UP!");
+    }
+
+    input_report_abs(pen_dev, ABS_X, pevt->x);
+    input_report_abs(pen_dev, ABS_Y, pevt->y);
+    input_report_abs(pen_dev, ABS_PRESSURE, pevt->p);
+
+    /* check if the pen support tilt event */
+    if ((pevt->tilt_x != 0) || (pevt->tilt_y != 0)) {
+        input_report_abs(pen_dev, ABS_TILT_X, pevt->tilt_x);
+        input_report_abs(pen_dev, ABS_TILT_Y, pevt->tilt_y);
+    }
+
+    input_report_key(pen_dev, BTN_TOUCH, pevt->tip);
+    input_report_key(pen_dev, BTN_TOOL_PEN, pevt->inrange);
+    input_sync(pen_dev);
+
+    return 0;
+}
+#endif
+
+static int fts_read_touchdata(struct fts_ts_data *data)
+{
+    int ret = 0;
+    u8 *buf = data->point_buf;
+    u8 cmd[1] = { 0 };
+
+#if IS_ENABLED(GOOGLE_REPORT_MODE)
+    u8 regB2_data[FTS_CUSTOMER_STATUS_LEN] = { 0 };
+    u8 check_regB2_status[2] = { 0 };
+    int i;
+
+    if (data->work_mode == FTS_REG_WORKMODE_WORK_VALUE) {
+        /* If fw_heatmap_mode is enableed compressed heatmap, to read register
+         * 0xB2 before fts_get_heatamp() to get the length of compressed
+         * heatmap first.
+         */
+        if (data->fw_heatmap_mode == FW_HEATMAP_MODE_COMPRESSED) {
+            cmd[0] = FTS_REG_CUSTOMER_STATUS;
+            fts_read(cmd, 1, regB2_data, FTS_CUSTOMER_STATUS_LEN);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+            data->compress_heatmap_wlen = (regB2_data[2] << 8) + regB2_data[3];
+#endif
+        }
+    }
+#endif
+
+    cmd[0] = FTS_CMD_READ_TOUCH_DATA;
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    ret = fts_get_heatmap(data);
+    if (ret < 0)
+        return ret;
+    memcpy(buf + 1, data->heatmap_raw, data->pnt_buf_size - 1);
+#else
+    ret = fts_read(cmd, 1, buf + 1, data->pnt_buf_size - 1);
+    if (ret < 0) {
+        FTS_ERROR("touch data(%x) abnormal,ret:%d", buf[1], ret);
+        return -EIO;
+    }
+#endif
+
+    if (data->gesture_mode) {
+        ret = fts_gesture_readdata(data, true);
+        if (ret == 0) {
+            FTS_INFO("succuss to get gesture data in irq handler");
+            return 1;
+        }
+    }
+
+#if IS_ENABLED(GOOGLE_REPORT_MODE)
+    if (data->work_mode == FTS_REG_WORKMODE_WORK_VALUE) {
+        /* If fw_heatmap_mode is disabled heatmap or enableed uncompressed
+         * heatmap, to read register 0xB2 after fts_get_heatamp().
+         */
+        if (data->fw_heatmap_mode != FW_HEATMAP_MODE_COMPRESSED) {
+            cmd[0] = FTS_REG_CUSTOMER_STATUS;
+            fts_read(cmd, 1, regB2_data, FTS_CUSTOMER_STATUS_LEN);
+        }
+
+        check_regB2_status[0] = regB2_data[0] ^ data->current_host_status[0] ;
+        if (check_regB2_status[0]) { // current_status is different with previous_status
+            for (i = STATUS_BASELINE_REFRESH_B1; i < STATUS_CNT_END; i++) {
+                if ((i == STATUS_BASELINE_REFRESH_B1) && (check_regB2_status[0] & 0x03)) {
+                    FTS_INFO("-------%s\n",
+                        status_baseline_refresh_str[regB2_data[0] & 0x03]);
+                } else {
+                    bool status_changed = check_regB2_status[0] & (1 << i);
+                    bool new_status = regB2_data[0] & (1 << i);
+                    if (status_changed) {
+                        FTS_INFO("-------%s %s\n", status_list_str[i],
+                            new_status ? "enter" : "exit");
+                        if (i == STATUS_RESET && new_status) {
+                            /* Write 0x01 to register(0xEC) to clear the reset
+                             * flag in bit 7 of register(0xB2).
+                             */
+                            fts_write_reg(FTS_REG_CLR_RESET, 0x01);
+                        }
+                    }
+                }
+            }
+            data->current_host_status[0] = regB2_data[0];
+        }
+        check_regB2_status[1] =
+            (regB2_data[1] ^ data->current_host_status[1]) & FTS_CUSTOMER_STATUS1_MASK;
+        if (check_regB2_status[1]) {
+            bool feature_changed;
+            bool feature_enabled;
+            FTS_ERROR("FW settings dose not match host side, host: 0x%x, B2[1]:0x%x\n",
+                data->current_host_status[1], regB2_data[1]);
+            for (i = FW_GLOVE; i < FW_CNT_END; i++) {
+                feature_changed = check_regB2_status[1] & (1 << i);
+                feature_enabled = regB2_data[1] & (1 << i);
+                if (feature_changed) {
+                    FTS_INFO("-------%s setting %s\n", feature_list_str[i],
+                        feature_enabled ? "enable" : "disable");
+                }
+            }
+            /* The status in data->current_host_status[1] are updated in
+             * fts_update_host_feature_setting().
+             */
+
+            /* recover touch firmware state. */
+            fts_tp_state_recovery(data);
+        }
+    }
+#endif
+
+    if (data->log_level >= 3) {
+        fts_show_touch_buffer(buf, data->pnt_buf_size);
+    }
+
+    return ret;
+}
+
+static int fts_read_parse_touchdata(struct fts_ts_data *data)
+{
+    int ret = 0;
+    int i = 0;
+    u8 pointid = 0;
+    int base = 0;
+    struct ts_event *events = data->events;
+    int max_touch_num = data->pdata->max_touch_number;
+    u8 *buf = data->point_buf;
+
+    ret = fts_read_touchdata(data);
+    if (ret) {
+        return ret;
+    }
+
+#if FTS_PEN_EN
+    if ((buf[2] & 0xF0) == 0xB0) {
+        fts_input_pen_report(data);
+        return 2;
+    }
+#endif
+
+    data->point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F;
+    data->touch_point = 0;
+
+    if (data->ic_info.is_incell) {
+        if ((data->point_num == 0x0F) && (buf[2] == 0xFF) && (buf[3] == 0xFF)
+            && (buf[4] == 0xFF) && (buf[5] == 0xFF) && (buf[6] == 0xFF)) {
+            FTS_DEBUG("touch buff is 0xff, need recovery state");
+            fts_release_all_finger();
+            fts_tp_state_recovery(data);
+            data->point_num = 0;
+            return -EIO;
+        }
+    }
+
+    if (data->point_num > max_touch_num) {
+        FTS_DEBUG("invalid point_num(%d)", data->point_num);
+        data->point_num = 0;
+        return -EIO;
+    }
+
+    for (i = 0; i < max_touch_num; i++) {
+        base = FTS_ONE_TCH_LEN * i;
+        pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4;
+        if (pointid >= FTS_MAX_ID)
+            break;
+        else if (pointid >= max_touch_num) {
+            FTS_ERROR("ID(%d) beyond max_touch_number", pointid);
+            return -EINVAL;
+        }
+
+        data->touch_point++;
+        events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) +
+                      (buf[FTS_TOUCH_X_L_POS + base] & 0xFF);
+        events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) +
+                      (buf[FTS_TOUCH_Y_L_POS + base] & 0xFF);
+        events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6;
+        events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4;
+        events[i].p = (((buf[FTS_TOUCH_AREA_POS + base] << 1) & 0x02) +
+                       (buf[FTS_TOUCH_PRE_POS + base] & 0x01)) *
+                       FTS_PRESSURE_SCALE;
+        events[i].minor =
+            ((buf[FTS_TOUCH_PRE_POS + base] >> 1) & 0x7F) * data->pdata->mm2px;
+        events[i].major =
+            ((buf[FTS_TOUCH_AREA_POS + base] >> 1) & 0x7F) * data->pdata->mm2px;
+
+        if (EVENT_DOWN(events[i].flag) && (data->point_num == 0)) {
+            FTS_INFO("abnormal touch data from fw");
+            return -EIO;
+        }
+    }
+
+    if (data->touch_point == 0) {
+        FTS_INFO("no touch point information(%02x)", buf[2]);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static void fts_irq_read_report(void)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_set_intr(1);
+#endif
+
+#if FTS_POINT_REPORT_CHECK_EN
+    fts_prc_queue_work(ts_data);
+#endif
+
+    ret = fts_read_parse_touchdata(ts_data);
+    if (ret == 0) {
+        mutex_lock(&ts_data->report_mutex);
+#if FTS_MT_PROTOCOL_B_EN
+        fts_input_report_b(ts_data);
+#else
+        fts_input_report_a(ts_data);
+#endif
+        mutex_unlock(&ts_data->report_mutex);
+    }
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    ret = touch_offload_reserve_frame(&ts_data->offload,
+                                      &ts_data->reserved_frame);
+    if (ret != 0) {
+        PR_LOGD("Could not reserve a frame: error=%d.\n", ret);
+        /* Stop offload when there are no buffers available. */
+        fts_offload_set_running(ts_data, false);
+    } else {
+        fts_offload_set_running(ts_data, true);
+        PR_LOGD("reserve a frame ok");
+        fts_populate_frame(ts_data, 0xFFFFFFFF);
+
+        ret = touch_offload_queue_frame(&ts_data->offload,
+                                        ts_data->reserved_frame);
+        if (ret != 0) {
+            FTS_ERROR("Failed to queue reserved frame: error=%d.\n", ret);
+        }
+    }
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    heatmap_read(&ts_data->v4l2, ktime_to_ns(ts_data->coords_timestamp));
+#endif
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_set_intr(0);
+#endif
+}
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+static void fts_ts_aggregate_bus_state(struct fts_ts_data *ts)
+{
+    /* Complete or cancel any outstanding transitions */
+    cancel_work_sync(&ts->suspend_work);
+    cancel_work_sync(&ts->resume_work);
+
+    if ((ts->bus_refmask == 0 &&
+        ts->power_status == FTS_TS_STATE_SUSPEND) ||
+        (ts->bus_refmask != 0 &&
+        ts->power_status != FTS_TS_STATE_SUSPEND))
+        return;
+
+    if (ts->bus_refmask == 0)
+        queue_work(ts->ts_workqueue, &ts->suspend_work);
+    else
+        queue_work(ts->ts_workqueue, &ts->resume_work);
+}
+
+int fts_ts_set_bus_ref(struct fts_ts_data *ts, u16 ref, bool enable)
+{
+    int result = 0;
+
+    mutex_lock(&ts->bus_mutex);
+
+    if ((enable && (ts->bus_refmask & ref)) ||
+        (!enable && !(ts->bus_refmask & ref))) {
+        mutex_unlock(&ts->bus_mutex);
+        return -EINVAL;
+    }
+
+    if (enable) {
+        /* IRQs can only keep the bus active. IRQs received while the
+         * bus is transferred to AOC should be ignored.
+         */
+        if (ref == FTS_TS_BUS_REF_IRQ && ts->bus_refmask == 0)
+            result = -EAGAIN;
+        else
+            ts->bus_refmask |= ref;
+    } else
+        ts->bus_refmask &= ~ref;
+    fts_ts_aggregate_bus_state(ts);
+
+    mutex_unlock(&ts->bus_mutex);
+
+    /* When triggering a wake, wait up to one second to resume. SCREEN_ON
+     * and IRQ references do not need to wait.
+     */
+    if (enable &&
+        ref != FTS_TS_BUS_REF_SCREEN_ON && ref != FTS_TS_BUS_REF_IRQ) {
+        wait_for_completion_timeout(&ts->bus_resumed, HZ);
+        if (ts->power_status != FTS_TS_STATE_POWER_ON) {
+            FTS_ERROR("Failed to wake the touch bus.\n");
+            result = -ETIMEDOUT;
+        }
+    }
+
+    return result;
+}
+
+struct drm_connector *get_bridge_connector(struct drm_bridge *bridge)
+{
+    struct drm_connector *connector;
+    struct drm_connector_list_iter conn_iter;
+
+    drm_connector_list_iter_begin(bridge->dev, &conn_iter);
+    drm_for_each_connector_iter(connector, &conn_iter) {
+        if (connector->encoder == bridge->encoder)
+            break;
+    }
+    drm_connector_list_iter_end(&conn_iter);
+    return connector;
+}
+
+static bool bridge_is_lp_mode(struct drm_connector *connector)
+{
+    if (connector && connector->state) {
+        struct exynos_drm_connector_state *s =
+            to_exynos_connector_state(connector->state);
+        return s->exynos_mode.is_lp_mode;
+    }
+    return false;
+}
+
+static void panel_bridge_enable(struct drm_bridge *bridge)
+{
+    struct fts_ts_data *ts =
+        container_of(bridge, struct fts_ts_data, panel_bridge);
+
+    if (!ts->is_panel_lp_mode)
+        fts_ts_set_bus_ref(ts, FTS_TS_BUS_REF_SCREEN_ON, true);
+}
+
+static void panel_bridge_disable(struct drm_bridge *bridge)
+{
+    struct fts_ts_data *ts =
+        container_of(bridge, struct fts_ts_data, panel_bridge);
+
+    if (bridge->encoder && bridge->encoder->crtc) {
+        const struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state;
+
+        if (drm_atomic_crtc_effectively_active(crtc_state))
+            return;
+    }
+
+    fts_ts_set_bus_ref(ts, FTS_TS_BUS_REF_SCREEN_ON, false);
+}
+
+static void panel_bridge_mode_set(struct drm_bridge *bridge,
+    const struct drm_display_mode *mode,
+    const struct drm_display_mode *adjusted_mode)
+{
+    struct fts_ts_data *ts =
+        container_of(bridge, struct fts_ts_data, panel_bridge);
+
+    if (!ts->connector || !ts->connector->state)
+        ts->connector = get_bridge_connector(bridge);
+
+    ts->is_panel_lp_mode = bridge_is_lp_mode(ts->connector);
+    fts_ts_set_bus_ref(ts, FTS_TS_BUS_REF_SCREEN_ON, !ts->is_panel_lp_mode);
+
+    if (adjusted_mode) {
+        int vrefresh = drm_mode_vrefresh(adjusted_mode);
+
+        if (ts->display_refresh_rate != vrefresh) {
+            FTS_INFO("refresh rate(Hz) changed to %d from %d\n",
+                vrefresh, ts->display_refresh_rate);
+            ts->display_refresh_rate = vrefresh;
+        }
+    }
+}
+
+static const struct drm_bridge_funcs panel_bridge_funcs = {
+    .enable = panel_bridge_enable,
+    .disable = panel_bridge_disable,
+    .mode_set = panel_bridge_mode_set,
+};
+
+static int register_panel_bridge(struct fts_ts_data *ts)
+{
+    FTS_FUNC_ENTER();
+#ifdef CONFIG_OF
+    ts->panel_bridge.of_node = ts->spi->dev.of_node;
+#endif
+    ts->panel_bridge.funcs = &panel_bridge_funcs;
+    drm_bridge_add(&ts->panel_bridge);
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static void unregister_panel_bridge(struct drm_bridge *bridge)
+{
+    struct drm_bridge *node;
+
+    FTS_FUNC_ENTER();
+    drm_bridge_remove(bridge);
+
+    if (!bridge->dev) /* not attached */
+        return;
+
+    drm_modeset_lock(&bridge->dev->mode_config.connection_mutex, NULL);
+    list_for_each_entry(node, &bridge->encoder->bridge_chain, chain_node)
+        if (node == bridge) {
+            if (bridge->funcs->detach)
+                bridge->funcs->detach(bridge);
+            list_del(&bridge->chain_node);
+            break;
+        }
+    drm_modeset_unlock(&bridge->dev->mode_config.connection_mutex);
+    bridge->dev = NULL;
+    FTS_FUNC_EXIT();
+}
+#endif
+
+/* Update a state machine used to toggle control of the touch IC's motion
+ * filter.
+ */
+static void fts_update_motion_filter(struct fts_ts_data *ts, u8 touches)
+{
+    /* Motion filter timeout, in milliseconds */
+    const u32 mf_timeout_ms = 500;
+    u8 next_state;
+
+    next_state = ts->mf_state;
+    if (ts->mf_mode == MF_OFF) {
+        next_state = MF_UNFILTERED;
+    } else if (ts->mf_mode == MF_DYNAMIC) {
+        /* Determine the next filter state. The motion filter is enabled by
+        * default and it is disabled while a single finger is touching the
+        * screen. If another finger is touched down or if a timeout expires,
+        * the motion filter is reenabled and remains enabled until all fingers
+        * are lifted.
+        */
+        next_state = ts->mf_state;
+        switch (ts->mf_state) {
+        case MF_FILTERED:
+            if (touches == 1) {
+                next_state = MF_UNFILTERED;
+                ts->mf_downtime = ktime_get();
+            }
+            break;
+        case MF_UNFILTERED:
+            if (touches == 0) {
+                next_state = MF_FILTERED;
+            } else if (touches > 1 || ktime_after(ktime_get(),
+                ktime_add_ms(ts->mf_downtime, mf_timeout_ms))) {
+                next_state = MF_FILTERED_LOCKED;
+            }
+            break;
+        case MF_FILTERED_LOCKED:
+            if (touches == 0)
+                next_state = MF_FILTERED;
+            break;
+        }
+    } else if (ts->mf_mode == MF_ON) {
+        next_state = MF_FILTERED;
+    } else {
+        /* Set MF_DYNAMIC as default when an invalid value is found. */
+        ts->mf_mode = MF_DYNAMIC;
+        return;
+    }
+
+    /* Send command to update firmware continuous report */
+    if ((next_state == MF_UNFILTERED) !=
+        (ts->mf_state == MF_UNFILTERED)) {
+        bool en = (next_state == MF_UNFILTERED) ? true : false;
+        fts_set_continuous_mode(ts, en);
+    }
+    ts->mf_state = next_state;
+}
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+static void fts_show_heatmap_data(struct fts_ts_data *ts_data) {
+    int i;
+    int idx_buff;
+    u8 tx = ts_data->pdata->tx_ch_num;
+    u8 rx = ts_data->pdata->rx_ch_num;
+    FTS_DEBUG("Show mutual data:\n");
+
+    idx_buff = 0;
+    for (i = 0; i < rx; i++) {
+        FTS_DEBUG("RX(%d):%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
+            idx_buff,
+            (s16)ts_data->heatmap_buff[idx_buff],
+            (s16)ts_data->heatmap_buff[idx_buff + 1],
+            (s16)ts_data->heatmap_buff[idx_buff + 2],
+            (s16)ts_data->heatmap_buff[idx_buff + 3],
+            (s16)ts_data->heatmap_buff[idx_buff + 4],
+            (s16)ts_data->heatmap_buff[idx_buff + 5],
+            (s16)ts_data->heatmap_buff[idx_buff + 6],
+            (s16)ts_data->heatmap_buff[idx_buff + 7],
+            (s16)ts_data->heatmap_buff[idx_buff + 8],
+            (s16)ts_data->heatmap_buff[idx_buff + 9],
+            (s16)ts_data->heatmap_buff[idx_buff + 10],
+            (s16)ts_data->heatmap_buff[idx_buff + 11],
+            (s16)ts_data->heatmap_buff[idx_buff + 12],
+            (s16)ts_data->heatmap_buff[idx_buff + 13],
+            (s16)ts_data->heatmap_buff[idx_buff + 14],
+            (s16)ts_data->heatmap_buff[idx_buff + 15]);
+            idx_buff += tx;
+    }
+
+    FTS_DEBUG("Show Tx self data:\n");
+    FTS_DEBUG("Tx(idx_buff=%d):%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
+        idx_buff,
+        (s16)ts_data->heatmap_buff[idx_buff],
+        (s16)ts_data->heatmap_buff[idx_buff + 1],
+        (s16)ts_data->heatmap_buff[idx_buff + 2],
+        (s16)ts_data->heatmap_buff[idx_buff + 3],
+        (s16)ts_data->heatmap_buff[idx_buff + 4],
+        (s16)ts_data->heatmap_buff[idx_buff + 5],
+        (s16)ts_data->heatmap_buff[idx_buff + 6],
+        (s16)ts_data->heatmap_buff[idx_buff + 7],
+        (s16)ts_data->heatmap_buff[idx_buff + 8],
+        (s16)ts_data->heatmap_buff[idx_buff + 9],
+        (s16)ts_data->heatmap_buff[idx_buff + 10],
+        (s16)ts_data->heatmap_buff[idx_buff + 11],
+        (s16)ts_data->heatmap_buff[idx_buff + 12],
+        (s16)ts_data->heatmap_buff[idx_buff + 13],
+        (s16)ts_data->heatmap_buff[idx_buff + 14],
+        (s16)ts_data->heatmap_buff[idx_buff + 15]);
+    idx_buff += 16;
+
+    FTS_DEBUG("Show Rx self data:\n");
+    FTS_DEBUG("Rx(idx_buff=%d)%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
+        idx_buff,
+        (short)ts_data->heatmap_buff[idx_buff],
+        (short)ts_data->heatmap_buff[idx_buff + 1],
+        (short)ts_data->heatmap_buff[idx_buff + 2],
+        (short)ts_data->heatmap_buff[idx_buff + 3],
+        (short)ts_data->heatmap_buff[idx_buff + 4],
+        (short)ts_data->heatmap_buff[idx_buff + 5],
+        (short)ts_data->heatmap_buff[idx_buff + 6],
+        (short)ts_data->heatmap_buff[idx_buff + 7],
+        (short)ts_data->heatmap_buff[idx_buff + 8],
+        (short)ts_data->heatmap_buff[idx_buff + 9],
+        (short)ts_data->heatmap_buff[idx_buff + 10],
+        (short)ts_data->heatmap_buff[idx_buff + 11],
+        (short)ts_data->heatmap_buff[idx_buff + 12],
+        (short)ts_data->heatmap_buff[idx_buff + 13],
+        (short)ts_data->heatmap_buff[idx_buff + 14],
+        (short)ts_data->heatmap_buff[idx_buff + 15]);
+    idx_buff += 16;
+
+    FTS_DEBUG("Rx(idx_buff=%d)%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
+        idx_buff,
+        (short)ts_data->heatmap_buff[idx_buff],
+        (short)ts_data->heatmap_buff[idx_buff + 1],
+        (short)ts_data->heatmap_buff[idx_buff + 2],
+        (short)ts_data->heatmap_buff[idx_buff + 3],
+        (short)ts_data->heatmap_buff[idx_buff + 4],
+        (short)ts_data->heatmap_buff[idx_buff + 5],
+        (short)ts_data->heatmap_buff[idx_buff + 6],
+        (short)ts_data->heatmap_buff[idx_buff + 7],
+        (short)ts_data->heatmap_buff[idx_buff + 8],
+        (short)ts_data->heatmap_buff[idx_buff + 9],
+        (short)ts_data->heatmap_buff[idx_buff + 10],
+        (short)ts_data->heatmap_buff[idx_buff + 11],
+        (short)ts_data->heatmap_buff[idx_buff + 12],
+        (short)ts_data->heatmap_buff[idx_buff + 13],
+        (short)ts_data->heatmap_buff[idx_buff + 14],
+        (short)ts_data->heatmap_buff[idx_buff + 15]);
+    idx_buff += 16;
+
+    FTS_DEBUG("Rx(idx_buff=%d)%5d %5d", idx_buff,
+        (short)ts_data->heatmap_buff[idx_buff],
+        (short)ts_data->heatmap_buff[idx_buff + 1]);
+    idx_buff += 2;
+    FTS_DEBUG("Print done, idx_buff=%d", idx_buff);
+}
+#endif /* GOOGLE_HEATMAP_DEBUG */
+
+static int fts_ptflib_decoder(struct fts_ts_data *ts_data, const u16 *in_array,
+    const int in_array_size, u16 *out_array, const int out_array_max_size)
+{
+    const u16 ESCAPE_MASK = 0xF000;
+    const u16 ESCAPE_BIT = 0x8000;
+
+    int i;
+    int j;
+    int out_array_size = 0;
+    u16 prev_word = 0;
+    u16 repetition = 0;
+    u16 *temp_out_array = out_array;
+
+    for (i = 0; i < in_array_size; i++) {
+        /* The data form firmware is big-endian, and needs to transfer it to
+         * little-endian.
+         */
+        u16 curr_word = (u16)(*((u8*)&in_array[i]) << 8) +
+                        *((u8*)&in_array[i] + 1);
+        if ((curr_word & ESCAPE_MASK) == ESCAPE_BIT) {
+            repetition = (curr_word & ~ESCAPE_MASK);
+            if (out_array_size + repetition > out_array_max_size)
+                break;
+            for (j = 0; j < repetition; j++) {
+                *temp_out_array++ = prev_word;
+                out_array_size++;
+            }
+        } else {
+            if (out_array_size >= out_array_max_size)
+                break;
+            *temp_out_array++ = curr_word;
+            out_array_size++;
+            prev_word = curr_word;
+        }
+    }
+
+    if (i != in_array_size || out_array_size != out_array_max_size) {
+        FTS_ERROR("%d (in=%d, out=%d, rep=%d, out_max=%d).\n",
+            i, in_array_size, out_array_size,
+            repetition, out_array_max_size);
+        memset(out_array, 0, out_array_max_size * sizeof(u16));
+        return -1;
+    }
+
+    return out_array_size;
+}
+
+extern void transpose_raw(u8 *src, u8 *dist, int tx, int rx, bool big_endian);
+static int fts_get_heatmap(struct fts_ts_data *ts_data) {
+    int ret = 0;
+    int i;
+    int idx_buff = 0;
+    int node_num = 0;
+    int self_node = 0;
+    int mutual_data_size = 0;
+    int self_data_size = 0;
+    int total_data_size = 0;
+    u8 cmd[1] = {0};
+    u8 tx = ts_data->pdata->tx_ch_num;
+    u8 rx = ts_data->pdata->rx_ch_num;
+    int idx_ms_raw = 0;
+    int idx_ss_tx_raw = 0;
+    int idx_ss_rx_raw = 0;
+    int idx_water_ss_tx_raw = 0;
+    int idx_water_ss_rx_raw = 0;
+
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_FUNC_ENTER();
+#endif
+
+    node_num = tx * rx;
+    self_node = tx + rx;
+    /* The mutual sensing raw data size : 16*34*2=1088 */
+    mutual_data_size = node_num * sizeof(u16);
+    /* The self sensing raw data size : 68*2=136 */
+    self_data_size = FTS_SELF_DATA_LEN * sizeof(u16);
+    /* The index of mutual sensing data : 91+(68*2)*2=363 */
+    idx_ms_raw = FTS_CAP_DATA_LEN + self_data_size * 2;
+    /* The tx index of water self sensing data : 91+34*2=159 */
+    idx_water_ss_tx_raw = FTS_CAP_DATA_LEN + rx * sizeof(u16);
+    /* The rx index of water self sensing data : 91 */
+    idx_water_ss_rx_raw = FTS_CAP_DATA_LEN;
+    /* The tx index of normal self sensing data : 91+68*2+34*2=295 */
+    idx_ss_tx_raw = FTS_CAP_DATA_LEN + self_data_size + rx * sizeof(u16);
+    /* The rx index of normal self sensing data : 91+68*2=227 */
+    idx_ss_rx_raw = FTS_CAP_DATA_LEN + self_data_size;
+
+    if (!ts_data->heatmap_buff) {
+        FTS_ERROR("The heatmap_buff is not allocated!!");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    if (!ts_data->fw_heatmap_mode) {
+        FTS_ERROR("The firmware heatmap is not enabled!!");
+        ret = -EINVAL;
+        goto exit;
+    }
+
+    cmd[0] = FTS_CMD_READ_TOUCH_DATA;
+    if (ts_data->fw_heatmap_mode == FW_HEATMAP_MODE_UNCOMPRESSED) {
+        /* The format of uncompressed heatmap from touch chip.
+         *
+         * |- cap header (91) -|- Water-SS -|- Normal-SS -|- Normal-MS -|
+         * |-        91       -|-   68*2   -|-   68*2    -|-  16*34*2  -|
+         */
+
+        /* Total touch data: (cap header(91) + heatmap(N-MS + W-SS + N-SS)). */
+        total_data_size = FTS_CAP_DATA_LEN + self_data_size * 2 +
+                          mutual_data_size;
+
+        if (total_data_size > ts_data->heatmap_raw_size) {
+            FTS_DEBUG("Warning : The total touch data size is %d!!",
+                total_data_size);
+            total_data_size = ts_data->heatmap_raw_size;
+        }
+
+        ret = fts_read(cmd, 1, ts_data->heatmap_raw, total_data_size);
+        if (ret < 0) {
+            FTS_ERROR("Failed to get heatmap raw data, ret=%d.", ret);
+            ret = -EIO;
+            goto exit;
+        }
+
+        /* Get the self-sensing type. */
+        ts_data->self_sensing_type =
+            ts_data->heatmap_raw[FTS_CAP_DATA_LEN - 1] & 0x80;
+
+        /*
+         * transform the order of MS from RX->TX, the output data is keep
+         * big-endian.
+         */
+        transpose_raw(ts_data->heatmap_raw + idx_ms_raw, ts_data->trans_raw,
+            tx, rx, true);
+    } else {
+        /* The format of compressed heatmap from touch chip.
+         *
+         * |- cap header -|- Water-SS -|- Normal-SS -|- compressed heatmap(MS)-|
+         * |-     91     -|-   68*2   -|-   68*2    -|- (B2[1]<<8+B2[2])*2    -|
+         */
+
+        if (ts_data->compress_heatmap_wlen < 0 ||
+            (ts_data->compress_heatmap_wlen * sizeof(u16)) > mutual_data_size) {
+            FTS_DEBUG("Warning : The compressed heatmap size is %d!!",
+                ts_data->compress_heatmap_wlen);
+            ts_data->compress_heatmap_wlen = 0;
+            memset(ts_data->trans_raw, 0, ts_data->trans_raw_size);
+        }
+
+        /* Total touch data:(cap header + W-SS + N-SS + compressed heatmap(N-MS)
+         */
+        total_data_size = FTS_CAP_DATA_LEN +
+                          self_data_size * 2 +
+                          ts_data->compress_heatmap_wlen * sizeof(u16);
+
+        if (total_data_size > ts_data->heatmap_raw_size) {
+            FTS_DEBUG("Warning : The total touch data size is %d!!",
+                total_data_size);
+            total_data_size = ts_data->heatmap_raw_size;
+        }
+
+        ret = fts_read(cmd, 1, ts_data->heatmap_raw, total_data_size);
+        if (ret < 0) {
+            FTS_ERROR("Failed to get compressed heatmap raw data,ret=%d.", ret);
+            ret = -EIO;
+            goto exit;
+        }
+
+        /* Get the self-sensing type. */
+        ts_data->self_sensing_type =
+            ts_data->heatmap_raw[FTS_CAP_DATA_LEN - 1] & 0x80;
+
+        if (ts_data->compress_heatmap_wlen > 0) {
+            /* decode the compressed data from heatmap_raw to heatmap_buff. */
+            fts_ptflib_decoder(ts_data,
+                (u16*)(&ts_data->heatmap_raw[idx_ms_raw]),
+                ts_data->compress_heatmap_wlen,
+                ts_data->heatmap_buff,
+                mutual_data_size / sizeof(u16));
+
+            /* MS: Transform the order from RX->TX. */
+            /* After decoding, the data become to little-endian, but the output of
+             * transpose_raw is big-endian.
+             */
+            transpose_raw(&((u8*)ts_data->heatmap_buff)[0], ts_data->trans_raw,
+                tx, rx, false);
+        }
+    }
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_DEBUG("Copy matual data,idx_buff=%d,idx_ms_raw=%d.",
+        idx_buff, idx_ms_raw);
+#endif
+
+    /* copy mutual sensing data. */
+    for (i = 0; i < node_num; i++) {
+        ((u16*)ts_data->heatmap_buff)[idx_buff++] =
+            (u16)(ts_data->trans_raw[(i * 2)] << 8) +
+            ts_data->trans_raw[(i * 2) + 1];
+    }
+
+    /* copy tx of Normal-SS. */
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_DEBUG("Copy the tx self data,idx_buff=%d,idx_ss_tx_raw=%d.",
+        idx_buff, idx_ss_tx_raw);
+#endif
+    for (i = 0 ; i < tx; i++) {
+        ((u16*)ts_data->heatmap_buff)[idx_buff++] =
+            (u16)(ts_data->heatmap_raw[idx_ss_tx_raw + (i * 2)] << 8) +
+            ts_data->heatmap_raw[idx_ss_tx_raw +(i * 2) + 1];
+    }
+
+    /* copy rx of Normal-SS. */
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_DEBUG("Copy the rx self data,idx_buff=%d,idx_ss_rx_raw=%d.",
+        idx_buff, idx_ss_rx_raw);
+#endif
+    for (i = 0 ; i < rx; i++) {
+        ((u16*)ts_data->heatmap_buff)[idx_buff++] =
+            (u16)(ts_data->heatmap_raw[idx_ss_rx_raw + (i * 2)] << 8) +
+            ts_data->heatmap_raw[idx_ss_rx_raw + (i * 2) + 1];
+    }
+
+    /* copy tx of Water-SS. */
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_DEBUG("Copy the tx of Water-SS,idx_buff=%d,idx_water_ss_tx_raw=%d.",
+        idx_buff, idx_water_ss_tx_raw);
+#endif
+    for (i = 0 ; i < tx; i++) {
+        ((u16*)ts_data->heatmap_buff)[idx_buff++] =
+            (u16)(ts_data->heatmap_raw[idx_water_ss_tx_raw + (i * 2)] << 8) +
+            ts_data->heatmap_raw[idx_water_ss_tx_raw +(i * 2) + 1];
+    }
+
+    /* copy rx of Water-SS. */
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_DEBUG("Copy the rx of Water-SS,idx_buff=%d,idx_water_ss_rx_raw=%d.",
+        idx_buff, idx_water_ss_rx_raw);
+#endif
+    for (i = 0 ; i < rx; i++) {
+        ((u16*)ts_data->heatmap_buff)[idx_buff++] =
+            (u16)(ts_data->heatmap_raw[idx_water_ss_rx_raw + (i * 2)] << 8) +
+            ts_data->heatmap_raw[idx_water_ss_rx_raw + (i * 2) + 1];
+    }
+    /* The format of heatmap data (U16) of heatmap_buff is:
+     *
+     * |-    MS   -|- Normal-SS -|- Water-SS -|
+     * |-  16*34  -|-   16+34   -|-  16+34   -|
+     */
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_FUNC_EXIT();
+#endif
+exit:
+    return ret;
+}
+#endif /* CONFIG_TOUCHSCREEN_OFFLOAD || CONFIG_TOUCHSCREEN_HEATMAP */
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+static void fts_offload_set_running(struct fts_ts_data *ts_data, bool running)
+{
+    ts_data->offload.offload_running = running;
+    /*
+     * Disable firmware grip_suppression/palm_rejection when offload is running
+     * and upper layer grip_suppression/palm_rejection is enabled.
+     */
+    if (running) {
+        if (ts_data->enable_fw_grip < FW_GRIP_FORCE_DISABLE) {
+            int new_fw_grip = ts_data->offload.config.filter_grip ?
+                              FW_GRIP_DISABLE : FW_GRIP_ENABLE;
+            if (ts_data->enable_fw_grip != new_fw_grip) {
+                ts_data->enable_fw_grip = new_fw_grip;
+                fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
+            }
+        }
+
+        if (ts_data->enable_fw_palm < FW_PALM_FORCE_DISABLE) {
+            int new_fw_palm = ts_data->offload.config.filter_palm ?
+                              FW_PALM_DISABLE : FW_PALM_ENABLE;
+            if (ts_data->enable_fw_palm != new_fw_palm) {
+                ts_data->enable_fw_palm = new_fw_palm;
+                fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
+            }
+        }
+    } else {
+        if (ts_data->enable_fw_grip < FW_GRIP_FORCE_DISABLE &&
+            ts_data->enable_fw_grip != FW_GRIP_ENABLE) {
+            ts_data->enable_fw_grip = FW_GRIP_ENABLE;
+            fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
+        }
+
+        if (ts_data->enable_fw_palm < FW_PALM_FORCE_DISABLE &&
+            ts_data->enable_fw_palm != FW_PALM_ENABLE) {
+            ts_data->enable_fw_palm = FW_PALM_ENABLE;
+            fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
+        }
+    }
+}
+
+static void fts_offload_report(void *handle,
+    struct TouchOffloadIocReport *report)
+{
+    struct fts_ts_data *ts_data = (struct fts_ts_data *)handle;
+    bool touch_down = 0;
+    int i;
+    int touch_count = 0;
+    int tool_type;
+
+    mutex_lock(&ts_data->report_mutex);
+
+    input_set_timestamp(ts_data->input_dev, report->timestamp);
+
+    for (i = 0; i < MAX_COORDS; i++) {
+        if (report->coords[i].status != COORD_STATUS_INACTIVE) {
+            input_mt_slot(ts_data->input_dev, i);
+            touch_count++;
+            touch_down = 1;
+            input_report_key(ts_data->input_dev, BTN_TOUCH, touch_down);
+            input_report_key(ts_data->input_dev, BTN_TOOL_FINGER, touch_down);
+            switch (report->coords[i].status) {
+            case COORD_STATUS_EDGE:
+            case COORD_STATUS_PALM:
+            case COORD_STATUS_CANCEL:
+                tool_type = MT_TOOL_PALM;
+                break;
+            case COORD_STATUS_FINGER:
+            default:
+                tool_type = MT_TOOL_FINGER;
+                break;
+            }
+            input_mt_report_slot_state(ts_data->input_dev, tool_type, 1);
+            input_report_abs(ts_data->input_dev, ABS_MT_POSITION_X,
+                report->coords[i].x);
+            input_report_abs(ts_data->input_dev, ABS_MT_POSITION_Y,
+                report->coords[i].y);
+            input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR,
+                report->coords[i].major);
+            input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MINOR,
+                report->coords[i].minor);
+            input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE,
+                report->coords[i].pressure);
+            input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION,
+                report->coords[i].rotation);
+        } else {
+            input_mt_slot(ts_data->input_dev, i);
+            input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 0);
+            input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 0);
+            input_report_abs(ts_data->input_dev, ABS_MT_TRACKING_ID, -1);
+            input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION, 0);
+        }
+    }
+    input_report_key(ts_data->input_dev, BTN_TOUCH, touch_down);
+    input_report_key(ts_data->input_dev, BTN_TOOL_FINGER, touch_down);
+
+    input_sync(ts_data->input_dev);
+    fts_update_motion_filter(ts_data, touch_count);
+
+    mutex_unlock(&ts_data->report_mutex);
+}
+
+static void fts_populate_coordinate_channel(struct fts_ts_data *ts_data,
+    struct touch_offload_frame *frame,
+    int channel)
+{
+    int i;
+    u8 active_coords = 0;
+
+    struct TouchOffloadDataCoord *dc =
+        (struct TouchOffloadDataCoord *)frame->channel_data[channel];
+    memset(dc, 0, frame->channel_data_size[channel]);
+    dc->header.channel_type = TOUCH_DATA_TYPE_COORD;
+    dc->header.channel_size = TOUCH_OFFLOAD_FRAME_SIZE_COORD;
+
+    for (i = 0; i < MAX_COORDS; i++) {
+        dc->coords[i].x = ts_data->offload.coords[i].x;
+        dc->coords[i].y = ts_data->offload.coords[i].y;
+        dc->coords[i].major = ts_data->offload.coords[i].major;
+        dc->coords[i].minor = ts_data->offload.coords[i].minor;
+        dc->coords[i].pressure = ts_data->offload.coords[i].pressure;
+        dc->coords[i].rotation = ts_data->offload.coords[i].rotation;
+        dc->coords[i].status = ts_data->offload.coords[i].status;
+        if (dc->coords[i].status != COORD_STATUS_INACTIVE)
+            active_coords += 1;
+    }
+    ts_data->touch_offload_active_coords = active_coords;
+}
+
+static void fts_populate_mutual_channel(struct fts_ts_data *ts_data,
+    struct touch_offload_frame *frame, int channel)
+{
+    struct TouchOffloadData2d *mutual_strength =
+        (struct TouchOffloadData2d *)frame->channel_data[channel];
+
+    mutual_strength->tx_size = ts_data->pdata->tx_ch_num;
+    mutual_strength->rx_size = ts_data->pdata->rx_ch_num;
+    mutual_strength->header.channel_type = frame->channel_type[channel];
+    mutual_strength->header.channel_size =
+        TOUCH_OFFLOAD_FRAME_SIZE_2D(mutual_strength->rx_size,
+            mutual_strength->tx_size);
+
+    memcpy(mutual_strength->data, ts_data->heatmap_buff,
+        mutual_strength->tx_size * mutual_strength->rx_size * sizeof(u16));
+}
+
+static void fts_populate_self_channel(struct fts_ts_data *ts_data,
+    struct touch_offload_frame *frame, int channel)
+{
+    u8 ss_type = 0;
+    int idx_ss_normal = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num;
+    int idx_ss_water = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num +
+        ts_data->pdata->tx_ch_num + ts_data->pdata->rx_ch_num;
+    int ss_size =
+        (ts_data->pdata->tx_ch_num + ts_data->pdata->rx_ch_num) * sizeof(u16);
+    struct TouchOffloadData1d *self_strength =
+        (struct TouchOffloadData1d *)frame->channel_data[channel];
+
+    self_strength->tx_size = ts_data->pdata->tx_ch_num;
+    self_strength->rx_size = ts_data->pdata->rx_ch_num;
+    self_strength->header.channel_type = frame->channel_type[channel];
+    self_strength->header.channel_size =
+        TOUCH_OFFLOAD_FRAME_SIZE_1D(self_strength->rx_size,
+            self_strength->tx_size);
+
+    switch (frame->channel_type[channel] & ~TOUCH_SCAN_TYPE_SELF) {
+    case TOUCH_DATA_TYPE_FILTERED:
+        ss_type = SS_WATER;
+        break;
+    case TOUCH_DATA_TYPE_STRENGTH:
+    default:
+        ss_type = SS_NORMAL;
+        break;
+    }
+
+    if (ss_type == SS_WATER) {
+        /* Copy Water-SS. */
+        memcpy(self_strength->data, ts_data->heatmap_buff + idx_ss_water,
+            ss_size);
+    } else {
+        /* Copy Normal-SS. */
+        memcpy(self_strength->data, ts_data->heatmap_buff + idx_ss_normal,
+            ss_size);
+    }
+}
+
+static void fts_populate_frame(struct fts_ts_data *ts_data, int populate_channel_types)
+{
+    static u64 index;
+    int i;
+    struct touch_offload_frame *frame = ts_data->reserved_frame;
+
+    frame->header.index = index++;
+    frame->header.timestamp = ts_data->coords_timestamp;
+
+    /* Populate all channels */
+    for (i = 0; i < frame->num_channels; i++) {
+        if ((frame->channel_type[i] & populate_channel_types) == TOUCH_DATA_TYPE_COORD) {
+            fts_populate_coordinate_channel(ts_data, frame, i);
+        } else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_MUTUAL &
+                    populate_channel_types) != 0) {
+            fts_populate_mutual_channel(ts_data, frame, i);
+        } else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_SELF &
+                    populate_channel_types) != 0) {
+            fts_populate_self_channel(ts_data, frame, i);
+        }
+    }
+}
+
+static void fts_offload_push_coord_frame(struct fts_ts_data *ts)
+{
+    int error;
+
+    FTS_INFO("active coords %u.", ts->touch_offload_active_coords);
+
+    error = touch_offload_reserve_frame(&ts->offload, &ts->reserved_frame);
+    if (error != 0) {
+        FTS_DEBUG("Could not reserve a frame: error=%d.\n", error);
+    } else {
+        fts_populate_frame(ts, TOUCH_DATA_TYPE_COORD);
+
+        error = touch_offload_queue_frame(&ts->offload, ts->reserved_frame);
+        if (error != 0) {
+            FTS_ERROR("Failed to queue reserved frame: error=%d.\n", error);
+        }
+    }
+}
+#endif /* CONFIG_TOUCHSCREEN_OFFLOAD */
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+static bool v4l2_read_frame(struct v4l2_heatmap *v4l2)
+{
+    bool ret = true;
+
+    struct fts_ts_data *ts_data =
+        container_of(v4l2, struct fts_ts_data, v4l2);
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+    FTS_FUNC_ENTER();
+#endif
+    if (ts_data->v4l2.width == ts_data->pdata->tx_ch_num &&
+        ts_data->v4l2.height == ts_data->pdata->rx_ch_num) {
+#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
+        FTS_DEBUG("v4l2 mutual strength data is ready.");
+#endif
+        memcpy(v4l2->frame, ts_data->heatmap_buff,
+            ts_data->v4l2.width * ts_data->v4l2.height * sizeof(u16));
+    } else {
+        FTS_ERROR("size mismatched, (%lu, %lu) vs (%u, %u)!\n",
+        ts_data->v4l2.width, ts_data->v4l2.height,
+        ts_data->pdata->tx_ch_num, ts_data->pdata->rx_ch_num);
+        ret = false;
+    }
+
+    return ret;
+}
+#endif /* CONFIG_TOUCHSCREEN_HEATMAP */
+
+static irqreturn_t fts_irq_ts(int irq, void *data)
+{
+    struct fts_ts_data *ts_data = data;
+
+    ts_data->isr_timestamp = ktime_get();
+    return IRQ_WAKE_THREAD;
+}
+
+extern int int_test_has_interrupt;
+static irqreturn_t fts_irq_handler(int irq, void *data)
+{
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    struct fts_ts_data *ts_data = fts_data;
+    if (fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_IRQ, true) < 0) {
+        if (!ts_data->gesture_mode) {
+            /* Interrupt during bus suspend */
+            FTS_INFO("Skipping stray interrupt since bus is suspended(power_status: %d)\n",
+                ts_data->power_status);
+            return IRQ_HANDLED;
+        }
+    }
+#endif
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if ((ts_data->suspended) && (ts_data->pm_suspend)) {
+        ret = wait_for_completion_timeout(
+                  &ts_data->pm_completion,
+                  msecs_to_jiffies(FTS_TIMEOUT_COMERR_PM));
+        if (!ret) {
+            FTS_ERROR("Bus don't resume from pm(deep),timeout,skip irq");
+            return IRQ_HANDLED;
+        }
+    }
+#endif
+    int_test_has_interrupt++;
+    fts_data->coords_timestamp = fts_data->isr_timestamp;
+    cpu_latency_qos_update_request(&ts_data->pm_qos_req, 100 /* usec */);
+    fts_irq_read_report();
+    cpu_latency_qos_update_request(&ts_data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_IRQ, false);
+#endif
+    return IRQ_HANDLED;
+}
+
+static int fts_irq_registration(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+
+    ts_data->irq = gpio_to_irq(pdata->irq_gpio);
+    pdata->irq_gpio_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+    FTS_INFO("irq:%d, flag:%x", ts_data->irq, pdata->irq_gpio_flags);
+    ret = request_threaded_irq(ts_data->irq, fts_irq_ts, fts_irq_handler,
+                               pdata->irq_gpio_flags,
+                               FTS_DRIVER_NAME, ts_data);
+
+    return ret;
+}
+
+#if FTS_PEN_EN
+static int fts_input_pen_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    struct input_dev *pen_dev;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+
+    FTS_FUNC_ENTER();
+    pen_dev = input_allocate_device();
+    if (!pen_dev) {
+        FTS_ERROR("Failed to allocate memory for input_pen device");
+        return -ENOMEM;
+    }
+
+    pen_dev->dev.parent = ts_data->dev;
+    pen_dev->name = FTS_DRIVER_PEN_NAME;
+    pen_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+    __set_bit(ABS_X, pen_dev->absbit);
+    __set_bit(ABS_Y, pen_dev->absbit);
+    __set_bit(BTN_STYLUS, pen_dev->keybit);
+    __set_bit(BTN_STYLUS2, pen_dev->keybit);
+    __set_bit(BTN_TOUCH, pen_dev->keybit);
+    __set_bit(BTN_TOOL_PEN, pen_dev->keybit);
+    __set_bit(INPUT_PROP_DIRECT, pen_dev->propbit);
+    input_set_abs_params(pen_dev, ABS_X, pdata->x_min, pdata->x_max, 0, 0);
+    input_set_abs_params(pen_dev, ABS_Y, pdata->y_min, pdata->y_max, 0, 0);
+    input_set_abs_params(pen_dev, ABS_PRESSURE, 0, 4096, 0, 0);
+
+    ret = input_register_device(pen_dev);
+    if (ret) {
+        FTS_ERROR("Input device registration failed");
+        input_free_device(pen_dev);
+        pen_dev = NULL;
+        return ret;
+    }
+
+    ts_data->pen_dev = pen_dev;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+#endif
+
+static int fts_input_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int key_num = 0;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+    struct input_dev *input_dev;
+
+    FTS_FUNC_ENTER();
+    input_dev = input_allocate_device();
+    if (!input_dev) {
+        FTS_ERROR("Failed to allocate memory for input device");
+        return -ENOMEM;
+    }
+
+    /* Init and register Input device */
+    input_dev->name = FTS_DRIVER_NAME;
+    if (ts_data->bus_type == FTS_BUS_TYPE_I2C)
+        input_dev->id.bustype = BUS_I2C;
+    else
+        input_dev->id.bustype = BUS_SPI;
+    input_dev->dev.parent = ts_data->dev;
+
+    input_dev->uniq = "google_touchscreen";
+
+    input_set_drvdata(input_dev, ts_data);
+
+    __set_bit(EV_SYN, input_dev->evbit);
+    __set_bit(EV_ABS, input_dev->evbit);
+    __set_bit(EV_KEY, input_dev->evbit);
+    __set_bit(BTN_TOUCH, input_dev->keybit);
+    __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+
+    if (pdata->have_key) {
+        FTS_INFO("set key capabilities");
+        for (key_num = 0; key_num < pdata->key_number; key_num++)
+            input_set_capability(input_dev, EV_KEY, pdata->keys[key_num]);
+    }
+
+#if FTS_MT_PROTOCOL_B_EN
+    input_mt_init_slots(input_dev, pdata->max_touch_number, INPUT_MT_DIRECT);
+#else
+    input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 0x0F, 0, 0);
+#endif
+    input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, pdata->x_max, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, pdata->y_max, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 0x3F, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 0x3F, 0, 0);
+#if FTS_REPORT_PRESSURE_EN
+    input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 0xFF, 0, 0);
+#endif
+    /* Units are (-4096, 4096), representing the range between rotation
+     * 90 degrees to left and 90 degrees to the right.
+     */
+    input_set_abs_params(input_dev, ABS_MT_ORIENTATION, -4096, 4096, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER, MT_TOOL_PALM, 0, 0);
+    ret = input_register_device(input_dev);
+    if (ret) {
+        FTS_ERROR("Input device registration failed");
+        input_set_drvdata(input_dev, NULL);
+        input_free_device(input_dev);
+        input_dev = NULL;
+        return ret;
+    }
+
+#if FTS_PEN_EN
+    ret = fts_input_pen_init(ts_data);
+    if (ret) {
+        FTS_ERROR("Input-pen device registration failed");
+        input_set_drvdata(input_dev, NULL);
+        input_free_device(input_dev);
+        input_dev = NULL;
+        return ret;
+    }
+#endif
+
+    ts_data->input_dev = input_dev;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static int fts_report_buffer_init(struct fts_ts_data *ts_data)
+{
+    int point_num = 0;
+    int events_num = 0;
+
+    point_num = FTS_MAX_POINTS_SUPPORT;
+    ts_data->pnt_buf_size = FTS_CAP_DATA_LEN;
+    ts_data->point_buf = (u8 *)kzalloc(ts_data->pnt_buf_size + 1, GFP_KERNEL);
+    if (!ts_data->point_buf) {
+        FTS_ERROR("failed to alloc memory for point buf");
+        return -ENOMEM;
+    }
+
+    events_num = point_num * sizeof(struct ts_event);
+    ts_data->events = (struct ts_event *)kzalloc(events_num, GFP_KERNEL);
+    if (!ts_data->events) {
+        FTS_ERROR("failed to alloc memory for point events");
+        kfree_safe(ts_data->point_buf);
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+#if FTS_POWER_SOURCE_CUST_EN
+/*****************************************************************************
+* Power Control
+*****************************************************************************/
+#if FTS_PINCTRL_EN
+static int fts_pinctrl_init(struct fts_ts_data *ts)
+{
+    int ret = 0;
+
+    ts->pinctrl = devm_pinctrl_get(ts->dev);
+    if (IS_ERR_OR_NULL(ts->pinctrl)) {
+        FTS_ERROR("Failed to get pinctrl, please check dts");
+        ret = PTR_ERR(ts->pinctrl);
+        goto err_pinctrl_get;
+    }
+
+    ts->pins_active = pinctrl_lookup_state(ts->pinctrl, "ts_active");
+    if (IS_ERR_OR_NULL(ts->pins_active)) {
+        FTS_ERROR("Pin state[active] not found");
+        ret = PTR_ERR(ts->pins_active);
+        goto err_pinctrl_lookup;
+    }
+
+    ts->pins_suspend = pinctrl_lookup_state(ts->pinctrl, "ts_suspend");
+    if (IS_ERR_OR_NULL(ts->pins_suspend)) {
+        FTS_ERROR("Pin state[suspend] not found");
+        ret = PTR_ERR(ts->pins_suspend);
+        goto err_pinctrl_lookup;
+    }
+
+    return 0;
+err_pinctrl_lookup:
+    if (ts->pinctrl) {
+        devm_pinctrl_put(ts->pinctrl);
+    }
+err_pinctrl_get:
+    ts->pinctrl = NULL;
+    ts->pins_suspend = NULL;
+    ts->pins_active = NULL;
+    return ret;
+}
+
+static int fts_pinctrl_select_normal(struct fts_ts_data *ts)
+{
+    int ret = 0;
+    FTS_DEBUG("Pins control select normal");
+    if (ts->pinctrl && ts->pins_active) {
+        ret = pinctrl_select_state(ts->pinctrl, ts->pins_active);
+        if (ret < 0) {
+            FTS_ERROR("Set normal pin state error:%d", ret);
+        }
+    }
+
+    return ret;
+}
+
+static int fts_pinctrl_select_suspend(struct fts_ts_data *ts)
+{
+    int ret = 0;
+    FTS_DEBUG("Pins control select suspend");
+    if (ts->pinctrl && ts->pins_suspend) {
+        ret = pinctrl_select_state(ts->pinctrl, ts->pins_suspend);
+        if (ret < 0) {
+            FTS_ERROR("Set suspend pin state error:%d", ret);
+        }
+    }
+
+    return ret;
+}
+#endif /* FTS_PINCTRL_EN */
+
+static int fts_power_source_ctrl(struct fts_ts_data *ts_data, int enable)
+{
+    int ret = 0;
+
+    if (IS_ERR_OR_NULL(ts_data->avdd)) {
+        FTS_ERROR("avdd is invalid");
+        return -EINVAL;
+    }
+
+    FTS_FUNC_ENTER();
+    if (enable) {
+        if (ts_data->power_disabled) {
+            FTS_DEBUG("regulator enable !");
+            ret = regulator_enable(ts_data->avdd);
+            if (ret) {
+                FTS_ERROR("enable avdd regulator failed,ret=%d", ret);
+            }
+
+            if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
+                ret = regulator_enable(ts_data->dvdd);
+                if (ret) {
+                    FTS_ERROR("enable dvdd regulator failed,ret=%d", ret);
+                }
+            }
+            /* sleep 1 ms to power on avdd/dvdd to match spec. */
+            msleep(1);
+            gpio_direction_output(ts_data->pdata->reset_gpio, 1);
+            ts_data->power_disabled = false;
+        }
+    } else {
+        if (!ts_data->power_disabled) {
+            FTS_DEBUG("regulator disable !");
+            gpio_direction_output(ts_data->pdata->reset_gpio, 0);
+            /* sleep 1 ms to power off avdd/dvdd to match spec. */
+            msleep(1);
+            ret = regulator_disable(ts_data->avdd);
+            if (ret) {
+                FTS_ERROR("disable avdd regulator failed,ret=%d", ret);
+            }
+            if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
+                ret = regulator_disable(ts_data->dvdd);
+                if (ret) {
+                    FTS_ERROR("disable dvdd regulator failed,ret=%d", ret);
+                }
+            }
+            ts_data->power_disabled = true;
+        }
+    }
+
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+/*****************************************************************************
+* Name: fts_power_source_init
+* Brief: Init regulator power:avdd/dvdd(if have), generally, no dvdd
+*        avdd---->avdd-supply in dts, kernel will auto add "-supply" to parse
+*        Must be call after fts_gpio_configure() execute,because this function
+*        will operate reset-gpio which request gpio in fts_gpio_configure()
+* Input:
+* Output:
+* Return: return 0 if init power successfully, otherwise return error code
+*****************************************************************************/
+static int fts_power_source_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+
+#if FTS_PINCTRL_EN
+    fts_pinctrl_init(ts_data);
+    fts_pinctrl_select_normal(ts_data);
+#endif
+
+    if (of_property_read_bool(ts_data->dev->of_node, "avdd-supply")) {
+        ts_data->avdd = regulator_get(ts_data->dev, "avdd");
+        if (IS_ERR_OR_NULL(ts_data->avdd)) {
+            ret = PTR_ERR(ts_data->avdd);
+            ts_data->avdd = NULL;
+            FTS_ERROR("get avdd regulator failed,ret=%d", ret);
+            return ret;
+        }
+    } else {
+        FTS_ERROR("avdd-supply not found!");
+    }
+
+    if (of_property_read_bool(ts_data->dev->of_node, "vdd-supply")) {
+        ts_data->dvdd = regulator_get(ts_data->dev, "vdd");
+
+        if (IS_ERR_OR_NULL(ts_data->dvdd)) {
+            ret = PTR_ERR(ts_data->dvdd);
+            ts_data->dvdd = NULL;
+            FTS_ERROR("get dvdd regulator failed,ret=%d", ret);
+        }
+    } else {
+        FTS_ERROR("vdd-supply not found!");
+    }
+
+    ts_data->power_disabled = true;
+    ret = fts_power_source_ctrl(ts_data, ENABLE);
+    if (ret) {
+        FTS_ERROR("fail to enable power(regulator)");
+    }
+
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_power_source_exit(struct fts_ts_data *ts_data)
+{
+    fts_power_source_ctrl(ts_data, DISABLE);
+#if FTS_PINCTRL_EN
+    fts_pinctrl_select_suspend(ts_data);
+#endif
+    if (!IS_ERR_OR_NULL(ts_data->avdd)) {
+        regulator_put(ts_data->avdd);
+        ts_data->avdd = NULL;
+    }
+
+    if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
+        regulator_put(ts_data->dvdd);
+        ts_data->dvdd = NULL;
+    }
+
+    return 0;
+}
+
+static int fts_power_source_suspend(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+#if !defined(FTS_AOC_GESTURE_EN)
+    ret = fts_power_source_ctrl(ts_data, DISABLE);
+    if (ret < 0) {
+        FTS_ERROR("power off fail, ret=%d", ret);
+    }
+#endif
+#if FTS_PINCTRL_EN
+    fts_pinctrl_select_suspend(ts_data);
+#endif
+
+    return ret;
+}
+
+static int fts_power_source_resume(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+#if FTS_PINCTRL_EN
+    fts_pinctrl_select_normal(ts_data);
+#endif
+#if !defined(FTS_AOC_GESTURE_EN)
+    ret = fts_power_source_ctrl(ts_data, ENABLE);
+    if (ret < 0) {
+        FTS_ERROR("power on fail, ret=%d", ret);
+    }
+#endif
+    return ret;
+}
+#endif /* FTS_POWER_SOURCE_CUST_EN */
+
+static int fts_gpio_configure(struct fts_ts_data *data)
+{
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    /* request irq gpio */
+    if (gpio_is_valid(data->pdata->irq_gpio)) {
+        ret = gpio_request(data->pdata->irq_gpio, "fts_irq_gpio");
+        if (ret) {
+            FTS_ERROR("[GPIO]irq gpio request failed");
+            goto err_irq_gpio_req;
+        }
+
+        ret = gpio_direction_input(data->pdata->irq_gpio);
+        if (ret) {
+            FTS_ERROR("[GPIO]set_direction for irq gpio failed");
+            goto err_irq_gpio_dir;
+        }
+    }
+
+    /* request reset gpio */
+    if (gpio_is_valid(data->pdata->reset_gpio)) {
+        ret = gpio_request(data->pdata->reset_gpio, "fts_reset_gpio");
+        if (ret) {
+            FTS_ERROR("[GPIO]reset gpio request failed");
+            goto err_irq_gpio_dir;
+        }
+
+        ret = gpio_direction_output(data->pdata->reset_gpio, 0);
+        if (ret) {
+            FTS_ERROR("[GPIO]set_direction for reset gpio failed");
+            goto err_reset_gpio_dir;
+        }
+    }
+
+    FTS_FUNC_EXIT();
+    return 0;
+
+err_reset_gpio_dir:
+    if (gpio_is_valid(data->pdata->reset_gpio))
+        gpio_free(data->pdata->reset_gpio);
+err_irq_gpio_dir:
+    if (gpio_is_valid(data->pdata->irq_gpio))
+        gpio_free(data->pdata->irq_gpio);
+err_irq_gpio_req:
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_get_dt_coords(struct device *dev, char *name,
+                             struct fts_ts_platform_data *pdata)
+{
+    int ret = 0;
+    u32 coords[FTS_COORDS_ARR_SIZE] = { 0 };
+    struct property *prop;
+    struct device_node *np = dev->of_node;
+    int coords_size;
+
+    prop = of_find_property(np, name, NULL);
+    if (!prop)
+        return -EINVAL;
+    if (!prop->value)
+        return -ENODATA;
+
+    coords_size = prop->length / sizeof(u32);
+    if (coords_size != FTS_COORDS_ARR_SIZE) {
+        FTS_ERROR("invalid:%s, size:%d", name, coords_size);
+        return -EINVAL;
+    }
+
+    ret = of_property_read_u32_array(np, name, coords, coords_size);
+    if (ret < 0) {
+        FTS_ERROR("Unable to read %s, please check dts", name);
+        pdata->x_min = FTS_X_MIN_DISPLAY_DEFAULT;
+        pdata->y_min = FTS_Y_MIN_DISPLAY_DEFAULT;
+        pdata->x_max = FTS_X_MAX_DISPLAY_DEFAULT;
+        pdata->y_max = FTS_Y_MAX_DISPLAY_DEFAULT;
+        return -ENODATA;
+    } else {
+        pdata->x_min = coords[0];
+        pdata->y_min = coords[1];
+        pdata->x_max = coords[2];
+        pdata->y_max = coords[3];
+    }
+
+    FTS_INFO("display x(%d %d) y(%d %d)", pdata->x_min, pdata->x_max,
+             pdata->y_min, pdata->y_max);
+    return 0;
+}
+
+static int fts_check_panel_map(struct device_node *np,
+    struct fts_ts_platform_data *pdata)
+{
+    int ret = 0;
+    int index;
+    struct of_phandle_args panelmap;
+    struct drm_panel *panel = NULL;
+
+    if (of_property_read_bool(np, "focaltech,panel_map")) {
+        for (index = 0 ;; index++) {
+            ret = of_parse_phandle_with_fixed_args(np,
+                    "focaltech,panel_map",
+                    1,
+                    index,
+                    &panelmap);
+            if (ret) {
+                FTS_ERROR("Can't find display panel!\n");
+                return -EPROBE_DEFER;
+            }
+            panel = of_drm_find_panel(panelmap.np);
+            of_node_put(panelmap.np);
+            if (!IS_ERR_OR_NULL(panel)) {
+                pdata->panel = panel;
+                pdata->initial_panel_index = panelmap.args[0];
+                break;
+            }
+        }
+    }
+    return 0;
+}
+
+static int fts_parse_dt(struct device *dev, struct fts_ts_platform_data *pdata)
+{
+    int ret = 0;
+    struct device_node *np = dev->of_node;
+    u32 temp_val = 0;
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    u8 offload_id[4];
+#endif
+
+    FTS_FUNC_ENTER();
+
+    /* Check if panel(s) exist or not. */
+    ret = fts_check_panel_map(np, pdata);
+    if (ret)
+        return ret;
+
+    ret = fts_get_dt_coords(dev, "focaltech,display-coords", pdata);
+    if (ret < 0)
+        FTS_ERROR("Unable to get display-coords");
+
+    /* key */
+    pdata->have_key = of_property_read_bool(np, "focaltech,have-key");
+    if (pdata->have_key) {
+        ret = of_property_read_u32(np, "focaltech,key-number", &pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Key number undefined!");
+
+        ret = of_property_read_u32_array(np, "focaltech,keys",
+                                         pdata->keys, pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Keys undefined!");
+        else if (pdata->key_number > FTS_MAX_KEYS)
+            pdata->key_number = FTS_MAX_KEYS;
+
+        ret = of_property_read_u32_array(np, "focaltech,key-x-coords",
+                                         pdata->key_x_coords,
+                                         pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Key Y Coords undefined!");
+
+        ret = of_property_read_u32_array(np, "focaltech,key-y-coords",
+                                         pdata->key_y_coords,
+                                         pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Key X Coords undefined!");
+
+        FTS_INFO("VK Number:%d, key:(%d,%d,%d), "
+                 "coords:(%d,%d),(%d,%d),(%d,%d)",
+                 pdata->key_number,
+                 pdata->keys[0], pdata->keys[1], pdata->keys[2],
+                 pdata->key_x_coords[0], pdata->key_y_coords[0],
+                 pdata->key_x_coords[1], pdata->key_y_coords[1],
+                 pdata->key_x_coords[2], pdata->key_y_coords[2]);
+    }
+
+    /* reset, irq gpio info */
+    pdata->reset_gpio = of_get_named_gpio_flags(np, "focaltech,reset-gpio",
+                        0, &pdata->reset_gpio_flags);
+    if (pdata->reset_gpio < 0)
+        FTS_ERROR("Unable to get reset_gpio");
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    pdata->offload_id = 0;
+    ret = of_property_read_u8_array(np, "focaltech,touch_offload_id",
+                                    offload_id, 4);
+    if (ret == -EINVAL) {
+        FTS_ERROR("Failed to read focaltech,touch_offload_id with error = %d\n",
+            ret);
+    } else {
+        pdata->offload_id = *(u32 *)offload_id;
+        FTS_DEBUG("Offload device ID = \"%c%c%c%c\" / 0x%08X\n",
+            offload_id[0], offload_id[1], offload_id[2], offload_id[3],
+            pdata->offload_id);
+    }
+#endif
+
+    ret = of_property_read_u32(np, "focaltech,tx_ch_num", &temp_val);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get tx_ch_num, please check dts");
+    } else {
+        pdata->tx_ch_num = temp_val;
+        FTS_DEBUG("tx_ch_num = %d", pdata->tx_ch_num);
+    }
+
+    ret = of_property_read_u32(np, "focaltech,rx_ch_num", &temp_val);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get rx_ch_num, please check dts");
+    } else {
+        pdata->rx_ch_num = temp_val;
+        FTS_DEBUG("rx_ch_num = %d", pdata->rx_ch_num);
+    }
+
+    ret = of_property_read_u8(np, "focaltech,mm2px", &pdata->mm2px);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get mm2px, please check dts");
+        pdata->mm2px = 1;
+    } else {
+        FTS_DEBUG("mm2px = %d", pdata->mm2px);
+    }
+
+    pdata->irq_gpio = of_get_named_gpio_flags(np, "focaltech,irq-gpio",
+                      0, &pdata->irq_gpio_flags);
+    if (pdata->irq_gpio < 0)
+        FTS_ERROR("Unable to get irq_gpio");
+
+    ret = of_property_read_u32(np, "focaltech,max-touch-number", &temp_val);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get max-touch-number, please check dts");
+        pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT;
+    } else {
+        if (temp_val < 2)
+            pdata->max_touch_number = 2; /* max_touch_number must >= 2 */
+        else if (temp_val > FTS_MAX_POINTS_SUPPORT)
+            pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT;
+        else
+            pdata->max_touch_number = temp_val;
+    }
+
+    FTS_INFO("max touch number:%d, irq gpio:%d, reset gpio:%d",
+             pdata->max_touch_number, pdata->irq_gpio, pdata->reset_gpio);
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static void fts_suspend_work(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data,
+        suspend_work);
+
+    FTS_DEBUG("Entry");
+
+    mutex_lock(&ts_data->device_mutex);
+
+    reinit_completion(&ts_data->bus_resumed);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    if (ts_data->power_status == FTS_TS_STATE_SUSPEND) {
+        FTS_ERROR("Already suspended.\n");
+        mutex_unlock(&ts_data->device_mutex);
+        return;
+    }
+#endif
+    fts_ts_suspend(ts_data->dev);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    ts_data->power_status = FTS_TS_STATE_SUSPEND;
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    if (ts_data->tbn_register_mask) {
+        tbn_release_bus(ts_data->tbn_register_mask);
+        ts_data->tbn_owner = TBN_AOC;
+    }
+#endif
+    mutex_unlock(&ts_data->device_mutex);
+}
+
+static void fts_resume_work(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data,
+                                  resume_work);
+
+    FTS_DEBUG("Entry");
+    mutex_lock(&ts_data->device_mutex);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    if (ts_data->tbn_register_mask) {
+        tbn_request_bus(ts_data->tbn_register_mask);
+        ts_data->tbn_owner = TBN_AP;
+    }
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    if (ts_data->power_status == FTS_TS_STATE_POWER_ON) {
+        FTS_ERROR("Already resumed.\n");
+        mutex_unlock(&ts_data->device_mutex);
+        return;
+    }
+#endif
+    fts_ts_resume(ts_data->dev);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    ts_data->power_status = FTS_TS_STATE_POWER_ON;
+#endif
+    complete_all(&ts_data->bus_resumed);
+
+    mutex_unlock(&ts_data->device_mutex);
+}
+
+#if defined(CONFIG_FB)
+static int fb_notifier_callback(struct notifier_block *self,
+                                unsigned long event, void *data)
+{
+    struct fb_event *evdata = data;
+    int *blank = NULL;
+    struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
+                                  fb_notif);
+
+    if (!evdata) {
+        FTS_ERROR("evdata is null");
+        return 0;
+    }
+
+    if (!(event == FB_EARLY_EVENT_BLANK || event == FB_EVENT_BLANK)) {
+        FTS_INFO("event(%lu) do not need process\n", event);
+        return 0;
+    }
+
+    blank = evdata->data;
+    FTS_INFO("FB event:%lu,blank:%d", event, *blank);
+    switch (*blank) {
+    case FB_BLANK_UNBLANK:
+        if (FB_EARLY_EVENT_BLANK == event) {
+            FTS_INFO("resume: event = %lu, not care\n", event);
+        } else if (FB_EVENT_BLANK == event) {
+            queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+        }
+        break;
+    case FB_BLANK_POWERDOWN:
+        if (FB_EARLY_EVENT_BLANK == event) {
+            cancel_work_sync(&fts_data->resume_work);
+            fts_ts_suspend(ts_data->dev);
+        } else if (FB_EVENT_BLANK == event) {
+            FTS_INFO("suspend: event = %lu, not care\n", event);
+        }
+        break;
+    default:
+        FTS_INFO("FB BLANK(%d) do not need process\n", *blank);
+        break;
+    }
+
+    return 0;
+}
+#elif defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+static struct drm_panel *active_panel;
+
+static int drm_check_dt(struct device_node *np)
+{
+    int i = 0;
+    int count = 0;
+    struct device_node *node = NULL;
+    struct drm_panel *panel = NULL;
+
+    count = of_count_phandle_with_args(np, "panel", NULL);
+    if (count <= 0) {
+        FTS_ERROR("find drm_panel count(%d) fail", count);
+        return -ENODEV;
+    }
+
+    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)) {
+            FTS_INFO("find drm_panel successfully");
+            active_panel = panel;
+            return 0;
+        }
+    }
+
+    FTS_ERROR("no find drm_panel");
+    return -ENODEV;
+}
+
+static int drm_notifier_callback(struct notifier_block *self,
+                                 unsigned long event, void *data)
+{
+    struct msm_drm_notifier *evdata = data;
+    int *blank = NULL;
+    struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
+                                  fb_notif);
+
+    if (!evdata) {
+        FTS_ERROR("evdata is null");
+        return 0;
+    }
+
+    if (!((event == DRM_PANEL_EARLY_EVENT_BLANK )
+          || (event == DRM_PANEL_EVENT_BLANK))) {
+        FTS_INFO("event(%lu) do not need process\n", event);
+        return 0;
+    }
+
+    blank = evdata->data;
+    FTS_INFO("DRM event:%lu,blank:%d", event, *blank);
+    switch (*blank) {
+    case DRM_PANEL_BLANK_UNBLANK:
+        if (DRM_PANEL_EARLY_EVENT_BLANK == event) {
+            FTS_INFO("resume: event = %lu, not care\n", event);
+        } else if (DRM_PANEL_EVENT_BLANK == event) {
+            queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+        }
+        break;
+    case DRM_PANEL_BLANK_POWERDOWN:
+        if (DRM_PANEL_EARLY_EVENT_BLANK == event) {
+            cancel_work_sync(&fts_data->resume_work);
+            fts_ts_suspend(ts_data->dev);
+        } else if (DRM_PANEL_EVENT_BLANK == event) {
+            FTS_INFO("suspend: event = %lu, not care\n", event);
+        }
+        break;
+    default:
+        FTS_INFO("DRM BLANK(%d) do not need process\n", *blank);
+        break;
+    }
+
+    return 0;
+}
+#elif defined(CONFIG_ARCH_MSM)
+static int drm_notifier_callback(struct notifier_block *self,
+                                 unsigned long event, void *data)
+{
+    struct msm_drm_notifier *evdata = data;
+    int *blank = NULL;
+    struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
+                                  fb_notif);
+
+    if (!evdata) {
+        FTS_ERROR("evdata is null");
+        return 0;
+    }
+
+    if (!((event == MSM_DRM_EARLY_EVENT_BLANK )
+          || (event == MSM_DRM_EVENT_BLANK))) {
+        FTS_INFO("event(%lu) do not need process\n", event);
+        return 0;
+    }
+
+    blank = evdata->data;
+    FTS_INFO("DRM event:%lu,blank:%d", event, *blank);
+    switch (*blank) {
+    case MSM_DRM_BLANK_UNBLANK:
+        if (MSM_DRM_EARLY_EVENT_BLANK == event) {
+            FTS_INFO("resume: event = %lu, not care\n", event);
+        } else if (MSM_DRM_EVENT_BLANK == event) {
+            queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+        }
+        break;
+    case MSM_DRM_BLANK_POWERDOWN:
+        if (MSM_DRM_EARLY_EVENT_BLANK == event) {
+            cancel_work_sync(&fts_data->resume_work);
+            fts_ts_suspend(ts_data->dev);
+        } else if (MSM_DRM_EVENT_BLANK == event) {
+            FTS_INFO("suspend: event = %lu, not care\n", event);
+        }
+        break;
+    default:
+        FTS_INFO("DRM BLANK(%d) do not need process\n", *blank);
+        break;
+    }
+
+    return 0;
+}
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+static void fts_ts_early_suspend(struct early_suspend *handler)
+{
+    struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data,
+                                  early_suspend);
+
+    cancel_work_sync(&fts_data->resume_work);
+    fts_ts_suspend(ts_data->dev);
+}
+
+static void fts_ts_late_resume(struct early_suspend *handler)
+{
+    struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data,
+                                  early_suspend);
+
+    queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+}
+#endif
+
+static int fts_ts_probe_entry(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int pdata_size = sizeof(struct fts_ts_platform_data);
+
+    FTS_FUNC_ENTER();
+    ts_data->driver_probed = false;
+    FTS_INFO("%s", FTS_DRIVER_VERSION);
+    ts_data->pdata = kzalloc(pdata_size, GFP_KERNEL);
+    if (!ts_data->pdata) {
+        FTS_ERROR("allocate memory for platform_data fail");
+        return -ENOMEM;
+    }
+
+    if (ts_data->dev->of_node) {
+        ret = fts_parse_dt(ts_data->dev, ts_data->pdata);
+        if (ret)
+            FTS_ERROR("device-tree parse fail");
+
+#if defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+        ret = drm_check_dt(ts_data->dev->of_node);
+        if (ret) {
+            FTS_ERROR("parse drm-panel fail");
+        }
+#endif
+#endif
+    } else {
+        if (ts_data->dev->platform_data) {
+            memcpy(ts_data->pdata, ts_data->dev->platform_data, pdata_size);
+        } else {
+            FTS_ERROR("platform_data is null");
+            return -ENODEV;
+        }
+    }
+
+    ts_data->ts_workqueue = create_singlethread_workqueue("fts_wq");
+    if (!ts_data->ts_workqueue) {
+        FTS_ERROR("create fts workqueue fail");
+    }
+
+    spin_lock_init(&ts_data->irq_lock);
+    mutex_init(&ts_data->report_mutex);
+    mutex_init(&ts_data->bus_lock);
+    mutex_init(&ts_data->reg_lock);
+    ts_data->is_deepsleep = false;
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    ts_data->power_status = FTS_TS_STATE_POWER_ON;
+    ts_data->bus_refmask = FTS_TS_BUS_REF_SCREEN_ON;
+    mutex_init(&ts_data->bus_mutex);
+#endif
+    mutex_init(&ts_data->device_mutex);
+    init_completion(&ts_data->bus_resumed);
+    complete_all(&ts_data->bus_resumed);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    if (register_tbn(&ts_data->tbn_register_mask)) {
+        ret = -ENODEV;
+        FTS_ERROR("Failed to register tbn context.\n");
+        goto err_init_tbn;
+    }
+    ts_data->tbn_owner = TBN_AP;
+    FTS_INFO("tbn_register_mask = %#x.\n", ts_data->tbn_register_mask);
+#endif
+
+    /* Init communication interface */
+    ret = fts_bus_init(ts_data);
+    if (ret) {
+        FTS_ERROR("bus initialize fail");
+        goto err_bus_init;
+    }
+
+    ret = fts_input_init(ts_data);
+    if (ret) {
+        FTS_ERROR("input initialize fail");
+        goto err_input_init;
+    }
+
+    ret = fts_report_buffer_init(ts_data);
+    if (ret) {
+        FTS_ERROR("report buffer init fail");
+        goto err_report_buffer;
+    }
+
+    ret = fts_gpio_configure(ts_data);
+    if (ret) {
+        FTS_ERROR("configure the gpios fail");
+        goto err_gpio_config;
+    }
+
+#if FTS_POWER_SOURCE_CUST_EN
+    ret = fts_power_source_init(ts_data);
+    if (ret) {
+        FTS_ERROR("fail to get power(regulator)");
+        goto err_power_init;
+    }
+#endif
+
+#if (!FTS_CHIP_IDC)
+    fts_reset_proc(FTS_RESET_INTERVAL);
+#endif
+
+    ret = fts_get_ic_information(ts_data);
+    if (ret) {
+        FTS_ERROR("not focal IC, unregister driver");
+        goto err_power_init;
+    }
+
+    ret = fts_create_apk_debug_channel(ts_data);
+    if (ret) {
+        FTS_ERROR("create apk debug node fail");
+    }
+
+#if GOOGLE_REPORT_MODE
+    memset(ts_data->current_host_status, 0, FTS_CUSTOMER_STATUS_LEN);
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_UNCOMPRESSED;
+    /* Update ts_data->fw_default_heatmap_mode from firmware setting */
+    fts_get_default_heatmap_mode(ts_data);
+    ts_data->fw_heatmap_mode = ts_data->fw_default_heatmap_mode;
+    ts_data->compress_heatmap_wlen = 0;
+#endif
+    ts_data->enable_fw_grip = FW_GRIP_ENABLE;
+    ts_data->enable_fw_palm = FW_GRIP_ENABLE;
+    ts_data->set_continuously_report = ENABLE;
+    ts_data->glove_mode = DISABLE;
+    fts_update_feature_setting(ts_data);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    ts_data->offload.caps.touch_offload_major_version = TOUCH_OFFLOAD_INTERFACE_MAJOR_VERSION;
+    ts_data->offload.caps.touch_offload_minor_version = TOUCH_OFFLOAD_INTERFACE_MINOR_VERSION;
+    ts_data->offload.caps.device_id = ts_data->pdata->offload_id;
+    ts_data->offload.caps.display_width = ts_data->pdata->x_max;
+    ts_data->offload.caps.display_height = ts_data->pdata->y_max;
+    ts_data->offload.caps.tx_size = ts_data->pdata->tx_ch_num;
+    ts_data->offload.caps.rx_size = ts_data->pdata->rx_ch_num;
+
+    ts_data->offload.caps.bus_type = BUS_TYPE_SPI;
+    ts_data->offload.caps.bus_speed_hz = 10000000;
+    ts_data->offload.caps.heatmap_size = HEATMAP_SIZE_FULL;
+    ts_data->offload.caps.touch_data_types = TOUCH_DATA_TYPE_COORD |
+                                             TOUCH_DATA_TYPE_STRENGTH |
+                                             TOUCH_DATA_TYPE_FILTERED |
+                                             TOUCH_DATA_TYPE_RAW;
+    ts_data->offload.caps.touch_scan_types = TOUCH_SCAN_TYPE_MUTUAL |
+                                             TOUCH_SCAN_TYPE_SELF;
+    ts_data->offload.caps.continuous_reporting = true;
+    ts_data->offload.caps.noise_reporting = false;
+    ts_data->offload.caps.cancel_reporting = true;
+    ts_data->offload.caps.rotation_reporting = true;
+    ts_data->offload.caps.size_reporting = true;
+    ts_data->offload.caps.filter_grip = true;
+    ts_data->offload.caps.filter_palm = true;
+    ts_data->offload.caps.num_sensitivity_settings = 1;
+
+    ts_data->offload.hcallback = (void *)ts_data;
+    ts_data->offload.report_cb = fts_offload_report;
+    touch_offload_init(&ts_data->offload);
+
+    ts_data->touch_offload_active_coords = 0;
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+
+    /* |-   MS    -|- Normal-SS -|- Water-SS  -|
+     * |- Tx*Rx*2 -|- (Tx+Rx)*2 -|- (Tx+Rx)*2 -|
+     * Total size = 1288 bytes
+     */
+    if (!ts_data->heatmap_buff) {
+        ts_data->heatmap_buff_size = sizeof(u16) *
+            ((ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num) +
+            (ts_data->pdata->tx_ch_num + ts_data->pdata->rx_ch_num) * 2);
+        FTS_DEBUG("Allocate heatmap_buff size=%d\n", ts_data->heatmap_buff_size);
+        ts_data->heatmap_buff = kmalloc(ts_data->heatmap_buff_size, GFP_KERNEL);
+        if (!ts_data->heatmap_buff) {
+            FTS_ERROR("allocate heatmap_buff failed\n");
+            goto err_heatmap_buff;
+        }
+    }
+
+    /* |- cap header (91) -|- Water-SS -|- Normal-SS -|- Normal-MS -|
+     * |-        91       -|-   68*2   -|-   68*2    -|-  16*34*2  -|
+     * Total size = 1379 bytes
+     */
+    if (!ts_data->heatmap_raw) {
+        int node_num = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num;
+        ts_data->heatmap_raw_size = FTS_CAP_DATA_LEN +
+            ((node_num + FTS_SELF_DATA_LEN * 2) * sizeof(u16));
+        FTS_DEBUG("Allocate heatmap_raw size=%d\n", ts_data->heatmap_raw_size);
+        ts_data->heatmap_raw = kmalloc(ts_data->heatmap_raw_size, GFP_KERNEL);
+        if (!ts_data->heatmap_raw) {
+            FTS_ERROR("allocate heatmap_raw failed\n");
+            goto err_heatmap_raw;
+        }
+    }
+
+    /* |-   MS    -|
+     * |- 16*34*2 -|
+     */
+    if (!ts_data->trans_raw) {
+        int node_num = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num;
+        ts_data->trans_raw_size = node_num * sizeof(u16);
+        FTS_DEBUG("Allocate trans_raw size=%d\n", ts_data->trans_raw_size);
+        ts_data->trans_raw = kmalloc(ts_data->trans_raw_size, GFP_KERNEL);
+        if (!ts_data->trans_raw) {
+            FTS_ERROR("allocate trans_raw failed\n");
+            goto err_trans_raw;
+        }
+    }
+#endif
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    /*
+     * Heatmap_probe must be called before irq routine is registered,
+     * because heatmap_read is called from interrupt context.
+    */
+    ts_data->v4l2.parent_dev = ts_data->dev;
+    ts_data->v4l2.input_dev = ts_data->input_dev;
+    ts_data->v4l2.read_frame = v4l2_read_frame;
+    ts_data->v4l2.width = ts_data->pdata->tx_ch_num;
+    ts_data->v4l2.height = ts_data->pdata->rx_ch_num;
+    /* 180 Hz operation */
+    ts_data->v4l2.timeperframe.numerator = 1;
+    ts_data->v4l2.timeperframe.denominator = 180;
+    ret = heatmap_probe(&ts_data->v4l2);
+    if (ret < 0) {
+        FTS_ERROR("heatmap probe unsuccessfully!");
+        goto err_heatmap_probe;
+    } else {
+        FTS_DEBUG("heatmap probe successfully!");
+    }
+#endif
+
+    /* init motion filter mode and state. */
+    ts_data->mf_mode = MF_DYNAMIC;
+    ts_data->mf_state = MF_UNFILTERED;
+
+    ret = fts_create_sysfs(ts_data);
+    if (ret) {
+        FTS_ERROR("create sysfs node fail");
+    }
+
+#if FTS_POINT_REPORT_CHECK_EN
+    ret = fts_point_report_check_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init point report check fail");
+    }
+#endif
+
+    ret = fts_ex_mode_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init glove/cover/charger fail");
+    }
+
+    ret = fts_gesture_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init gesture fail");
+    }
+
+#if FTS_TEST_EN
+    ret = fts_test_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init production test fail");
+    }
+#endif
+
+#if FTS_ESDCHECK_EN
+    ret = fts_esdcheck_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init esd check fail");
+    }
+#endif
+    /* init pm_qos before interrupt registered. */
+    cpu_latency_qos_add_request(&ts_data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
+
+    ret = fts_irq_registration(ts_data);
+    if (ret) {
+        FTS_ERROR("request irq failed");
+        goto err_irq_req;
+    }
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    ret = register_panel_bridge(ts_data);
+    if (ret < 0) {
+        FTS_ERROR("register_panel_bridge failed. ret = 0x%08X\n", ret);
+    }
+#endif
+
+    if (ts_data->ts_workqueue) {
+        INIT_WORK(&ts_data->resume_work, fts_resume_work);
+        INIT_WORK(&ts_data->suspend_work, fts_suspend_work);
+    }
+
+    ret = fts_fwupg_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init fw upgrade fail");
+    }
+
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+    init_completion(&ts_data->pm_completion);
+    ts_data->pm_suspend = false;
+#endif
+
+#if defined(CONFIG_FB)
+    ts_data->fb_notif.notifier_call = fb_notifier_callback;
+    ret = fb_register_client(&ts_data->fb_notif);
+    if (ret) {
+        FTS_ERROR("[FB]Unable to register fb_notifier: %d", ret);
+    }
+#elif defined(CONFIG_DRM_PANEL) || defined(CONFIG_ARCH_MSM)
+    ts_data->fb_notif.notifier_call = drm_notifier_callback;
+#if defined(CONFIG_DRM_PANEL)
+    if (active_panel) {
+        ret = drm_panel_notifier_register(active_panel, &ts_data->fb_notif);
+        if (ret)
+            FTS_ERROR("[DRM]drm_panel_notifier_register fail: %d\n", ret);
+    }
+#elif defined(CONFIG_ARCH_MSM)
+    ret = msm_drm_register_client(&ts_data->fb_notif);
+    if (ret) {
+        FTS_ERROR("[DRM]Unable to register fb_notifier: %d\n", ret);
+    }
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+    ts_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + FTS_SUSPEND_LEVEL;
+    ts_data->early_suspend.suspend = fts_ts_early_suspend;
+    ts_data->early_suspend.resume = fts_ts_late_resume;
+    register_early_suspend(&ts_data->early_suspend);
+#endif
+
+    ts_data->work_mode = FTS_REG_WORKMODE_WORK_VALUE;
+#if GOOGLE_REPORT_MODE
+    fts_read_reg(FTS_REG_CUSTOMER_STATUS, &ts_data->current_host_status[0]);
+    FTS_INFO("-------Palm mode %s\n",
+        (ts_data->current_host_status[0] & (1 << STATUS_PALM)) ? "enter" : "exit");
+    FTS_INFO("-------Water mode %s\n",
+        (ts_data->current_host_status[0] & (1 << STATUS_WATER)) ? "enter" : "exit");
+    FTS_INFO("-------Grip mode %s\n",
+        (ts_data->current_host_status[0] & (1 << STATUS_GRIP)) ? "enter" : "exit");
+    FTS_INFO("-------Glove mode %s\n",
+        (ts_data->current_host_status[0] & (1 << STATUS_GLOVE)) ? "enter" : "exit");
+    FTS_INFO("-------Edge palm %s\n",
+        (ts_data->current_host_status[0] & (1 << STATUS_EDGE_PALM)) ? "enter" : "exit");
+    FTS_INFO("-------Reset %s\n",
+        (ts_data->current_host_status[0] & (1 << STATUS_RESET)) ? "enter" : "exit");
+#endif
+
+    ts_data->driver_probed = true;
+    FTS_FUNC_EXIT();
+    return 0;
+
+err_irq_req:
+    cpu_latency_qos_remove_request(&ts_data->pm_qos_req);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    heatmap_remove(&ts_data->v4l2);
+err_heatmap_probe:
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    kfree_safe(ts_data->trans_raw);
+err_trans_raw:
+    kfree_safe(ts_data->heatmap_raw);
+err_heatmap_raw:
+    kfree_safe(ts_data->heatmap_buff);
+err_heatmap_buff:
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    touch_offload_cleanup(&ts_data->offload);
+#endif
+
+#if FTS_POWER_SOURCE_CUST_EN
+err_power_init:
+    fts_power_source_exit(ts_data);
+#endif
+    if (gpio_is_valid(ts_data->pdata->reset_gpio))
+        gpio_free(ts_data->pdata->reset_gpio);
+    if (gpio_is_valid(ts_data->pdata->irq_gpio))
+        gpio_free(ts_data->pdata->irq_gpio);
+err_gpio_config:
+    kfree_safe(ts_data->point_buf);
+    kfree_safe(ts_data->events);
+err_report_buffer:
+    input_unregister_device(ts_data->input_dev);
+#if FTS_PEN_EN
+    input_unregister_device(ts_data->pen_dev);
+#endif
+err_input_init:
+    if (ts_data->ts_workqueue)
+        destroy_workqueue(ts_data->ts_workqueue);
+err_bus_init:
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    if (ts_data->tbn_register_mask)
+        unregister_tbn(&ts_data->tbn_register_mask);
+err_init_tbn:
+#endif
+    kfree_safe(ts_data->bus_tx_buf);
+    kfree_safe(ts_data->bus_rx_buf);
+    kfree_safe(ts_data->pdata);
+
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_ts_remove_entry(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    free_irq(ts_data->irq, ts_data);
+
+#if FTS_POINT_REPORT_CHECK_EN
+    fts_point_report_check_exit(ts_data);
+#endif
+    fts_release_apk_debug_channel(ts_data);
+
+#if FTS_TEST_EN
+    /* remove the test nodes and sub-dir in /proc/focaltech_touch/selftest/ */
+    fts_test_exit(ts_data);
+#endif
+    /* remove all nodes and sub-dir in /proc/focaltech_touch/ */
+    fts_remove_sysfs(ts_data);
+
+    fts_ex_mode_exit(ts_data);
+
+    fts_fwupg_exit(ts_data);
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_exit(ts_data);
+#endif
+
+    fts_gesture_exit(ts_data);
+    fts_bus_exit(ts_data);
+
+    input_unregister_device(ts_data->input_dev);
+#if FTS_PEN_EN
+    input_unregister_device(ts_data->pen_dev);
+#endif
+
+    cancel_work_sync(&ts_data->suspend_work);
+    cancel_work_sync(&ts_data->resume_work);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    if (ts_data->tbn_register_mask)
+        unregister_tbn(&ts_data->tbn_register_mask);
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    unregister_panel_bridge(&ts_data->panel_bridge);
+#endif
+
+    if (ts_data->ts_workqueue)
+        destroy_workqueue(ts_data->ts_workqueue);
+
+    cpu_latency_qos_remove_request(&ts_data->pm_qos_req);
+
+#if defined(CONFIG_FB)
+    if (fb_unregister_client(&ts_data->fb_notif))
+        FTS_ERROR("[FB]Error occurred while unregistering fb_notifier.");
+#elif defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+    if (active_panel)
+        drm_panel_notifier_unregister(active_panel, &ts_data->fb_notif);
+#elif defined(CONFIG_ARCH_MSM)
+    if (msm_drm_unregister_client(&ts_data->fb_notif))
+        FTS_ERROR("[DRM]Error occurred while unregistering fb_notifier.\n");
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+    unregister_early_suspend(&ts_data->early_suspend);
+#endif
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    touch_offload_cleanup(&ts_data->offload);
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    kfree_safe(ts_data->heatmap_buff);
+    kfree_safe(ts_data->heatmap_raw);
+    kfree_safe(ts_data->trans_raw);
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    heatmap_remove(&ts_data->v4l2);
+#endif
+
+    if (gpio_is_valid(ts_data->pdata->reset_gpio))
+        gpio_free(ts_data->pdata->reset_gpio);
+
+    if (gpio_is_valid(ts_data->pdata->irq_gpio))
+        gpio_free(ts_data->pdata->irq_gpio);
+
+#if FTS_POWER_SOURCE_CUST_EN
+    fts_power_source_exit(ts_data);
+#endif
+
+    kfree_safe(ts_data->point_buf);
+    kfree_safe(ts_data->events);
+
+    kfree_safe(ts_data->pdata);
+    kfree_safe(ts_data);
+
+    FTS_FUNC_EXIT();
+
+    return 0;
+}
+
+static int fts_write_reg_safe(u8 reg, u8 write_val) {
+    int ret = 0;
+    int i;
+    int j;
+    u8 reg_val;
+
+    for (i = 0; i < MAX_RETRY_CNT; i++) {
+        ret = fts_write_reg(reg, write_val);
+        if (ret < 0) {
+            FTS_DEBUG("write 0x%X failed", reg);
+            return ret;
+        }
+        for (j = 0; j < MAX_RETRY_CNT; j++) {
+            reg_val = 0xFF;
+            ret = fts_read_reg(reg, &reg_val);
+            if (ret < 0) {
+                FTS_DEBUG("read 0x%X failed", reg);
+                return ret;
+            }
+            if (write_val == reg_val) {
+                return ret;
+            }
+            msleep(1);
+        }
+
+        FTS_ERROR("%s failed, reg(0x%X), write_val(0x%x), reg_val(0x%x), " \
+            "retry(%d)", __func__, reg, write_val, reg_val, i);
+    }
+    if (i == MAX_RETRY_CNT)
+        ret = -EIO;
+    return ret;
+}
+
+static void fts_update_host_feature_setting(struct fts_ts_data *ts_data,
+    bool en, u8 fw_mode_setting){
+    if (en)
+        ts_data->current_host_status[1] |= 1 << fw_mode_setting;
+    else
+        ts_data->current_host_status[1] &= ~(1 << fw_mode_setting);
+}
+
+int fts_get_default_heatmap_mode(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    u8 value_heatmap = 0;
+    u8 value_compressed = 0;
+    u8 reg_heatmap = FTS_REG_HEATMAP_9E;
+    u8 reg_compressed = FTS_REG_HEATMAP_ED;
+
+    if (!ts_data->driver_probed || ts_data->fw_loading) {
+        ret = fts_read_reg(reg_compressed, &value_compressed);
+        if (ret) {
+            FTS_ERROR("Read reg(%2X) error!", reg_compressed);
+            goto exit;
+        }
+
+        ret = fts_read_reg(reg_heatmap, &value_heatmap);
+        if (ret) {
+            FTS_ERROR("Read reg(%2X) error!", reg_heatmap);
+            goto exit;
+        }
+
+        if (value_heatmap == 0)
+            ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_DISABLE;
+        else if (value_compressed)
+            ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_COMPRESSED;
+        else
+            ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_UNCOMPRESSED;
+
+exit:
+        if (ret == 0) {
+            FTS_DEBUG("Default fw_heatamp is %s and %s.\n",
+                value_compressed? "compressed" : "uncompressed",
+                value_heatmap ? "enabled" : "disabled");
+        }
+    }
+    return ret;
+}
+
+int fts_set_heatmap_mode(struct fts_ts_data *ts_data, u8 heatmap_mode)
+{
+    int ret = 0;
+    u8 value_heatmap = 0;
+    u8 value_compressed = 0;
+    u8 reg_heatmap = FTS_REG_HEATMAP_9E;
+    u8 reg_compressed = FTS_REG_HEATMAP_ED;
+    int count = 0;
+    char tmpbuf[FTS_MESSAGE_LENGTH];
+
+    switch (heatmap_mode) {
+      case FW_HEATMAP_MODE_DISABLE:
+          value_heatmap = DISABLE;
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Disable fw_heatmap");
+          break;
+      case FW_HEATMAP_MODE_COMPRESSED:
+          value_heatmap = ENABLE;
+          value_compressed = ENABLE;
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Enable compressed fw_heatmap");
+          break;
+      case FW_HEATMAP_MODE_UNCOMPRESSED:
+          value_heatmap = ENABLE;
+          value_compressed = DISABLE;
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Enable uncompressed fw_heatmap");
+          break;
+      default:
+          FTS_ERROR("The input heatmap more(%d) is invalid.", heatmap_mode);
+          return -EINVAL;
+    }
+
+    ret = fts_write_reg_safe(reg_compressed, value_compressed);
+    if (ret) {
+        goto exit;
+    }
+    ret = fts_write_reg_safe(reg_heatmap, value_heatmap);
+    if (ret == 0) {
+      ts_data->fw_heatmap_mode = heatmap_mode;
+      fts_update_host_feature_setting(ts_data, value_heatmap, FW_HEATMAP);
+    }
+
+exit:
+    FTS_DEBUG("%s %s.\n", tmpbuf,
+        (ret == 0) ? "successfully" : "unsuccessfully");
+
+    return ret;
+}
+
+int fts_set_grip_mode(struct fts_ts_data *ts_data, u8 grip_mode)
+{
+    int ret = 0;
+    bool en = grip_mode % 2;
+    u8 value = en ? 0x00 : 0xAA;
+    u8 reg = FTS_REG_EDGE_MODE_EN;
+
+    ret = fts_write_reg_safe(reg, value);
+    if (ret == 0) {
+        fts_update_host_feature_setting(ts_data, en, FW_GRIP);
+    }
+
+    FTS_DEBUG("%s fw_grip(%d) %s.\n", en ? "Enable" : "Disable",
+        ts_data->enable_fw_grip,
+        (ret == 0)  ? "successfully" : "unsuccessfully");
+    return ret;
+}
+
+int fts_set_palm_mode(struct fts_ts_data *ts_data, u8 palm_mode)
+{
+    int ret = 0;
+    bool en = palm_mode % 2;
+    u8 value = en ? ENABLE : DISABLE;
+    u8 reg = FTS_REG_PALM_EN;
+
+    ret = fts_write_reg_safe(reg, value);
+    if (ret == 0) {
+        fts_update_host_feature_setting(ts_data, en, FW_PALM);
+    }
+
+    FTS_DEBUG("%s fw_palm(%d) %s.\n", en ? "Enable" : "Disable",
+        ts_data->enable_fw_palm,
+        (ret == 0) ? "successfully" : "unsuccessfully");
+    return ret;
+}
+
+int fts_set_continuous_mode(struct fts_ts_data *ts_data, bool en)
+{
+    int ret = 0;
+    u8 value = en ? ENABLE : DISABLE;
+    u8 reg = FTS_REG_CONTINUOUS_EN;
+
+    mutex_lock(&ts_data->reg_lock);
+    if (!ts_data->is_deepsleep) {
+        ret = fts_write_reg_safe(reg, value);
+        if (ret == 0) {
+            ts_data->set_continuously_report = value;
+            fts_update_host_feature_setting(ts_data, en, FW_CONTINUOUS);
+        }
+
+        PR_LOGD("%s fw_continuous %s.\n", en ? "Enable" : "Disable",
+            (ret == 0) ? "successfully" : "unsuccessfully");
+    }
+    mutex_unlock(&ts_data->reg_lock);
+    return ret;
+}
+
+int fts_set_glove_mode(struct fts_ts_data *ts_data, bool en)
+{
+    int ret = 0;
+    u8 value = en ? ENABLE : DISABLE;
+    u8 reg = FTS_REG_GLOVE_MODE_EN;
+
+    ret = fts_write_reg_safe(reg, value);
+    if (ret == 0) {
+        ts_data->glove_mode = value;
+        fts_update_host_feature_setting(ts_data, en, FW_GLOVE);
+    }
+
+    FTS_DEBUG("%s fw_glove %s.\n", en ? "Enable" : "Disable",
+        (ret == 0) ? "successfully" : "unsuccessfully");
+    return ret;
+}
+
+/**
+ * fts_update_feature_setting()
+ *
+ * Restore the feature settings after the device resume.
+ *
+ * @param
+ *    [ in] ts_data: touch driver handle.
+ *
+ */
+void fts_update_feature_setting(struct fts_ts_data *ts_data)
+{
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    fts_set_heatmap_mode(ts_data, ts_data->fw_default_heatmap_mode);
+#endif
+
+    fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
+
+    fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
+
+    fts_set_continuous_mode(ts_data, ts_data->set_continuously_report);
+
+    fts_set_glove_mode(ts_data, ts_data->glove_mode);
+}
+
+static int fts_ts_suspend(struct device *dev)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    FTS_FUNC_ENTER();
+    if (ts_data->bus_refmask)
+        FTS_DEBUG("bus_refmask 0x%X\n", ts_data->bus_refmask);
+
+
+    if (ts_data->suspended) {
+        FTS_INFO("Already in suspend state");
+        return 0;
+    }
+
+    if (ts_data->fw_loading) {
+        FTS_INFO("fw upgrade in process, can't suspend");
+        return 0;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_suspend();
+#endif
+
+    if (ts_data->gesture_mode) {
+        fts_gesture_suspend(ts_data);
+    } else {
+        if (ts_data->bus_refmask == FTS_TS_BUS_REF_BUGREPORT &&
+            ktime_ms_delta(ktime_get(), ts_data->bugreport_ktime_start) >
+            30 * MSEC_PER_SEC) {
+            fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_BUGREPORT, false);
+            pm_relax(ts_data->dev);
+            ts_data->bugreport_ktime_start = 0;
+            FTS_DEBUG("Force release FTS_TS_BUS_REF_BUGREPORT reference bit.");
+            return -EBUSY;
+        }
+
+        /* Disable irq */
+        fts_irq_disable();
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+        fts_set_heatmap_mode(ts_data, FW_HEATMAP_MODE_DISABLE);
+#endif
+        FTS_DEBUG("make TP enter into sleep mode");
+        mutex_lock(&ts_data->reg_lock);
+        ret = fts_write_reg(FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP);
+        ts_data->is_deepsleep = true;
+        mutex_unlock(&ts_data->reg_lock);
+        if (ret < 0)
+            FTS_ERROR("set TP to sleep mode fail, ret=%d", ret);
+
+        if (!ts_data->ic_info.is_incell) {
+#if FTS_POWER_SOURCE_CUST_EN
+            ret = fts_power_source_suspend(ts_data);
+            if (ret < 0) {
+                FTS_ERROR("power enter suspend fail");
+            }
+#endif
+        }
+    }
+
+    fts_release_all_finger();
+    ts_data->suspended = true;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+
+/**
+ * Report a finger down event on the long press gesture area then immediately
+ * report a cancel event(MT_TOOL_PALM).
+ */
+static void fts_report_cancel_event(struct fts_ts_data *ts_data)
+{
+    FTS_INFO("Report cancel event for UDFPS");
+
+    mutex_lock(&ts_data->report_mutex);
+    /* Finger down on UDFPS area. */
+    input_mt_slot(ts_data->input_dev, 0);
+    input_report_key(ts_data->input_dev, BTN_TOUCH, 1);
+    input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 1);
+    input_report_abs(ts_data->input_dev, ABS_MT_POSITION_X,
+        ts_data->fts_gesture_data.coordinate_x[0]);
+    input_report_abs(ts_data->input_dev, ABS_MT_POSITION_Y,
+        ts_data->fts_gesture_data.coordinate_y[0]);
+    input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR,
+        ts_data->fts_gesture_data.major[0]);
+    input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MINOR,
+        ts_data->fts_gesture_data.minor[0]);
+#ifndef SKIP_PRESSURE
+    input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 1);
+#endif
+    input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION,
+        ts_data->fts_gesture_data.orientation[0]);
+    input_sync(ts_data->input_dev);
+
+    /* Report MT_TOOL_PALM for canceling the touch event. */
+    input_mt_slot(ts_data->input_dev, 0);
+    input_report_key(ts_data->input_dev, BTN_TOUCH, 1);
+    input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_PALM, 1);
+    input_sync(ts_data->input_dev);
+
+    /* Release touches. */
+    input_mt_slot(ts_data->input_dev, 0);
+#ifndef SKIP_PRESSURE
+    input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 0);
+#endif
+    input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 0);
+    input_report_abs(ts_data->input_dev, ABS_MT_TRACKING_ID, -1);
+    input_report_key(ts_data->input_dev, BTN_TOUCH, 0);
+    input_sync(ts_data->input_dev);
+    mutex_unlock(&ts_data->report_mutex);
+}
+
+static void fts_check_finger_status(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    u8 power_mode = FTS_REG_POWER_MODE_SLEEP;
+    ktime_t timeout = ktime_add_ms(ktime_get(), 500); /* 500ms. */
+
+    /* If power mode is deep sleep mode, then reurn. */
+    ret = fts_read_reg(FTS_REG_POWER_MODE, &power_mode);
+    if (ret)
+        return;
+
+    if (power_mode == FTS_REG_POWER_MODE_SLEEP)
+        return;
+
+    while (ktime_get() < timeout) {
+        ret = fts_gesture_readdata(ts_data, false);
+        if (ret)
+            break;
+
+        if (ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_LPTW_DOWN) {
+            msleep(30);
+            continue;
+        }
+
+        if (ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_LPTW_UP ||
+            ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_STTW) {
+            fts_report_cancel_event(ts_data);
+        }
+        break;
+    }
+}
+
+static int fts_ts_resume(struct device *dev)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    if (!ts_data->suspended) {
+        FTS_DEBUG("Already in awake state");
+        return 0;
+    }
+
+    fts_release_all_finger();
+
+    if (!ts_data->ic_info.is_incell) {
+        if (!ts_data->gesture_mode) {
+#if FTS_POWER_SOURCE_CUST_EN
+            fts_power_source_resume(ts_data);
+#endif
+            fts_check_finger_status(ts_data);
+        }
+
+        fts_reset_proc(FTS_RESET_INTERVAL);
+    }
+
+    ret = fts_wait_tp_to_valid();
+    if (ret != 0) {
+        FTS_ERROR("Resume has been cancelled by wake up timeout");
+#if FTS_POWER_SOURCE_CUST_EN
+        if (!ts_data->gesture_mode)
+            fts_power_source_suspend(ts_data);
+#endif
+        return ret;
+    }
+
+    ts_data->is_deepsleep = false;
+    fts_ex_mode_recovery(ts_data);
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_resume();
+#endif
+
+    if (ts_data->gesture_mode) {
+        fts_gesture_resume(ts_data);
+    } else {
+        fts_irq_enable();
+    }
+
+    ts_data->suspended = false;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+static int fts_pm_suspend(struct device *dev)
+{
+    struct fts_ts_data *ts_data = dev_get_drvdata(dev);
+
+    FTS_INFO("system enters into pm_suspend");
+    ts_data->pm_suspend = true;
+    reinit_completion(&ts_data->pm_completion);
+    return 0;
+}
+
+static int fts_pm_resume(struct device *dev)
+{
+    struct fts_ts_data *ts_data = dev_get_drvdata(dev);
+
+    FTS_INFO("system resumes from pm_suspend");
+    ts_data->pm_suspend = false;
+    complete(&ts_data->pm_completion);
+    return 0;
+}
+
+static const struct dev_pm_ops fts_dev_pm_ops = {
+    .suspend = fts_pm_suspend,
+    .resume = fts_pm_resume,
+};
+#endif
+
+/*****************************************************************************
+* TP Driver
+*****************************************************************************/
+static int fts_ts_probe(struct spi_device *spi)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = NULL;
+
+    FTS_INFO("Touch Screen(SPI BUS) driver proboe...");
+    spi->mode = SPI_MODE_0;
+    spi->bits_per_word = 8;
+    spi->rt = true;
+    ret = spi_setup(spi);
+    if (ret) {
+        FTS_ERROR("spi setup fail");
+        return ret;
+    }
+
+    /* malloc memory for global struct variable */
+    ts_data = (struct fts_ts_data *)kzalloc(sizeof(*ts_data), GFP_KERNEL);
+    if (!ts_data) {
+        FTS_ERROR("allocate memory for fts_data fail");
+        return -ENOMEM;
+    }
+
+    fts_data = ts_data;
+    ts_data->spi = spi;
+    ts_data->dev = &spi->dev;
+    ts_data->log_level = FTS_KEY_LOG_LEVEL;
+
+    ts_data->bus_type = FTS_BUS_TYPE_SPI_V2;
+    spi_set_drvdata(spi, ts_data);
+
+    ret = fts_ts_probe_entry(ts_data);
+    if (ret) {
+        FTS_ERROR("Touch Screen(SPI BUS) driver probe fail");
+        kfree_safe(ts_data);
+        return ret;
+    }
+
+    FTS_INFO("Touch Screen(SPI BUS) driver probe successfully");
+    return 0;
+}
+
+static void fts_ts_remove(struct spi_device *spi)
+{
+    fts_ts_remove_entry(spi_get_drvdata(spi));
+}
+
+static void fts_ts_shutdown(struct spi_device *spi)
+{
+    fts_ts_remove(spi);
+}
+
+static const struct spi_device_id fts_ts_id[] = {
+    {FTS_DRIVER_NAME, 0},
+    {},
+};
+static const struct of_device_id fts_dt_match[] = {
+    {.compatible = "focaltech,ts", },
+    {},
+};
+MODULE_DEVICE_TABLE(of, fts_dt_match);
+
+static struct spi_driver fts_ts_driver = {
+    .probe = fts_ts_probe,
+    .remove = fts_ts_remove,
+    .shutdown = fts_ts_shutdown,
+    .driver = {
+        .name = FTS_DRIVER_NAME,
+        .owner = THIS_MODULE,
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+        .pm = &fts_dev_pm_ops,
+#endif
+        .of_match_table = of_match_ptr(fts_dt_match),
+    },
+    .id_table = fts_ts_id,
+};
+
+static int __init fts_ts_init(void)
+{
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    ret = spi_register_driver(&fts_ts_driver);
+    if ( ret != 0 ) {
+        FTS_ERROR("Focaltech touch screen driver init failed!");
+    }
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static void __exit fts_ts_exit(void)
+{
+    spi_unregister_driver(&fts_ts_driver);
+}
+
+module_init(fts_ts_init);
+module_exit(fts_ts_exit);
+
+MODULE_AUTHOR("FocalTech Driver Team");
+MODULE_DESCRIPTION("FocalTech Touchscreen Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/ft3658/focaltech_core.h b/ft3658/focaltech_core.h
new file mode 100644
index 0000000..c86c92d
--- /dev/null
+++ b/ft3658/focaltech_core.h
@@ -0,0 +1,495 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/*****************************************************************************
+*
+* File Name: focaltech_core.h
+
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+#ifndef __LINUX_FOCALTECH_CORE_H__
+#define __LINUX_FOCALTECH_CORE_H__
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/uaccess.h>
+#include <linux/firmware.h>
+#include <linux/debugfs.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/jiffies.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_qos.h>
+#include "focaltech_common.h"
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+#include <touch_offload.h>
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+#include <heatmap.h>
+#endif
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_MAX_POINTS_SUPPORT              10 /* constant value, can't be changed */
+#define FTS_MAX_KEYS                        4
+#define FTS_KEY_DIM                         10
+#define FTS_ONE_TCH_LEN                     6
+#define FTS_TOUCH_DATA_LEN  (FTS_MAX_POINTS_SUPPORT * FTS_ONE_TCH_LEN + 3)
+
+#define FTS_GESTURE_POINTS_MAX              1
+#define FTS_GESTURE_DATA_LEN                14
+#define FTS_GESTURE_TOTAL_DATA_SIZE         (FTS_GESTURE_POINTS_MAX * FTS_GESTURE_DATA_LEN)
+
+#define FTS_MAX_ID                          0x0A
+#define FTS_TOUCH_X_H_POS                   3
+#define FTS_TOUCH_X_L_POS                   4
+#define FTS_TOUCH_Y_H_POS                   5
+#define FTS_TOUCH_Y_L_POS                   6
+#define FTS_TOUCH_PRE_POS                   7
+#define FTS_TOUCH_AREA_POS                  8
+#define FTS_TOUCH_POINT_NUM                 2
+#define FTS_TOUCH_EVENT_POS                 3
+#define FTS_TOUCH_ID_POS                    5
+#define FTS_COORDS_ARR_SIZE                 4
+#define FTS_X_MIN_DISPLAY_DEFAULT           0
+#define FTS_Y_MIN_DISPLAY_DEFAULT           0
+#define FTS_X_MAX_DISPLAY_DEFAULT           720
+#define FTS_Y_MAX_DISPLAY_DEFAULT           1280
+
+#define FTS_TOUCH_DOWN                      0
+#define FTS_TOUCH_UP                        1
+#define FTS_TOUCH_CONTACT                   2
+#define EVENT_DOWN(flag)                    ((FTS_TOUCH_DOWN == flag) || (FTS_TOUCH_CONTACT == flag))
+#define EVENT_UP(flag)                      (FTS_TOUCH_UP == flag)
+#define EVENT_NO_DOWN(data)                 (!data->point_num)
+
+#define FTS_MAX_COMPATIBLE_TYPE             4
+#define FTS_MAX_COMMMAND_LENGTH             16
+
+
+/*****************************************************************************
+*  Alternative mode (When something goes wrong, the modules may be able to solve the problem.)
+*****************************************************************************/
+/*
+ * For commnication error in PM(deep sleep) state
+ */
+#define FTS_PATCH_COMERR_PM                     0
+#define FTS_TIMEOUT_COMERR_PM                   700
+
+#define FTS_HIGH_REPORT                         0
+#define FTS_SIZE_DEFAULT                        15
+
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+struct ftxxxx_proc {
+    struct proc_dir_entry *proc_entry;
+    u8 opmode;
+    u8 cmd_len;
+    u8 cmd[FTS_MAX_COMMMAND_LENGTH];
+};
+
+struct fts_ts_platform_data {
+    u32 irq_gpio;
+    u32 irq_gpio_flags;
+    u32 reset_gpio;
+    u32 reset_gpio_flags;
+    struct drm_panel *panel;
+    u32 initial_panel_index;
+    bool have_key;
+    u32 key_number;
+    u32 keys[FTS_MAX_KEYS];
+    u32 key_y_coords[FTS_MAX_KEYS];
+    u32 key_x_coords[FTS_MAX_KEYS];
+    u32 x_max;
+    u32 y_max;
+    u32 x_min;
+    u32 y_min;
+    u32 max_touch_number;
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    u32 offload_id;
+#endif
+    u32 tx_ch_num;
+    u32 rx_ch_num;
+    /* convert mm to pixel for major and minor */
+    u8 mm2px;
+};
+
+struct ts_event {
+    int x;      /*x coordinate */
+    int y;      /*y coordinate */
+    int p;      /* pressure */
+    int flag;   /* touch event flag: 0 -- down; 1-- up; 2 -- contact */
+    int id;     /*touch ID */
+    int area;
+    int major;
+    int minor;
+};
+
+struct pen_event {
+    int inrange;
+    int tip;
+    int x;      /*x coordinate */
+    int y;      /*y coordinate */
+    int p;      /* pressure */
+    int flag;   /* touch event flag: 0 -- down; 1-- up; 2 -- contact */
+    int id;     /*touch ID */
+    int tilt_x;
+    int tilt_y;
+    int tool_type;
+};
+
+/* Motion filter finite state machine (FSM) states
+ * MF_FILTERED        - default coordinate filtering
+ * MF_UNFILTERED      - unfiltered single-touch coordinates
+ * MF_FILTERED_LOCKED - filtered coordinates. Locked until touch is lifted.
+ */
+typedef enum {
+    MF_FILTERED,
+    MF_UNFILTERED,
+    MF_FILTERED_LOCKED,
+} motion_filter_state_t;
+
+/* Motion filter mode.
+ *  MF_OFF    : 0 = Always unfilter.
+ *  MF_DYNAMIC: 1 = Dynamic change motion filter.
+ *  MF_ON     : 2 = Always filter by touch FW.
+ */
+enum MF_MODE {
+    MF_OFF,
+    MF_DYNAMIC,
+    MF_ON,
+};
+
+/*
+* gesture_id    - mean which gesture is recognised
+* point_num     - points number of this gesture
+* coordinate_x  - All gesture point x coordinate
+* coordinate_y  - All gesture point y coordinate
+* major         - All gesture major value
+* minor         - All gesture minor value
+* orientation   - All gesture orientation value
+*/
+struct fts_gesture_st {
+    u8 gesture_id;
+    u8 point_num;
+    int coordinate_x[FTS_GESTURE_POINTS_MAX];
+    int coordinate_y[FTS_GESTURE_POINTS_MAX];
+    int major[FTS_GESTURE_POINTS_MAX];
+    int minor[FTS_GESTURE_POINTS_MAX];
+    int orientation[FTS_GESTURE_POINTS_MAX];
+};
+
+enum SS_TYPE {
+    SS_NORMAL,
+    SS_WATER,
+};
+
+struct fts_ts_data {
+    struct i2c_client *client;
+    struct spi_device *spi;
+    struct device *dev;
+    struct input_dev *input_dev;
+    struct input_dev *pen_dev;
+    struct fts_ts_platform_data *pdata;
+    struct ts_ic_info ic_info;
+    struct workqueue_struct *ts_workqueue;
+    struct work_struct fwupg_work;
+    struct delayed_work esdcheck_work;
+    struct delayed_work prc_work;
+    struct work_struct resume_work;
+    struct work_struct suspend_work;
+    struct pm_qos_request pm_qos_req;
+    struct ftxxxx_proc proc;
+    spinlock_t irq_lock;
+    struct mutex report_mutex;
+    struct mutex bus_lock;
+    struct mutex reg_lock;
+    struct mutex device_mutex;
+    struct completion bus_resumed;
+    struct fts_gesture_st fts_gesture_data;
+    unsigned long intr_jiffies;
+    int irq;
+    int log_level;
+    int fw_is_running;      /* confirm fw is running when using spi:default 0 */
+    int dummy_byte;
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+    struct completion pm_completion;
+    bool pm_suspend;
+#endif
+    bool suspended;
+    bool fw_loading;
+    bool irq_disabled;
+    bool power_disabled;
+    bool glove_mode;
+    bool cover_mode;
+    bool charger_mode;
+    bool gesture_mode;      /* gesture enable or disable, default: disable */
+    bool prc_mode;
+    bool driver_probed;
+    struct pen_event pevent;
+    /* multi-touch */
+    struct ts_event *events;
+    u8 *bus_tx_buf;
+    u8 *bus_rx_buf;
+    int bus_type;
+    u8 *point_buf;
+    int pnt_buf_size;
+    int touchs;
+    int key_state;
+    int touch_point;
+    int point_num;
+
+#if GOOGLE_REPORT_MODE
+    u8 current_host_status[FTS_CUSTOMER_STATUS_LEN];
+#endif
+
+    /* Motion filter mode.
+     *  MF_OFF    : 0 = Always unfilter.
+     *  MF_DYNAMIC: 1 = Dynamic change motion filter.
+     *  MF_ON     : 2 = Always filter by touch FW.
+     */
+    u8 mf_mode;
+    /* Payload for continuously report. */
+    u8 set_continuously_report;
+    /* Motion filter finite state machine (FSM) state */
+    motion_filter_state_t mf_state;
+    /* Time of initial single-finger touch down. This timestamp is used to
+     * compute the duration a single finger is touched before it is lifted.
+     */
+    ktime_t mf_downtime;
+    ktime_t bugreport_ktime_start;
+    u8 work_mode;
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    u8 fw_heatmap_mode;
+    u8 fw_default_heatmap_mode;
+    int compress_heatmap_wlen;
+#endif
+    u8 enable_fw_grip;
+    u8 enable_fw_palm;
+    ktime_t isr_timestamp; /* Time that the event was first received from the
+                        * touch IC, acquired during hard interrupt, in
+                        * CLOCK_MONOTONIC */
+    ktime_t coords_timestamp;
+    bool is_deepsleep;
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    u8 *heatmap_raw;
+    u16 heatmap_raw_size;
+    u8 *trans_raw;
+    u16 trans_raw_size;
+    u16 *heatmap_buff;
+    u16 heatmap_buff_size;
+    u8 self_sensing_type;
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
+    struct touch_offload_context offload;
+    struct touch_offload_frame *reserved_frame;
+    u8 touch_offload_active_coords;
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+    struct v4l2_heatmap v4l2;
+#endif
+    struct proc_dir_entry *proc_touch_entry;
+    struct regulator *avdd;
+    struct regulator *dvdd;
+#if FTS_PINCTRL_EN
+    struct pinctrl *pinctrl;
+    struct pinctrl_state *pins_active;
+    struct pinctrl_state *pins_suspend;
+#endif
+#if defined(CONFIG_FB) || defined(CONFIG_DRM)
+    struct notifier_block fb_notif;
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+    struct early_suspend early_suspend;
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+    volatile int power_status;
+    u16 bus_refmask;
+    struct mutex bus_mutex;
+    struct drm_bridge panel_bridge;
+    struct drm_connector *connector;
+    bool is_panel_lp_mode;
+    int display_refresh_rate;
+#endif
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    u32 tbn_register_mask;
+    u8  tbn_owner;
+#endif
+};
+
+enum FTS_BUS_TYPE {
+    FTS_BUS_TYPE_NONE,
+    FTS_BUS_TYPE_I2C,
+    FTS_BUS_TYPE_SPI,
+    FTS_BUS_TYPE_SPI_V2,
+};
+
+#if GOOGLE_REPORT_MODE
+enum FTS_CUSTOMER_STATUS {
+    STATUS_BASELINE_REFRESH_B0,
+    STATUS_BASELINE_REFRESH_B1,
+    STATUS_PALM,
+    STATUS_WATER,
+    STATUS_GRIP,
+    STATUS_GLOVE,
+    STATUS_EDGE_PALM,
+    STATUS_RESET,
+    STATUS_CNT_END,
+};
+
+enum FTS_FW_MODE_SETTING{
+    FW_GLOVE = 0,
+    FW_GRIP,
+    FW_PALM,
+    FW_HEATMAP,
+    FW_CONTINUOUS,
+    FW_CNT_END,
+};
+#endif
+
+enum FTS_SCAN_MODE {
+    MODE_AUTO,
+    MODE_NORMAL_ACTIVE,
+    MODE_NORMAL_IDLE,
+    MODE_LOW_POWER_ACTIVE,
+    MODE_LOW_POWER_IDLE,
+    MODE_CNT,
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+extern struct fts_ts_data *fts_data;
+
+/* communication interface */
+int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen);
+int fts_read_reg(u8 addr, u8 *value);
+int fts_write(u8 *writebuf, u32 writelen);
+int fts_write_reg(u8 addr, u8 value);
+void fts_hid2std(void);
+int fts_bus_init(struct fts_ts_data *ts_data);
+int fts_bus_exit(struct fts_ts_data *ts_data);
+int fts_spi_transfer_direct(u8 *writebuf, u32 writelen, u8 *readbuf, u32 readlen);
+
+/* Gesture functions */
+int fts_gesture_init(struct fts_ts_data *ts_data);
+int fts_gesture_exit(struct fts_ts_data *ts_data);
+void fts_gesture_recovery(struct fts_ts_data *ts_data);
+int fts_gesture_readdata(struct fts_ts_data *ts_data, bool is_report);
+int fts_gesture_suspend(struct fts_ts_data *ts_data);
+int fts_gesture_resume(struct fts_ts_data *ts_data);
+
+/* Heatmap and Offload*/
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
+    IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
+int fts_get_default_heatmap_mode(struct fts_ts_data *ts_data);
+int fts_set_heatmap_mode(struct fts_ts_data *ts_data, u8 heatmap_mode);
+#endif
+int fts_set_grip_mode(struct fts_ts_data *ts_datam, u8 grip_mode);
+int fts_set_palm_mode(struct fts_ts_data *ts_data, u8 palm_mode);
+int fts_set_continuous_mode(struct fts_ts_data *ts_data, bool en);
+int fts_set_glove_mode(struct fts_ts_data *ts_data, bool en);
+
+/* Apk and functions */
+int fts_create_apk_debug_channel(struct fts_ts_data *);
+void fts_release_apk_debug_channel(struct fts_ts_data *);
+
+/* ADB functions */
+int fts_create_sysfs(struct fts_ts_data *ts_data);
+int fts_remove_sysfs(struct fts_ts_data *ts_data);
+
+/* ESD */
+#if FTS_ESDCHECK_EN
+int fts_esdcheck_init(struct fts_ts_data *ts_data);
+int fts_esdcheck_exit(struct fts_ts_data *ts_data);
+int fts_esdcheck_switch(bool enable);
+int fts_esdcheck_proc_busy(bool proc_debug);
+int fts_esdcheck_set_intr(bool intr);
+int fts_esdcheck_suspend(void);
+int fts_esdcheck_resume(void);
+#endif
+
+/* Production test */
+#if FTS_TEST_EN
+int fts_test_init(struct fts_ts_data *ts_data);
+int fts_test_exit(struct fts_ts_data *ts_data);
+#endif
+
+/* Point Report Check*/
+#if FTS_POINT_REPORT_CHECK_EN
+int fts_point_report_check_init(struct fts_ts_data *ts_data);
+int fts_point_report_check_exit(struct fts_ts_data *ts_data);
+void fts_prc_queue_work(struct fts_ts_data *ts_data);
+#endif
+
+/* FW upgrade */
+int fts_fwupg_init(struct fts_ts_data *ts_data);
+int fts_fwupg_exit(struct fts_ts_data *ts_data);
+int fts_upgrade_bin(char *fw_name, bool force);
+int fts_enter_test_environment(bool test_state);
+
+/* Other */
+int fts_reset_proc(int hdelayms);
+int fts_check_cid(struct fts_ts_data *ts_data, u8 id_h);
+int fts_wait_tp_to_valid(void);
+void fts_release_all_finger(void);
+void fts_tp_state_recovery(struct fts_ts_data *ts_data);
+int fts_ex_mode_init(struct fts_ts_data *ts_data);
+int fts_ex_mode_exit(struct fts_ts_data *ts_data);
+int fts_ex_mode_recovery(struct fts_ts_data *ts_data);
+void fts_update_feature_setting(struct fts_ts_data *ts_data);
+void fts_irq_disable(void);
+void fts_irq_enable(void);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+/* Bus reference tracking */
+int fts_ts_set_bus_ref(struct fts_ts_data *ts, u16 ref, bool enable);
+#endif
+
+#endif /* __LINUX_FOCALTECH_CORE_H__ */
diff --git a/ft3658/focaltech_esdcheck.c b/ft3658/focaltech_esdcheck.c
new file mode 100644
index 0000000..8bc40e8
--- /dev/null
+++ b/ft3658/focaltech_esdcheck.c
@@ -0,0 +1,462 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_esdcheck.c
+*
+*    Author: Focaltech Driver Team
+*
+*   Created: 2016-08-03
+*
+*  Abstract: ESD check function
+*
+*   Version: v1.0
+*
+* Revision History:
+*        v1.0:
+*            First release. By luougojin 2016-08-03
+*        v1.1: By luougojin 2017-02-15
+*            1. Add LCD_ESD_PATCH to control idc_esdcheck_lcderror
+*****************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+#if FTS_ESDCHECK_EN
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define ESDCHECK_WAIT_TIME              1000    /* ms */
+#define LCD_ESD_PATCH                   0
+#define ESDCHECK_INTRCNT_MAX            2
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+struct fts_esdcheck_st {
+    u8      mode                : 1;    /* 1- need check esd 0- no esd check */
+    u8      suspend             : 1;
+    u8      proc_debug          : 1;    /* apk or adb use */
+    u8      intr                : 1;    /* 1- Interrupt trigger */
+    u8      unused              : 4;
+    u8      intr_cnt;
+    u8      flow_work_hold_cnt;         /* Flow Work Cnt(reg0x91) keep a same value for x times. >=5 times is ESD, need reset */
+    u8      flow_work_cnt_last;         /* Save Flow Work Cnt(reg0x91) value */
+    u32     hardware_reset_cnt;
+    u32     nack_cnt;
+    u32     dataerror_cnt;
+};
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+static struct fts_esdcheck_st fts_esdcheck_data;
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+#if LCD_ESD_PATCH
+int lcd_need_reset;
+static int tp_need_recovery; /* LCD reset cause Tp reset */
+int idc_esdcheck_lcderror(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    u8 val = 0;
+
+    FTS_DEBUG("check LCD ESD");
+    if ( (tp_need_recovery == 1) && (lcd_need_reset == 0) ) {
+        tp_need_recovery = 0;
+        /* LCD reset, need recover TP state */
+        fts_release_all_finger();
+        fts_tp_state_recovery(ts_data);
+    }
+
+    ret = fts_read_reg(FTS_REG_ESD_SATURATE, &val);
+    if ( ret < 0) {
+        FTS_ERROR("read reg0xED fail,ret:%d", ret);
+        return -EIO;
+    }
+
+    if (val == 0xAA) {
+        /*
+        * 1. Set flag lcd_need_reset = 1;
+        * 2. LCD driver need reset(recovery) LCD and set lcd_need_reset to 0
+        * 3. recover TP state
+        */
+        FTS_INFO("LCD ESD, need execute LCD reset");
+        lcd_need_reset = 1;
+        tp_need_recovery = 1;
+    }
+
+    return 0;
+}
+#endif
+
+static int fts_esdcheck_tp_reset(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    fts_esdcheck_data.flow_work_hold_cnt = 0;
+    fts_esdcheck_data.hardware_reset_cnt++;
+
+    fts_reset_proc(200);
+    fts_release_all_finger();
+    fts_tp_state_recovery(ts_data);
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static bool get_chip_id(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int i = 0;
+    u8 idh = 0;
+    u8 chip_id = ts_data->ic_info.ids.chip_idh;
+
+    for (i = 0; i < 3; i++) {
+        ret = fts_read_reg(FTS_REG_CHIP_ID, &idh);
+        if (ret < 0) {
+            FTS_ERROR("read chip id fail,ret:%d", ret);
+            fts_esdcheck_data.nack_cnt++;
+        } else {
+            if ((idh == chip_id) || (fts_check_cid(ts_data, idh) == 0)) {
+                break;
+            } else {
+                FTS_DEBUG("read chip_id:%x,retry:%d", idh, i);
+                fts_esdcheck_data.dataerror_cnt++;
+            }
+        }
+        msleep(10);
+    }
+
+    /* if can't get correct data in 3 times, then need hardware reset */
+    if (i >= 3) {
+        FTS_ERROR("read chip id 3 times fail, need execute TP reset");
+        return true;
+    }
+
+    return false;
+}
+
+/*****************************************************************************
+*  Name: get_flow_cnt
+*  Brief: Read flow cnt(0x91)
+*  Input:
+*  Output:
+*  Return:  1(true) - Reg 0x91(flow cnt) abnormal: hold a value for 5 times
+*           0(false) - Reg 0x91(flow cnt) normal
+*****************************************************************************/
+static bool get_flow_cnt(struct fts_ts_data *ts_data)
+{
+    int     ret = 0;
+    u8      reg_value = 0;
+    u8      reg_addr = 0;
+
+    reg_addr = FTS_REG_FLOW_WORK_CNT;
+    ret = fts_read(&reg_addr, 1, &reg_value, 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg0x91 fail,ret:%d", ret);
+        fts_esdcheck_data.nack_cnt++;
+    } else {
+        if ( reg_value == fts_esdcheck_data.flow_work_cnt_last ) {
+            FTS_DEBUG("reg0x91,val:%x,last:%x", reg_value,
+                      fts_esdcheck_data.flow_work_cnt_last);
+            fts_esdcheck_data.flow_work_hold_cnt++;
+        } else {
+            fts_esdcheck_data.flow_work_hold_cnt = 0;
+        }
+
+        fts_esdcheck_data.flow_work_cnt_last = reg_value;
+    }
+
+    /* Flow Work Cnt keep a value for 5 times, need execute TP reset */
+    if (fts_esdcheck_data.flow_work_hold_cnt >= 5) {
+        FTS_DEBUG("reg0x91 keep a value for 5 times, need execute TP reset");
+        return true;
+    }
+
+    return false;
+}
+
+static int esdcheck_algorithm(struct fts_ts_data *ts_data)
+{
+    int     ret = 0;
+    u8      reg_value = 0;
+    u8      reg_addr = 0;
+    bool    hardware_reset = 0;
+
+    /* 1. esdcheck is interrupt, then return */
+    if (fts_esdcheck_data.intr == 1) {
+        fts_esdcheck_data.intr_cnt++;
+        if (fts_esdcheck_data.intr_cnt > ESDCHECK_INTRCNT_MAX)
+            fts_esdcheck_data.intr = 0;
+        else
+            return 0;
+    }
+
+    /* 2. check power state, if suspend, no need check esd */
+    if (fts_esdcheck_data.suspend == 1) {
+        FTS_DEBUG("In suspend, not check esd");
+        /* because in suspend state, adb can be used, when upgrade FW, will
+         * active ESD check(active = 1); But in suspend, then will don't
+         * queue_delayed_work, when resume, don't check ESD again
+         */
+        return 0;
+    }
+
+    /* 3. check fts_esdcheck_data.proc_debug state, if 1-proc busy, no need check esd*/
+    if (fts_esdcheck_data.proc_debug == 1) {
+        FTS_INFO("In apk/adb command mode, not check esd");
+        return 0;
+    }
+
+    /* 4. In factory mode, can't check esd */
+    reg_addr = FTS_REG_WORKMODE;
+    ret = fts_read_reg(reg_addr, &reg_value);
+    if ( ret < 0 ) {
+        fts_esdcheck_data.nack_cnt++;
+    } else if ( (reg_value & 0x70) !=  FTS_REG_WORKMODE_WORK_VALUE) {
+        FTS_DEBUG("not in work mode(%x), no check esd", reg_value);
+        return 0;
+    }
+
+    /* 5. IDC esd check lcd  default:close */
+#if LCD_ESD_PATCH
+    idc_esdcheck_lcderror(ts_data);
+#endif
+
+    /* 6. Get Chip ID */
+    hardware_reset = get_chip_id(ts_data);
+
+    /* 7. get Flow work cnt: 0x91 If no change for 5 times, then ESD and reset */
+    if (!hardware_reset) {
+        hardware_reset = get_flow_cnt(ts_data);
+    }
+
+    /* 8. If need hardware reset, then handle it here */
+    if (hardware_reset == 1) {
+        FTS_DEBUG("NoACK=%d, Error Data=%d, Hardware Reset=%d",
+                  fts_esdcheck_data.nack_cnt,
+                  fts_esdcheck_data.dataerror_cnt,
+                  fts_esdcheck_data.hardware_reset_cnt);
+        fts_esdcheck_tp_reset(ts_data);
+    }
+
+    return 0;
+}
+
+static void esdcheck_func(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work,
+                                  struct fts_ts_data, esdcheck_work.work);
+
+    if (ENABLE == fts_esdcheck_data.mode) {
+        esdcheck_algorithm(ts_data);
+        queue_delayed_work(ts_data->ts_workqueue, &ts_data->esdcheck_work,
+                           msecs_to_jiffies(ESDCHECK_WAIT_TIME));
+    }
+
+}
+
+int fts_esdcheck_set_intr(bool intr)
+{
+    /* interrupt don't add debug message */
+    fts_esdcheck_data.intr = intr;
+    fts_esdcheck_data.intr_cnt = (u8)intr;
+    return 0;
+}
+
+static int fts_esdcheck_get_status(void)
+{
+    /* interrupt don't add debug message */
+    return fts_esdcheck_data.mode;
+}
+
+/*****************************************************************************
+*  Name: fts_esdcheck_proc_busy
+*  Brief: When APK or ADB command access TP via driver, then need set proc_debug,
+*         then will not check ESD.
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+int fts_esdcheck_proc_busy(bool proc_debug)
+{
+    fts_esdcheck_data.proc_debug = proc_debug;
+    return 0;
+}
+
+/*****************************************************************************
+*  Name: fts_esdcheck_switch
+*  Brief: FTS esd check function switch.
+*  Input:   enable:  1 - Enable esd check
+*                    0 - Disable esd check
+*  Output:
+*  Return:
+*****************************************************************************/
+int fts_esdcheck_switch(bool enable)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    FTS_FUNC_ENTER();
+    if (fts_esdcheck_data.mode == ENABLE) {
+        if (enable) {
+            FTS_DEBUG("ESD check start");
+            fts_esdcheck_data.flow_work_hold_cnt = 0;
+            fts_esdcheck_data.flow_work_cnt_last = 0;
+            fts_esdcheck_data.intr = 0;
+            fts_esdcheck_data.intr_cnt = 0;
+            queue_delayed_work(ts_data->ts_workqueue,
+                               &ts_data->esdcheck_work,
+                               msecs_to_jiffies(ESDCHECK_WAIT_TIME));
+        } else {
+            FTS_DEBUG("ESD check stop");
+            cancel_delayed_work_sync(&ts_data->esdcheck_work);
+        }
+    }
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_esdcheck_suspend(void)
+{
+    FTS_FUNC_ENTER();
+    fts_esdcheck_switch(DISABLE);
+    fts_esdcheck_data.suspend = 1;
+    fts_esdcheck_data.intr = 0;
+    fts_esdcheck_data.intr_cnt = 0;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_esdcheck_resume( void )
+{
+    FTS_FUNC_ENTER();
+    fts_esdcheck_switch(ENABLE);
+    fts_esdcheck_data.suspend = 0;
+    fts_esdcheck_data.intr = 0;
+    fts_esdcheck_data.intr_cnt = 0;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static ssize_t fts_esdcheck_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_DEBUG("enable esdcheck");
+        fts_esdcheck_data.mode = ENABLE;
+        fts_esdcheck_switch(ENABLE);
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_DEBUG("disable esdcheck");
+        fts_esdcheck_switch(DISABLE);
+        fts_esdcheck_data.mode = DISABLE;
+    }
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_esdcheck_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count = snprintf(buf, PAGE_SIZE, "Esd check: %s\n", \
+                     fts_esdcheck_get_status() ? "On" : "Off");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* sysfs esd node
+ *   read example: cat  fts_esd_mode        ---read esd mode
+ *   write example:echo 01 > fts_esd_mode   ---make esdcheck enable
+ *
+ */
+static DEVICE_ATTR (fts_esd_mode, S_IRUGO | S_IWUSR, fts_esdcheck_show, fts_esdcheck_store);
+
+static struct attribute *fts_esd_mode_attrs[] = {
+
+    &dev_attr_fts_esd_mode.attr,
+    NULL,
+};
+
+static struct attribute_group fts_esd_group = {
+    .attrs = fts_esd_mode_attrs,
+};
+
+int fts_create_esd_sysfs(struct device *dev)
+{
+    int ret = 0;
+
+    ret = sysfs_create_group(&dev->kobj, &fts_esd_group);
+    if ( ret != 0) {
+        FTS_ERROR("fts_create_esd_sysfs(sysfs) create fail");
+        sysfs_remove_group(&dev->kobj, &fts_esd_group);
+        return ret;
+    }
+    return 0;
+}
+
+int fts_esdcheck_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    if (ts_data->ts_workqueue) {
+        INIT_DELAYED_WORK(&ts_data->esdcheck_work, esdcheck_func);
+    } else {
+        FTS_ERROR("fts workqueue is NULL, can't run esd check function");
+        return -EINVAL;
+    }
+
+    memset((u8 *)&fts_esdcheck_data, 0, sizeof(struct fts_esdcheck_st));
+
+    fts_esdcheck_data.mode = ENABLE;
+    fts_esdcheck_data.intr = 0;
+    fts_esdcheck_data.intr_cnt = 0;
+    fts_esdcheck_switch(ENABLE);
+    fts_create_esd_sysfs(ts_data->dev);
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_esdcheck_exit(struct fts_ts_data *ts_data)
+{
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_esd_group);
+    return 0;
+}
+#endif /* FTS_ESDCHECK_EN */
+
diff --git a/ft3658/focaltech_ex_fun.c b/ft3658/focaltech_ex_fun.c
new file mode 100644
index 0000000..71ee61a
--- /dev/null
+++ b/ft3658/focaltech_ex_fun.c
@@ -0,0 +1,2879 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: Focaltech_ex_fun.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define PROC_UPGRADE                            0
+#define PROC_READ_REGISTER                      1
+#define PROC_WRITE_REGISTER                     2
+#define PROC_AUTOCLB                            4
+#define PROC_UPGRADE_INFO                       5
+#define PROC_WRITE_DATA                         6
+#define PROC_READ_DATA                          7
+#define PROC_SET_TEST_FLAG                      8
+#define PROC_SET_SLAVE_ADDR                     10
+#define PROC_HW_RESET                           11
+#define PROC_READ_STATUS                        12
+#define PROC_SET_BOOT_MODE                      13
+#define PROC_ENTER_TEST_ENVIRONMENT             14
+#define PROC_WRITE_DATA_DIRECT                  16
+#define PROC_READ_DATA_DIRECT                   17
+#define PROC_CONFIGURE                          18
+#define PROC_CONFIGURE_INTR                     20
+#define PROC_NAME                               "ftxxxx-debug"
+#define PROC_BUF_SIZE                           512
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+enum {
+    RWREG_OP_READ = 0,
+    RWREG_OP_WRITE = 1,
+};
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+static struct rwreg_operation_t {
+    int type;           /*  0: read, 1: write */
+    int reg;            /*  register */
+    int len;            /*  read/write length */
+    int val;            /*  length = 1; read: return value, write: op return */
+    int res;            /*  0: success, otherwise: fail */
+    char *opbuf;        /*  length >= 1, read return value, write: op return */
+} rw_op;
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+static ssize_t fts_debug_write(
+    struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
+{
+    u8 *writebuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int buflen = count;
+    int writelen = 0;
+    int ret = 0;
+    char tmp[PROC_BUF_SIZE];
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 1) {
+        FTS_ERROR("apk proc count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        writebuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == writebuf) {
+            FTS_ERROR("apk proc write buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        writebuf = tmpbuf;
+    }
+
+    if (copy_from_user(writebuf, buff, buflen)) {
+        FTS_ERROR("[APK]: copy from user error!!");
+        ret = -EFAULT;
+        goto proc_write_err;
+    }
+
+    proc->opmode = writebuf[0];
+    switch (proc->opmode) {
+    case PROC_SET_TEST_FLAG:
+        FTS_DEBUG("[APK]: PROC_SET_TEST_FLAG = %x", writebuf[1]);
+        if (writebuf[1] == 0) {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(ENABLE);
+#endif
+        } else {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(DISABLE);
+#endif
+        }
+        break;
+
+    case PROC_READ_REGISTER:
+        proc->cmd[0] = writebuf[1];
+        break;
+
+    case PROC_WRITE_REGISTER:
+        ret = fts_write_reg(writebuf[1], writebuf[2]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_REGISTER write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_READ_DATA:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA:
+        writelen = buflen - 1;
+        ret = fts_write(writebuf + 1, writelen);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_SET_SLAVE_ADDR:
+        break;
+
+    case PROC_HW_RESET:
+        if (buflen < PROC_BUF_SIZE) {
+            snprintf(tmp, PROC_BUF_SIZE, "%s", writebuf + 1);
+            tmp[buflen - 1] = '\0';
+            if (strncmp(tmp, "focal_driver", 12) == 0) {
+                FTS_INFO("APK execute HW Reset");
+                fts_reset_proc(0);
+            }
+        }
+        break;
+
+    case PROC_SET_BOOT_MODE:
+        FTS_DEBUG("[APK]: PROC_SET_BOOT_MODE = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            ts_data->fw_is_running = true;
+        } else {
+            ts_data->fw_is_running = false;
+        }
+        break;
+    case PROC_ENTER_TEST_ENVIRONMENT:
+        FTS_DEBUG("[APK]: PROC_ENTER_TEST_ENVIRONMENT = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            fts_enter_test_environment(0);
+        } else {
+            fts_enter_test_environment(1);
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA_DIRECT) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA_DIRECT:
+        writelen = buflen - 1;
+        ret = fts_spi_transfer_direct(writebuf + 1, writelen, NULL, 0);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA_DIRECT write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE:
+        ts_data->spi->mode = writebuf[1];
+        ts_data->spi->bits_per_word = writebuf[2];
+        ts_data->spi->max_speed_hz = *(u32 *)(writebuf + 4);
+        FTS_INFO("spi,mode=%d,bits=%d,speed=%d", ts_data->spi->mode,
+                 ts_data->spi->bits_per_word, ts_data->spi->max_speed_hz);
+        ret = spi_setup(ts_data->spi);
+        if (ret) {
+            FTS_ERROR("spi setup fail");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE_INTR:
+        if (writebuf[1] == 0)
+            fts_irq_disable();
+        else
+            fts_irq_enable();
+        break;
+
+    default:
+        break;
+    }
+
+    ret = buflen;
+proc_write_err:
+    if ((buflen > PROC_BUF_SIZE) && writebuf) {
+        kfree(writebuf);
+        writebuf = NULL;
+    }
+    return ret;
+}
+
+static ssize_t fts_debug_read(
+    struct file *filp, char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    int num_read_chars = 0;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = tmpbuf;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+
+    switch (proc->opmode) {
+    case PROC_READ_REGISTER:
+        num_read_chars = 1;
+        ret = fts_read_reg(proc->cmd[0], &readbuf[0]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_REGISTER read error");
+            goto proc_read_err;
+        }
+        break;
+    case PROC_WRITE_REGISTER:
+        break;
+
+    case PROC_READ_DATA:
+        num_read_chars = buflen;
+        ret = fts_read(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        num_read_chars = buflen;
+        ret = fts_spi_transfer_direct(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA_DIRECT read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_WRITE_DATA:
+        break;
+
+    default:
+        break;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+
+    ret = num_read_chars;
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops fts_proc_fops = {
+    .proc_read   = fts_debug_read,
+    .proc_write  = fts_debug_write,
+};
+#else
+static const struct file_operations fts_proc_fops = {
+    .owner  = THIS_MODULE,
+    .read   = fts_debug_read,
+    .write  = fts_debug_write,
+};
+#endif
+
+#else
+static int fts_debug_write(
+    struct file *filp, const char __user *buff, unsigned long len, void *data)
+{
+    u8 *writebuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int buflen = count;
+    int writelen = 0;
+    int ret = 0;
+    char tmp[PROC_BUF_SIZE];
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 1) {
+        FTS_ERROR("apk proc write count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        writebuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == writebuf) {
+            FTS_ERROR("apk proc write buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        writebuf = tmpbuf;
+    }
+
+    if (copy_from_user(writebuf, buff, buflen)) {
+        FTS_ERROR("[APK]: copy from user error!!");
+        ret = -EFAULT;
+        goto proc_write_err;
+    }
+
+    proc->opmode = writebuf[0];
+    switch (proc->opmode) {
+    case PROC_SET_TEST_FLAG:
+        FTS_DEBUG("[APK]: PROC_SET_TEST_FLAG = %x", writebuf[1]);
+        if (writebuf[1] == 0) {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(ENABLE);
+#endif
+        } else {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(DISABLE);
+#endif
+        }
+        break;
+
+    case PROC_READ_REGISTER:
+        proc->cmd[0] = writebuf[1];
+        break;
+
+    case PROC_WRITE_REGISTER:
+        ret = fts_write_reg(writebuf[1], writebuf[2]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_REGISTER write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_READ_DATA:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA:
+        writelen = buflen - 1;
+        ret = fts_write(writebuf + 1, writelen);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_SET_SLAVE_ADDR:
+        break;
+
+    case PROC_HW_RESET:
+        if (buflen < PROC_BUF_SIZE) {
+            snprintf(tmp, PROC_BUF_SIZE, "%s", writebuf + 1);
+            tmp[buflen - 1] = '\0';
+            if (strncmp(tmp, "focal_driver", 12) == 0) {
+                FTS_INFO("APK execute HW Reset");
+                fts_reset_proc(0);
+            }
+        }
+        break;
+
+    case PROC_SET_BOOT_MODE:
+        FTS_DEBUG("[APK]: PROC_SET_BOOT_MODE = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            ts_data->fw_is_running = true;
+        } else {
+            ts_data->fw_is_running = false;
+        }
+        break;
+    case PROC_ENTER_TEST_ENVIRONMENT:
+        FTS_DEBUG("[APK]: PROC_ENTER_TEST_ENVIRONMENT = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            fts_enter_test_environment(0);
+        } else {
+            fts_enter_test_environment(1);
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA_DIRECT) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA_DIRECT:
+        writelen = buflen - 1;
+        ret = fts_spi_transfer_direct(writebuf + 1, writelen, NULL, 0);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA_DIRECT write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE:
+        ts_data->spi->mode = writebuf[1];
+        ts_data->spi->bits_per_word = writebuf[2];
+        ts_data->spi->max_speed_hz = *(u32 *)(writebuf + 4);
+        FTS_INFO("spi,mode=%d,bits=%d,speed=%d", ts_data->spi->mode,
+                 ts_data->spi->bits_per_word, ts_data->spi->max_speed_hz);
+        ret = spi_setup(ts_data->spi);
+        if (ret) {
+            FTS_ERROR("spi setup fail");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE_INTR:
+        if (writebuf[1] == 0)
+            fts_irq_disable();
+        else
+            fts_irq_enable();
+        break;
+
+    default:
+        break;
+    }
+
+    ret = buflen;
+proc_write_err:
+    if ((buflen > PROC_BUF_SIZE) && writebuf) {
+        kfree(writebuf);
+        writebuf = NULL;
+    }
+    return ret;
+}
+
+static int fts_debug_read(
+    char *page, char **start, off_t off, int count, int *eof, void *data )
+{
+    int ret = 0;
+    int num_read_chars = 0;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = tmpbuf;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+
+    switch (proc->opmode) {
+    case PROC_READ_REGISTER:
+        num_read_chars = 1;
+        ret = fts_read_reg(proc->cmd[0], &readbuf[0]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_REGISTER read error");
+            goto proc_read_err;
+        }
+        break;
+    case PROC_WRITE_REGISTER:
+        break;
+
+    case PROC_READ_DATA:
+        num_read_chars = buflen;
+        ret = fts_read(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        num_read_chars = buflen;
+        ret = fts_spi_transfer_direct(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA_DIRECT read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_WRITE_DATA:
+        break;
+
+    default:
+        break;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+
+    ret = num_read_chars;
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+#endif
+
+int fts_create_apk_debug_channel(struct fts_ts_data *ts_data)
+{
+    struct ftxxxx_proc *proc = &ts_data->proc;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+    proc->proc_entry = proc_create(PROC_NAME, 0777, NULL, &fts_proc_fops);
+    if (NULL == proc->proc_entry) {
+        FTS_ERROR("create proc entry fail");
+        return -ENOMEM;
+    }
+#else
+    proc->proc_entry = create_proc_entry(PROC_NAME, 0777, NULL);
+    if (NULL == proc->proc_entry) {
+        FTS_ERROR("create proc entry fail");
+        return -ENOMEM;
+    }
+    proc->proc_entry->write_proc = fts_debug_write;
+    proc->proc_entry->read_proc = fts_debug_read;
+#endif
+
+    FTS_INFO("Create proc entry success!");
+    return 0;
+}
+
+void fts_release_apk_debug_channel(struct fts_ts_data *ts_data)
+{
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (proc->proc_entry) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+        proc_remove(proc->proc_entry);
+#else
+        remove_proc_entry(PROC_NAME, NULL);
+#endif
+    }
+}
+
+/************************************************************************
+ * sysfs interface
+ ***********************************************************************/
+/* fts_hw_reset interface */
+static ssize_t fts_hw_reset_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+    ssize_t count = 0;
+
+    mutex_lock(&input_dev->mutex);
+    fts_reset_proc(0);
+    count = snprintf(buf, PAGE_SIZE, "hw reset executed\n");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_hw_reset_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_irq interface */
+static ssize_t fts_irq_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    ssize_t count = 0;
+
+    count = snprintf(buf, PAGE_SIZE, "irq_enable:%d\n",
+        !fts_data->irq_disabled);
+
+    return count;
+}
+
+static ssize_t fts_irq_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_INFO("enable irq");
+        fts_irq_enable();
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_INFO("disable irq");
+        fts_irq_disable();
+    }
+    mutex_unlock(&input_dev->mutex);
+    return count;
+}
+
+/* fts_boot_mode interface */
+static ssize_t fts_bootmode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    mutex_lock(&input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_INFO("[EX-FUN]set to boot mode");
+        fts_data->fw_is_running = false;
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_INFO("[EX-FUN]set to fw mode");
+        fts_data->fw_is_running = true;
+    }
+    mutex_unlock(&input_dev->mutex);
+    FTS_FUNC_EXIT();
+
+    return count;
+}
+
+static ssize_t fts_bootmode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    ssize_t count = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    mutex_lock(&input_dev->mutex);
+    if (true == fts_data->fw_is_running) {
+        count = snprintf(buf, PAGE_SIZE, "tp is in fw mode\n");
+    } else {
+        count = snprintf(buf, PAGE_SIZE, "tp is in boot mode\n");
+    }
+    mutex_unlock(&input_dev->mutex);
+    FTS_FUNC_EXIT();
+
+    return count;
+}
+
+/* fts_tpfwver interface */
+static ssize_t fts_tpfwver_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+    ssize_t num_read_chars = 0;
+    u8 fw_major_ver = 0;
+    u8 fw_minor_ver = 0;
+
+    mutex_lock(&input_dev->mutex);
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+    ret = fts_read_reg(FTS_REG_FW_MAJOR_VER, &fw_major_ver);
+    if ((ret < 0) || (fw_major_ver == 0xFF) || (fw_major_ver == 0x00)) {
+        num_read_chars = snprintf(buf, PAGE_SIZE,
+                                  "get tp fw major version fail!\n");
+        mutex_unlock(&input_dev->mutex);
+        return num_read_chars;
+    }
+    ret = fts_read_reg(FTS_REG_FW_MINOR_VER, &fw_minor_ver);
+    if (ret < 0) {
+        num_read_chars = snprintf(buf, PAGE_SIZE,
+                                  "get tp fw minor version fail!\n");
+        mutex_unlock(&input_dev->mutex);
+        return num_read_chars;
+    }
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+    num_read_chars = snprintf(buf, PAGE_SIZE, "V%02x_D%02x\n", fw_major_ver,
+        fw_minor_ver);
+
+    mutex_unlock(&input_dev->mutex);
+    return num_read_chars;
+}
+
+static ssize_t fts_tpfwver_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_rw_reg */
+static ssize_t fts_tprwreg_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count;
+    int i;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+
+    if (rw_op.len < 0) {
+        count = snprintf(buf, PAGE_SIZE, "Invalid cmd line\n");
+    } else if (rw_op.len == 1) {
+        if (RWREG_OP_READ == rw_op.type) {
+            if (rw_op.res == 0) {
+                count = snprintf(buf, PAGE_SIZE, "Read %02X: %02X\n", rw_op.reg, rw_op.val);
+            } else {
+                count = snprintf(buf, PAGE_SIZE, "Read %02X failed, ret: %d\n", rw_op.reg,  rw_op.res);
+            }
+        } else {
+            if (rw_op.res == 0) {
+                count = snprintf(buf, PAGE_SIZE, "Write %02X, %02X success\n", rw_op.reg,  rw_op.val);
+            } else {
+                count = snprintf(buf, PAGE_SIZE, "Write %02X failed, ret: %d\n", rw_op.reg,  rw_op.res);
+            }
+        }
+    } else {
+        if (RWREG_OP_READ == rw_op.type) {
+            count = snprintf(buf, PAGE_SIZE, "Read Reg: [%02X]-[%02X]\n", rw_op.reg, rw_op.reg + rw_op.len);
+            count += snprintf(buf + count, PAGE_SIZE, "Result: ");
+            if (rw_op.res) {
+                count += snprintf(buf + count, PAGE_SIZE, "failed, ret: %d\n", rw_op.res);
+            } else {
+                if (rw_op.opbuf) {
+                    for (i = 0; i < rw_op.len; i++) {
+                        count += snprintf(buf + count, PAGE_SIZE, "%02X ", rw_op.opbuf[i]);
+                    }
+                    count += snprintf(buf + count, PAGE_SIZE, "\n");
+                }
+            }
+        } else {
+            ;
+            count = snprintf(buf, PAGE_SIZE, "Write Reg: [%02X]-[%02X]\n", rw_op.reg, rw_op.reg + rw_op.len - 1);
+            count += snprintf(buf + count, PAGE_SIZE, "Write Data: ");
+            if (rw_op.opbuf) {
+                for (i = 1; i < rw_op.len; i++) {
+                    count += snprintf(buf + count, PAGE_SIZE, "%02X ", rw_op.opbuf[i]);
+                }
+                count += snprintf(buf + count, PAGE_SIZE, "\n");
+            }
+            if (rw_op.res) {
+                count += snprintf(buf + count, PAGE_SIZE, "Result: failed, ret: %d\n", rw_op.res);
+            } else {
+                count += snprintf(buf + count, PAGE_SIZE, "Result: success\n");
+            }
+        }
+        /*if (rw_op.opbuf) {
+            kfree(rw_op.opbuf);
+            rw_op.opbuf = NULL;
+        }*/
+    }
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static int shex_to_int(const char *hex_buf, int size)
+{
+    int i;
+    int base = 1;
+    int value = 0;
+    char single;
+
+    for (i = size - 1; i >= 0; i--) {
+        single = hex_buf[i];
+
+        if ((single >= '0') && (single <= '9')) {
+            value += (single - '0') * base;
+        } else if ((single >= 'a') && (single <= 'z')) {
+            value += (single - 'a' + 10) * base;
+        } else if ((single >= 'A') && (single <= 'Z')) {
+            value += (single - 'A' + 10) * base;
+        } else {
+            return -EINVAL;
+        }
+
+        base *= 16;
+    }
+
+    return value;
+}
+
+
+static u8 shex_to_u8(const char *hex_buf, int size)
+{
+    return (u8)shex_to_int(hex_buf, size);
+}
+/*
+ * Format buf:
+ * [0]: '0' write, '1' read(reserved)
+ * [1-2]: addr, hex
+ * [3-4]: length, hex
+ * [5-6]...[n-(n+1)]: data, hex
+ */
+static int fts_parse_buf(const char *buf, size_t cmd_len)
+{
+    int length;
+    int i;
+    char *tmpbuf;
+
+    rw_op.reg = shex_to_u8(buf + 1, 2);
+    length = shex_to_int(buf + 3, 2);
+
+    if (buf[0] == '1') {
+        rw_op.len = length;
+        rw_op.type = RWREG_OP_READ;
+        FTS_DEBUG("read %02X, %d bytes", rw_op.reg, rw_op.len);
+    } else {
+        if (cmd_len < (length * 2 + 5)) {
+            pr_err("data invalided!\n");
+            return -EINVAL;
+        }
+        FTS_DEBUG("write %02X, %d bytes", rw_op.reg, length);
+
+        /* first byte is the register addr */
+        rw_op.type = RWREG_OP_WRITE;
+        rw_op.len = length + 1;
+    }
+
+    if (rw_op.len > 0) {
+        tmpbuf = (char *)kzalloc(rw_op.len, GFP_KERNEL);
+        if (!tmpbuf) {
+            FTS_ERROR("allocate memory failed!\n");
+            return -ENOMEM;
+        }
+
+        if (RWREG_OP_WRITE == rw_op.type) {
+            tmpbuf[0] = rw_op.reg & 0xFF;
+            FTS_DEBUG("write buffer: ");
+            for (i = 1; i < rw_op.len; i++) {
+                tmpbuf[i] = shex_to_u8(buf + 5 + i * 2 - 2, 2);
+                FTS_DEBUG("buf[%d]: %02X", i, tmpbuf[i] & 0xFF);
+            }
+        }
+        rw_op.opbuf = tmpbuf;
+    }
+
+    return rw_op.len;
+}
+
+static ssize_t fts_tprwreg_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+    ssize_t cmd_length = 0;
+
+    mutex_lock(&input_dev->mutex);
+    cmd_length = count - 1;
+
+    if (rw_op.opbuf) {
+        kfree(rw_op.opbuf);
+        rw_op.opbuf = NULL;
+    }
+
+    FTS_DEBUG("cmd len: %d, buf: %s", (int)cmd_length, buf);
+    /* compatible old ops */
+    if (2 == cmd_length) {
+        rw_op.type = RWREG_OP_READ;
+        rw_op.len = 1;
+        rw_op.reg = shex_to_int(buf, 2);
+    } else if (4 == cmd_length) {
+        rw_op.type = RWREG_OP_WRITE;
+        rw_op.len = 1;
+        rw_op.reg = shex_to_int(buf, 2);
+        rw_op.val = shex_to_int(buf + 2, 2);
+    } else if (cmd_length < 5) {
+        FTS_ERROR("Invalid cmd buffer");
+        mutex_unlock(&input_dev->mutex);
+        return -EINVAL;
+    } else {
+        rw_op.len = fts_parse_buf(buf, cmd_length);
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+    if (rw_op.len < 0) {
+        FTS_ERROR("cmd buffer error!");
+
+    } else {
+        if (RWREG_OP_READ == rw_op.type) {
+            if (rw_op.len == 1) {
+                u8 reg, val;
+                reg = rw_op.reg & 0xFF;
+                rw_op.res = fts_read_reg(reg, &val);
+                rw_op.val = val;
+            } else {
+                char reg;
+                reg = rw_op.reg & 0xFF;
+
+                rw_op.res = fts_read(&reg, 1, rw_op.opbuf, rw_op.len);
+            }
+
+            if (rw_op.res < 0) {
+                FTS_ERROR("Could not read 0x%02x", rw_op.reg);
+            } else {
+                FTS_INFO("read 0x%02x, %d bytes successful", rw_op.reg, rw_op.len);
+                rw_op.res = 0;
+            }
+
+        } else {
+            if (rw_op.len == 1) {
+                u8 reg, val;
+                reg = rw_op.reg & 0xFF;
+                val = rw_op.val & 0xFF;
+                rw_op.res = fts_write_reg(reg, val);
+            } else {
+                rw_op.res = fts_write(rw_op.opbuf, rw_op.len);
+            }
+            if (rw_op.res < 0) {
+                FTS_ERROR("Could not write 0x%02x", rw_op.reg);
+
+            } else {
+                FTS_INFO("Write 0x%02x, %d bytes successful", rw_op.val, rw_op.len);
+                rw_op.res = 0;
+            }
+        }
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* fts_upgrade_bin interface */
+static ssize_t fts_fwupgradebin_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    return -EPERM;
+}
+
+static ssize_t fts_fwupgradebin_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    if ((count <= 1) || (count >= FILE_NAME_LENGTH - 32)) {
+        FTS_ERROR("fw bin name's length(%d) fail", (int)count);
+        return -EINVAL;
+    }
+    memset(fwname, 0, sizeof(fwname));
+    snprintf(fwname, FILE_NAME_LENGTH, "%s", buf);
+    fwname[count - 1] = '\0';
+
+    FTS_INFO("upgrade with bin file through sysfs node");
+    mutex_lock(&input_dev->mutex);
+    fts_upgrade_bin(fwname, 0);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* fts_force_upgrade interface */
+static ssize_t fts_fwforceupg_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    return -EPERM;
+}
+
+static ssize_t fts_fwforceupg_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    char fwname[FILE_NAME_LENGTH];
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    if ((count <= 1) || (count >= FILE_NAME_LENGTH - 32)) {
+        FTS_ERROR("fw bin name's length(%d) fail", (int)count);
+        return -EINVAL;
+    }
+    memset(fwname, 0, sizeof(fwname));
+    snprintf(fwname, FILE_NAME_LENGTH, "%s", buf);
+    fwname[count - 1] = '\0';
+
+    FTS_INFO("force upgrade through sysfs node");
+    mutex_lock(&input_dev->mutex);
+    fts_upgrade_bin(fwname, 1);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* fts_driver_info interface */
+static ssize_t fts_driverinfo_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count += snprintf(buf + count, PAGE_SIZE, "Driver Ver:%s\n",
+                      FTS_DRIVER_VERSION);
+
+    count += snprintf(buf + count, PAGE_SIZE, "Resolution:(%d,%d)~(%d,%d)\n",
+                      pdata->x_min, pdata->y_min, pdata->x_max, pdata->y_max);
+
+    count += snprintf(buf + count, PAGE_SIZE, "Max Touchs:%d\n",
+                      pdata->max_touch_number);
+
+    count += snprintf(buf + count, PAGE_SIZE,
+                      "reset gpio:%d,int gpio:%d,irq:%d\n",
+                      pdata->reset_gpio, pdata->irq_gpio, ts_data->irq);
+
+    count += snprintf(buf + count, PAGE_SIZE, "IC ID:0x%02x%02x\n",
+                      ts_data->ic_info.ids.chip_idh,
+                      ts_data->ic_info.ids.chip_idl);
+
+    if (ts_data->bus_type == FTS_BUS_TYPE_I2C) {
+        count += snprintf(buf + count, PAGE_SIZE, "BUS:%s,addr:0x%x\n",
+                          "I2C", ts_data->client->addr);
+    } else {
+        count += snprintf(buf + count, PAGE_SIZE,
+                          "BUS:%s,mode:%d,max_freq:%d\n", "SPI",
+                          ts_data->spi->mode, ts_data->spi->max_speed_hz);
+    }
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_driverinfo_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_dump_reg interface */
+static ssize_t fts_dumpreg_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+    fts_read_reg(FTS_REG_POWER_MODE, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Power Mode:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_FW_MAJOR_VER, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "FW Major Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_FW_MINOR_VER, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "FW Minor Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_LIC_VER, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "LCD Initcode Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_IDE_PARA_VER_ID, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Param Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_IDE_PARA_STATUS, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Param status:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_VENDOR_ID, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Vendor ID:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_GESTURE_EN, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Gesture Mode:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_CHARGER_MODE_EN, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "charge stat:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_INT_CNT, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "INT count:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_FLOW_WORK_CNT, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "ESD count:0x%02x\n", val);
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_dumpreg_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_dump_reg interface */
+static ssize_t fts_tpbuf_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    int i = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count += snprintf(buf + count, PAGE_SIZE, "touch point buffer:\n");
+    for (i = 0; i < fts_data->pnt_buf_size; i++) {
+        count += snprintf(buf + count, PAGE_SIZE, "%02x ", fts_data->point_buf[i]);
+    }
+    count += snprintf(buf + count, PAGE_SIZE, "\n");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_tpbuf_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_log_level interface */
+static ssize_t fts_log_level_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count += snprintf(buf + count, PAGE_SIZE, "log level:%d\n",
+                      fts_data->log_level);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_log_level_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int value = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    mutex_lock(&input_dev->mutex);
+    sscanf(buf, "%d", &value);
+    FTS_DEBUG("log level:%d->%d", fts_data->log_level, value);
+    fts_data->log_level = value;
+    mutex_unlock(&input_dev->mutex);
+    FTS_FUNC_EXIT();
+
+    return count;
+}
+
+/* get the fw version  example:cat fw_version */
+static DEVICE_ATTR(fts_fw_version, S_IRUGO | S_IWUSR, fts_tpfwver_show, fts_tpfwver_store);
+
+/* read and write register(s)
+*   All data type is **HEX**
+*   Single Byte:
+*       read:   echo 88 > rw_reg ---read register 0x88
+*       write:  echo 8807 > rw_reg ---write 0x07 into register 0x88
+*   Multi-bytes:
+*       [0:rw-flag][1-2: reg addr, hex][3-4: length, hex][5-6...n-n+1: write data, hex]
+*       rw-flag: 0, write; 1, read
+*       read:  echo 10005           > rw_reg ---read reg 0x00-0x05
+*       write: echo 000050102030405 > rw_reg ---write reg 0x00-0x05 as 01,02,03,04,05
+*  Get result:
+*       cat rw_reg
+*/
+static DEVICE_ATTR(fts_rw_reg, S_IRUGO | S_IWUSR, fts_tprwreg_show, fts_tprwreg_store);
+/*  upgrade from fw bin file   example:echo "*.bin" > fts_upgrade_bin */
+static DEVICE_ATTR(fts_upgrade_bin, S_IRUGO | S_IWUSR, fts_fwupgradebin_show, fts_fwupgradebin_store);
+static DEVICE_ATTR(fts_force_upgrade, S_IRUGO | S_IWUSR, fts_fwforceupg_show, fts_fwforceupg_store);
+static DEVICE_ATTR(fts_driver_info, S_IRUGO | S_IWUSR, fts_driverinfo_show, fts_driverinfo_store);
+static DEVICE_ATTR(fts_dump_reg, S_IRUGO | S_IWUSR, fts_dumpreg_show, fts_dumpreg_store);
+static DEVICE_ATTR(fts_hw_reset, S_IRUGO | S_IWUSR, fts_hw_reset_show, fts_hw_reset_store);
+static DEVICE_ATTR(fts_irq, S_IRUGO | S_IWUSR, fts_irq_show, fts_irq_store);
+static DEVICE_ATTR(fts_boot_mode, S_IRUGO | S_IWUSR, fts_bootmode_show, fts_bootmode_store);
+static DEVICE_ATTR(fts_touch_point, S_IRUGO | S_IWUSR, fts_tpbuf_show, fts_tpbuf_store);
+static DEVICE_ATTR(fts_log_level, S_IRUGO | S_IWUSR, fts_log_level_show, fts_log_level_store);
+
+/* add your attr in here*/
+static struct attribute *fts_attributes[] = {
+    &dev_attr_fts_fw_version.attr,
+    &dev_attr_fts_rw_reg.attr,
+    &dev_attr_fts_dump_reg.attr,
+    &dev_attr_fts_upgrade_bin.attr,
+    &dev_attr_fts_force_upgrade.attr,
+    &dev_attr_fts_driver_info.attr,
+    &dev_attr_fts_hw_reset.attr,
+    &dev_attr_fts_irq.attr,
+    &dev_attr_fts_boot_mode.attr,
+    &dev_attr_fts_touch_point.attr,
+    &dev_attr_fts_log_level.attr,
+    NULL
+};
+
+static struct attribute_group fts_attribute_group = {
+    .attrs = fts_attributes
+};
+
+static ssize_t proc_fw_update_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    struct fts_ts_data *ts_data = pde_data(file_inode(filp));
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+    int buflen = count;
+
+    FTS_INFO("upgrade with bin file through proc node");
+    if (!ts_data) {
+        FTS_ERROR("ts_data is null");
+        return -EINVAL;
+    }
+
+    if ((buflen <= 0) || (buflen >= FILE_NAME_LENGTH)) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(fwname, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+    fwname[buflen - 1] = '\0';
+
+    mutex_lock(&ts_data->input_dev->mutex);
+    fts_upgrade_bin(fwname, 0);
+    mutex_unlock(&ts_data->input_dev->mutex);
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_fw_update_fops = {
+    .proc_write  = proc_fw_update_write,
+};
+#else
+static const struct file_operations proc_fw_update_fops = {
+    .owner  = THIS_MODULE,
+    .write = proc_fw_update_write,
+};
+#endif
+
+/* scan modes */
+static ssize_t proc_scan_modes_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int cnt = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "scan_modes=0,1,2,3,4");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "0:Auto mode");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+         "1:Normal Active");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "2:Normal Idle");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "3:Low Power Active");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "4:Low Power Idle");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_scan_modes_fops = {
+    .proc_read   = proc_scan_modes_read,
+};
+#else
+static const struct file_operations proc_scan_modes_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_scan_modes_read,
+};
+#endif
+
+/* touch mode */
+static ssize_t proc_touch_mode_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 gesture_mode = 0;
+    u8 power_mode = 0;
+    u8 monitor_ctrl = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_GESTURE_EN, &gesture_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xD0 fails");
+        return ret;
+    }
+
+    ret = fts_read_reg(FTS_REG_POWER_MODE, &power_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xA5 fails");
+        return ret;
+    }
+
+    ret = fts_read_reg(FTS_REG_MONITOR_CTRL, &monitor_ctrl);
+    if (ret < 0) {
+        FTS_ERROR("read reg0x86 fails");
+        return ret;
+    }
+
+    if (gesture_mode) {
+        if (power_mode == 0)
+            cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+                "touch_mode:%d-%s\n", MODE_LOW_POWER_ACTIVE, "Low Power Active");
+        else if (power_mode == 1)
+            cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt,
+                "touch_mode:%d-%s\n", MODE_LOW_POWER_IDLE, "Low Power Idle");
+    } else if (monitor_ctrl) {
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+            "touch_mode:%d-%s\n", MODE_AUTO, "Auto mode");
+    } else {
+        if (power_mode == 0)
+            cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+                "touch_mode:%d-%s\n", MODE_NORMAL_ACTIVE, "Normal Active");
+        else if (power_mode == 1)
+            cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+                "touch_mode:%d-%s\n", MODE_NORMAL_IDLE, "Normal Idle");
+    }
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_touch_mode_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int touch_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &touch_mode);
+    if ((ret != 1) || (touch_mode < 0) || (touch_mode >= MODE_CNT)) {
+        FTS_ERROR("get mode(%d) fails,ret=%d", touch_mode, ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch touch_mode to %d", touch_mode);
+    switch (touch_mode) {
+    case MODE_AUTO:
+        ret = fts_write_reg(FTS_REG_MONITOR_CTRL, 1);
+        if (ret < 0) {
+            FTS_ERROR("write reg0x86 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_GESTURE_EN, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xd0 fails");
+            return ret;
+        }
+        break;
+    case MODE_NORMAL_ACTIVE:
+        ret = fts_write_reg(FTS_REG_MONITOR_CTRL, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0x86 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_POWER_MODE, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xA5 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_GESTURE_EN, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xD0 fails");
+            return ret;
+        }
+        break;
+    case MODE_NORMAL_IDLE:
+        ret = fts_write_reg(FTS_REG_MONITOR_CTRL, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0x86 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_POWER_MODE, 1);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xA5 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_GESTURE_EN, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xD0 fails");
+            return ret;
+        }
+        break;
+    case MODE_LOW_POWER_ACTIVE:
+        ret = fts_write_reg(FTS_REG_GESTURE_EN, 1);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xD0 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_POWER_MODE, 0);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xA5 fails");
+            return ret;
+        }
+        break;
+    case MODE_LOW_POWER_IDLE:
+        ret = fts_write_reg(FTS_REG_GESTURE_EN, 1);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xD0 fails");
+            return ret;
+        }
+
+        ret = fts_write_reg(FTS_REG_POWER_MODE, 1);
+        if (ret < 0) {
+            FTS_ERROR("write reg0xA5 fails");
+            return ret;
+        }
+        break;
+    default:
+        FTS_ERROR("Input index of mode is out of range!");
+        break;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_touch_mode_fops = {
+    .proc_read   = proc_touch_mode_read,
+    .proc_write  = proc_touch_mode_write,
+};
+#else
+static const struct file_operations proc_touch_mode_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_touch_mode_read,
+    .write  = proc_touch_mode_write,
+};
+#endif
+
+/* lpwg */
+static ssize_t proc_lpwg_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 gesture_mode = 0;
+    u8 gesture_function = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_GESTURE_EN, &gesture_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xD0 fails");
+        return ret;
+    }
+
+    ret = fts_read_reg(FTS_REG_GESTURE_SWITCH, &gesture_function);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xCF fails");
+        return ret;
+    }
+
+    if (gesture_function == 1)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Gesture_mode: STTW\n");
+    else if (gesture_function == 2)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Gesture_mode: LPTW\n");
+    else if (gesture_function == 3)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Gesture_mode: STTW + LPTW\n");
+    else if (gesture_function == 0)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Disable STTW and LPTW\n");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_lpwg_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int gesture_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &gesture_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch gesture mode to %d", gesture_mode);
+
+    switch (gesture_mode) {
+    case 0:
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 0);
+         if (ret < 0) {
+            FTS_ERROR("write reg 0xCF fails");
+            return ret;
+         }
+         break;
+
+    case 1:    //Single tap
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 1);
+         if (ret < 0) {
+             FTS_ERROR("write reg 0xCF fails");
+             return ret;
+         }
+         FTS_INFO("switch gesture function to STTW");
+         break;
+
+    case 2:    //Long press
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 2);
+         if (ret < 0) {
+             FTS_ERROR("write reg 0xCF fails");
+             return ret;
+         }
+         FTS_INFO("switch gesture function to LPTW");
+         break;
+
+    case 3:    //Single tap + Long press
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 3);
+         if (ret < 0) {
+             FTS_ERROR("write reg 0xCF fails");
+             return ret;
+          }
+         FTS_INFO("switch gesture function to STTW + LPTW");
+         break;
+
+    default:
+         break;
+   }
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_lpwg_fops = {
+    .proc_read   = proc_lpwg_read,
+    .proc_write  = proc_lpwg_write,
+};
+#else
+static const struct file_operations proc_lpwg_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_lpwg_read,
+    .write  = proc_lpwg_write,
+};
+#endif
+
+/* high sensitivity */
+static ssize_t proc_hs_read(struct file *filp, char __user *buff, size_t count,
+    loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 hs_mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_GLOVE_MODE_EN, &hs_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xC0 fails");
+        return ret;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "high_sensitivity mode:%s\n", hs_mode ? "Enable" : "Disable");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_hs_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int hs_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &hs_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    ret = fts_set_glove_mode(ts_data, !!hs_mode);
+    if (ret < 0)
+      return ret;
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_hs_fops = {
+    .proc_read   = proc_hs_read,
+    .proc_write  = proc_hs_write,
+};
+#else
+static const struct file_operations proc_hs_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_hs_read,
+    .write  = proc_hs_write,
+};
+#endif
+
+/* palm */
+static ssize_t proc_palm_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    FTS_DEBUG("fw_palm = %d", ts_data->enable_fw_palm);
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "%u\n", ts_data->enable_fw_palm);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+/* Set palm rejection mode.
+ * 0 - Disable fw palm rejection.
+ * 1 - Enable fw palm rejection.
+ * 2 - Force disable fw palm rejection.
+ * 3 - Force enable fw palm rejection.
+ */
+static ssize_t proc_palm_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int palm_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &palm_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    if (palm_mode < 0 || palm_mode > 3) {
+        FTS_ERROR("get palm mode fails, fw_palm should be in [0,1,2,3].");
+        return -EINVAL;
+    }
+
+    ts_data->enable_fw_palm = palm_mode;
+    FTS_INFO("switch fw_aplm to %u\n", ts_data->enable_fw_palm);
+
+    ret = fts_set_palm_mode(ts_data, palm_mode);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_palm_fops = {
+    .proc_read   = proc_palm_read,
+    .proc_write  = proc_palm_write,
+};
+#else
+static const struct file_operations proc_palm_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_palm_read,
+    .write  = proc_palm_write,
+};
+#endif
+
+/* grip */
+static ssize_t proc_grip_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    FTS_DEBUG("fw_grip = %u", ts_data->enable_fw_grip);
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "%u\n", ts_data->enable_fw_grip);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+/* Set Grip suppression mode.
+ * 0 - Disable fw grip suppression.
+ * 1 - Enable fw grip suppression.
+ * 2 - Force disable fw grip suppression.
+ * 3 - Force enable fw grip suppression.
+ */
+static ssize_t proc_grip_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int grip_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &grip_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+    if (grip_mode < 0 || grip_mode > 3) {
+        FTS_ERROR("get mode fails, grip_mode should be in [0,1,2,3].");
+        return -EINVAL;
+    }
+
+    ts_data->enable_fw_grip = grip_mode;
+    FTS_INFO("switch fw_grip to %u\n", ts_data->enable_fw_grip);
+
+    ret = fts_set_grip_mode(ts_data, grip_mode);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_grip_fops = {
+    .proc_read   = proc_grip_read,
+    .proc_write  = proc_grip_write,
+};
+#else
+static const struct file_operations proc_grip_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_grip_read,
+    .write  = proc_grip_write,
+};
+#endif
+
+/* sense on and off */
+static ssize_t proc_sense_onoff_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_SENSE_ONOFF, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xEA fails");
+        return ret;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Sensing mode:%s\n",
+        mode ? "Enable" : "Disable");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_sense_onoff_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch touch sense on/off to %d", mode);
+    ret = fts_write_reg(FTS_REG_SENSE_ONOFF, !!mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg0xEA fails");
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_sense_onoff_fops = {
+    .proc_read   = proc_sense_onoff_read,
+    .proc_write  = proc_sense_onoff_write,
+};
+#else
+static const struct file_operations proc_sense_onoff_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_sense_onoff_read,
+    .write  = proc_sense_onoff_write,
+};
+#endif
+
+/* IRQ on and off */
+static ssize_t proc_irq_onoff_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_IRQ_ONOFF, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xEB fails");
+        return ret;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "touch IRQ:%s\n",
+        mode ? "Enable" : "Disable");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_irq_onoff_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch touch IRQ on/off to %d", mode);
+    ret = fts_write_reg(FTS_REG_IRQ_ONOFF, !!mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg_0xEB fails");
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_irq_onoff_fops = {
+    .proc_read   = proc_irq_onoff_read,
+    .proc_write  = proc_irq_onoff_write,
+};
+#else
+static const struct file_operations proc_irq_onoff_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_irq_onoff_read,
+    .write  = proc_irq_onoff_write,
+};
+#endif
+
+/* heatmap on and off */
+static ssize_t proc_heatmap_onoff_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    u8 compressed = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_HEATMAP_9E, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0x%X fails", FTS_REG_HEATMAP_9E);
+        return ret;
+    }
+
+    if (mode) {
+        fts_read_reg(FTS_REG_HEATMAP_ED, &compressed);
+        if (ret < 0) {
+            FTS_ERROR("read reg_0x%X fails", FTS_REG_HEATMAP_ED);
+            return ret;
+        }
+        cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "%s ",
+            compressed ? "Compressed" : "Uncompressed");
+    }
+
+    cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "heatmap is %s\n",
+                    mode ? "Enable" : "Disable");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_heatmap_onoff_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    if (mode < FW_HEATMAP_MODE_DISABLE || mode > FW_HEATMAP_MODE_UNCOMPRESSED) {
+        FTS_ERROR("Please input the parameters in \n \
+             0: Disable firmware heatmap. \n \
+             1: Enable firmware compressed heatmap. \n \
+             2: Enable firmware uncompressed heatmap.");
+        return -EINVAL;
+    }
+    FTS_INFO("switch heatmap on/off to %d", mode);
+    fts_set_heatmap_mode(ts_data, mode);
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_heatmap_onoff_fops = {
+    .proc_read   = proc_heatmap_onoff_read,
+    .proc_write  = proc_heatmap_onoff_write,
+};
+#else
+static const struct file_operations proc_heatmap_onoff_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_heatmap_onoff_read,
+    .write  = proc_heatmap_onoff_write,
+};
+#endif
+
+static ssize_t proc_LPTW_setting_write(
+    struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+
+    int buflen = count;
+    int lptw_write_data[FTS_LPTW_BUF_LEN] = {0};
+    u8  write_data[FTS_LPTW_BUF_LEN] = {0};
+
+    u8 cmd[2] = {0};
+    u32 data_length = 0;
+    int i;
+
+    cmd[0] = FTS_LPTW_REG_SET_E1;
+    cmd[1] = FTS_LPTW_REG_SET_E2;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%x%x%x%x%x%x%x%x%x%x%x%x%x", &lptw_write_data[0],
+        &lptw_write_data[1], &lptw_write_data[2], &lptw_write_data[3],
+        &lptw_write_data[4], &lptw_write_data[5], &lptw_write_data[6],
+        &lptw_write_data[7], &lptw_write_data[8], &lptw_write_data[9],
+        &lptw_write_data[10], &lptw_write_data[11], &lptw_write_data[12]);
+
+    if(lptw_write_data[0] == FTS_LPTW_REG_SET_E1)
+        data_length = FTS_LPTW_E1_BUF_LEN;
+    else if (lptw_write_data[0] == FTS_LPTW_REG_SET_E2)
+        data_length = FTS_LPTW_E2_BUF_LEN;
+    else
+        data_length = 0;
+
+    for (i = 0; i < data_length; i++)
+        write_data[i] = (char)lptw_write_data[i];
+
+    if (data_length != 0){
+        ret=fts_write(write_data, data_length);
+        if (ret < 0) {
+            FTS_ERROR("write data to register E1/E2 fail");
+            return ret;
+        }
+    }
+
+ return count;
+}
+
+/*LPTW setting read*/
+static ssize_t proc_LPTW_setting_read(
+    struct file *filp, char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+    u8 cmd[2] = {0};
+    int num_read_chars = 0;
+    loff_t pos = *ppos;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 read_tmpbuf[20] = {0};
+
+    if (pos)
+        return 0;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = read_tmpbuf;
+    }
+
+    cmd[0] = FTS_LPTW_REG_SET_E1;
+    cmd[1] = FTS_LPTW_REG_SET_E2;
+    ret = fts_read(&cmd[0], 1, readbuf, FTS_LPTW_E1_BUF_LEN - 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE1 fails");
+        goto proc_read_err;
+    }
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "==LPTW Gesture setting(E1)==\n");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "min_x :%4d\n",
+        ((readbuf[0] & 0x0F) << 8) + (readbuf[1] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "min_y :%4d\n",
+        ((readbuf[2] & 0x0F) << 8) + (readbuf[3] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "max_x :%4d\n",
+        ((readbuf[4] & 0x0F) << 8) + (readbuf[5] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "max_y :%4d\n",
+        ((readbuf[6] & 0x0F) << 8) + (readbuf[7] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_frame_count :%3d\n",(readbuf[8] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "jitter :%3d\n" ,(readbuf[9] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "max_touch_size :%3d\n\n", (readbuf[10] & 0xFF));
+
+    ret = fts_read(&cmd[1], 1, readbuf, FTS_LPTW_E2_BUF_LEN - 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE2 fails");
+        goto proc_read_err;
+    }
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "==LPTW Gesture setting(E2)==\n");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_min_x :%2d\n", (readbuf[0] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_max_x :%2d\n", (readbuf[1] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_min_y :%2d\n", (readbuf[2] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_max_y :%2d\n", (readbuf[3] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_min_tx :%2d\n", (readbuf[4] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_max_tx :%2d\n", (readbuf[5] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_min_rx :%2d\n", (readbuf[6] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_max_rx :%2d\n", (readbuf[7] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_node_count :%2d\n", (readbuf[8] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "motion_boundary :%4d\n\n",((readbuf[10] & 0x0F) << 8) +
+        (readbuf[11] & 0xFF));
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops LPTW_setting_fops = {
+    .proc_read   = proc_LPTW_setting_read,
+    .proc_write  = proc_LPTW_setting_write,
+};
+#else
+static const struct file_operations LPTW_setting_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_LPTW_setting_read,
+    .write  = proc_LPTW_setting_write,
+};
+#endif
+
+static ssize_t proc_STTW_setting_write(
+    struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+    int buflen = count;
+    int sttw_write_data[FTS_STTW_E3_BUF_LEN] = {0};
+    u8  write_data[FTS_STTW_E3_BUF_LEN] = {0};
+
+    u8 cmd[2] = {0};
+    u8 data_length = 0;
+    int i = 0;
+    cmd[0] = FTS_STTW_REG_SET_E3;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%x%x%x%x%x%x%x%x%x%x%x%x%x", &sttw_write_data[0],
+        &sttw_write_data[1], &sttw_write_data[2], &sttw_write_data[3],
+        &sttw_write_data[4], &sttw_write_data[5], &sttw_write_data[6],
+        &sttw_write_data[7], &sttw_write_data[8], &sttw_write_data[9],
+        &sttw_write_data[10], &sttw_write_data[11], &sttw_write_data[12]);
+
+    if (sttw_write_data[0] == FTS_STTW_REG_SET_E3) {
+        data_length = FTS_STTW_E3_BUF_LEN;
+        for (i = 0; i < data_length; i++)
+            write_data[i] = (char)sttw_write_data[i];
+
+        ret = fts_write(write_data,data_length);
+        if (ret < 0) {
+            FTS_ERROR("write data to register E3 fail");
+            return ret;
+        }
+    }
+
+ return count;
+}
+
+/*STTW setting read*/
+static ssize_t proc_STTW_setting_read(
+    struct file *filp, char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+    u8 cmd[2] = {0};
+    int num_read_chars = 0;
+    loff_t pos = *ppos;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 read_tmpbuf[20] = {0};
+
+    if (pos)
+        return 0;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = read_tmpbuf;
+    }
+
+    cmd[0] = FTS_STTW_REG_SET_E3;
+
+    ret = fts_read(&cmd[0], 1, readbuf, FTS_STTW_E3_BUF_LEN - 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE3 fails");
+        goto proc_read_err;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "==STTW Gesture setting(E3)==\n");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_x :%4d\n",((readbuf[0] & 0x0F) << 8) +(readbuf[1] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_y :%4d\n",((readbuf[2] & 0x0F) << 8) +(readbuf[3] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "max_x :%4d\n",((readbuf[4] & 0x0F) << 8) +(readbuf[5] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "max_y :%4d\n",((readbuf[6] & 0x0F) << 8) +(readbuf[7] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_frame_count :%3d\n", (readbuf[8] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "max_frame_count :%3d\n", (readbuf[9] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "jitter :%3d\n",(readbuf[10] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "tap_max_touch_size :%3d\n", (readbuf[11] & 0xFF));
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops STTW_setting_fops = {
+    .proc_read   = proc_STTW_setting_read,
+    .proc_write  = proc_STTW_setting_write,
+};
+#else
+static const struct file_operations STTW_setting_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_STTW_setting_read,
+    .write  = proc_STTW_setting_write,
+};
+#endif
+
+/* motion filter mode */
+static ssize_t proc_mf_mode_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    FTS_DEBUG("mf_mode = %u", ts_data->mf_mode);
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "%u\n", ts_data->mf_mode);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+/**
+ * Attribute to set motion filter mode.
+ *  0 = Always unfilter.
+ *  1 = Dynamic change motion filter.
+ *  2 = Always filter by touch FW.
+ */
+static ssize_t proc_mf_mode_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mf_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mf_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+    if (mf_mode < 0 || mf_mode > 2) {
+        FTS_ERROR("get mode fails, mf_mode should be in [0,1,2].");
+        return -EINVAL;
+    }
+
+    ts_data->mf_mode = mf_mode;
+    FTS_INFO("switch fw_mode to %u\n", ts_data->mf_mode);
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_mf_mode_fops = {
+    .proc_read   = proc_mf_mode_read,
+    .proc_write  = proc_mf_mode_write,
+};
+#else
+static const struct file_operations proc_mf_mode_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_mf_mode_read,
+    .write  = proc_mf_mode_write,
+};
+#endif
+
+/**
+ * proc_force_active_write()
+ *
+ * Attribute to set different scan mode.
+ * 0x10 - Set FTS_TS_BUS_REF_FORCE_ACTIVE bit 0.
+ * 0x11 - Set FTS_TS_BUS_REF_FORCE_ACTIVE bit 1.
+ * 0x20 - Set FTS_TS_BUS_REF_BUGREPORT bit 0.
+ * 0x21 - Set FTS_TS_BUS_REF_BUGREPORT bit 1.
+ *
+ * @return
+ *    on success, return count; otherwise, return error code
+ */
+static ssize_t proc_force_active_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    unsigned char input;
+    int buflen = count;
+    bool active;
+    u32 ref = 0;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        ret = -EINVAL;
+        goto exit;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        ret = -EFAULT;
+        goto exit;
+    }
+
+    ret = kstrtou8(tmpbuf, 16, &input);
+    if (ret != 0) {
+        FTS_ERROR("get mode fails, ret=%d", ret);
+        ret = -EINVAL;
+        goto exit;
+    }
+    switch (input) {
+    case 0x10:
+        ref = FTS_TS_BUS_REF_FORCE_ACTIVE;
+        active = false;
+        break;
+    case 0x11:
+        ref = FTS_TS_BUS_REF_FORCE_ACTIVE;
+        active = true;
+        break;
+    case 0x20:
+        ref = FTS_TS_BUS_REF_BUGREPORT;
+        active = false;
+        ts_data->bugreport_ktime_start = 0;
+        break;
+    case 0x21:
+        ref = FTS_TS_BUS_REF_BUGREPORT;
+        active = true;
+        ts_data->bugreport_ktime_start = ktime_get();
+        break;
+    default:
+        FTS_ERROR("Invalid input %#x.\n", input);
+        ret = -EINVAL;
+        goto exit;
+    }
+
+    FTS_INFO("Set bus reference bit %#x %s.", ref,
+        active ? "enable" : "disable");
+
+    if (active)
+        pm_stay_awake(ts_data->dev);
+    else
+        pm_relax(ts_data->dev);
+
+    ret = fts_ts_set_bus_ref(ts_data, ref, active);
+    if (ret < 0) {
+        FTS_ERROR("Set bus reference bit %#x %s failed.", ref,
+            active ? "enable" : "disable");
+      goto exit;
+    }
+
+    ret = count;
+
+exit:
+    return ret;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_force_active_fops = {
+    .proc_write  = proc_force_active_write,
+};
+#else
+static const struct file_operations proc_force_active_fops = {
+    .owner  = THIS_MODULE,
+    .write  = proc_force_active_write,
+};
+#endif
+
+
+struct proc_dir_entry *proc_fw_update;
+struct proc_dir_entry *proc_scan_modes;
+struct proc_dir_entry *proc_touch_mode;
+struct proc_dir_entry *proc_lpwg;
+struct proc_dir_entry *proc_high_sensitivity;
+struct proc_dir_entry *proc_palm;
+struct proc_dir_entry *proc_grip;
+struct proc_dir_entry *proc_sense_onoff;
+struct proc_dir_entry *proc_irq_onoff;
+struct proc_dir_entry *proc_heatmap_onoff;
+struct proc_dir_entry *proc_LPTW_setting;
+struct proc_dir_entry *proc_STTW_setting;
+struct proc_dir_entry *proc_mf_mode;
+struct proc_dir_entry *proc_force_active;
+
+static int fts_create_ctrl_procs(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    proc_fw_update = proc_create_data("fw_update", S_IWUSR,
+        ts_data->proc_touch_entry, &proc_fw_update_fops, ts_data);
+    if (!proc_fw_update) {
+        FTS_ERROR("create proc_fw_update entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_scan_modes = proc_create_data("scan_modes", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_scan_modes_fops, ts_data);
+    if (!proc_scan_modes) {
+        FTS_ERROR("create proc_scan_modes entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_touch_mode = proc_create_data("touch_mode", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_touch_mode_fops, ts_data);
+    if (!proc_touch_mode) {
+        FTS_ERROR("create proc_touch_mode entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_lpwg = proc_create_data("lpwg", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_lpwg_fops, ts_data);
+    if (!proc_lpwg) {
+        FTS_ERROR("create proc_lpwg entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_high_sensitivity = proc_create_data("high_sensitivity",
+        S_IRUSR|S_IWUSR, ts_data->proc_touch_entry, &proc_hs_fops, ts_data);
+    if (!proc_lpwg) {
+        FTS_ERROR("create proc_high_sensitivity entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_palm = proc_create_data("fw_palm", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_palm_fops, ts_data);
+    if (!proc_palm) {
+        FTS_ERROR("create proc_palm entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_grip = proc_create_data("fw_grip", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_grip_fops, ts_data);
+    if (!proc_grip) {
+        FTS_ERROR("create proc_grip entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_sense_onoff = proc_create_data("sense_onoff", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_sense_onoff_fops, ts_data);
+    if (!proc_sense_onoff) {
+        FTS_ERROR("create proc_sense_onoff entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_irq_onoff = proc_create_data("irq_onoff", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_irq_onoff_fops, ts_data);
+    if (!proc_irq_onoff) {
+        FTS_ERROR("create proc_irq_onoff entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_heatmap_onoff = proc_create_data("heatmap_onoff", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_heatmap_onoff_fops, ts_data);
+    if (!proc_heatmap_onoff) {
+        FTS_ERROR("create proc_heatmap_onoff entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_LPTW_setting = proc_create_data("LPTW_setting", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &LPTW_setting_fops, ts_data);
+    if (!proc_LPTW_setting) {
+        FTS_ERROR("create proc_LPTW_settingentry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_STTW_setting = proc_create_data("STTW_setting", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &STTW_setting_fops, ts_data);
+    if (!proc_STTW_setting) {
+        FTS_ERROR("create proc_STTW_settingentry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_mf_mode = proc_create_data("mf_mode", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_mf_mode_fops, ts_data);
+    if (!proc_mf_mode) {
+        FTS_ERROR("create proc_mf_mode fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_force_active = proc_create_data("force_active", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_force_active_fops, ts_data);
+    if (!proc_force_active) {
+        FTS_ERROR("create proc_force_active fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    FTS_INFO("create control procs succeeds");
+    return 0;
+}
+
+static void fts_free_ctrl_procs(void)
+{
+    if (proc_fw_update)
+        proc_remove(proc_fw_update);
+
+    if (proc_scan_modes)
+        proc_remove(proc_scan_modes);
+
+    if (proc_touch_mode)
+        proc_remove(proc_touch_mode);
+
+    if (proc_lpwg)
+        proc_remove(proc_lpwg);
+
+    if (proc_high_sensitivity)
+        proc_remove(proc_high_sensitivity);
+
+    if (proc_palm)
+        proc_remove(proc_palm);
+
+    if (proc_grip)
+        proc_remove(proc_grip);
+
+    if (proc_sense_onoff)
+        proc_remove(proc_sense_onoff);
+
+    if (proc_irq_onoff)
+        proc_remove(proc_irq_onoff);
+
+    if (proc_heatmap_onoff)
+        proc_remove(proc_heatmap_onoff);
+
+    if (proc_LPTW_setting)
+        proc_remove(proc_LPTW_setting);
+
+    if (proc_STTW_setting)
+        proc_remove(proc_STTW_setting);
+
+    if (proc_mf_mode)
+        proc_remove(proc_mf_mode);
+
+    if (proc_force_active)
+        proc_remove(proc_force_active);
+}
+
+int fts_create_sysfs(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    ret = sysfs_create_group(&ts_data->dev->kobj, &fts_attribute_group);
+    if (ret) {
+        FTS_ERROR("[EX]: sysfs_create_group() failed!!");
+        sysfs_remove_group(&ts_data->dev->kobj, &fts_attribute_group);
+        return -ENOMEM;
+    } else {
+        FTS_INFO("[EX]: sysfs_create_group() succeeded!!");
+    }
+
+    ts_data->proc_touch_entry = proc_mkdir("focaltech_touch", NULL);
+    if (!ts_data->proc_touch_entry) {
+        FTS_ERROR("create proc/focaltech_touch fails");
+    }
+
+    ret = fts_create_ctrl_procs(ts_data);
+    if (ret) {
+        FTS_ERROR("Create ctrl procs fails");
+    }
+
+    return ret;
+}
+
+int fts_remove_sysfs(struct fts_ts_data *ts_data)
+{
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_attribute_group);
+    fts_free_ctrl_procs();
+    if (ts_data->proc_touch_entry)
+        proc_remove(fts_data->proc_touch_entry);
+    return 0;
+}
diff --git a/ft3658/focaltech_ex_mode.c b/ft3658/focaltech_ex_mode.c
new file mode 100644
index 0000000..14091f9
--- /dev/null
+++ b/ft3658/focaltech_ex_mode.c
@@ -0,0 +1,306 @@
+/*
+ *
+ * FocalTech ftxxxx TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_ex_mode.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-31
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* 2.Private constant and macro definitions using #define
+*****************************************************************************/
+
+/*****************************************************************************
+* 3.Private enumerations, structures and unions using typedef
+*****************************************************************************/
+enum _ex_mode {
+    MODE_GLOVE = 0,
+    MODE_COVER,
+    MODE_CHARGER,
+};
+
+/*****************************************************************************
+* 4.Static variables
+*****************************************************************************/
+
+/*****************************************************************************
+* 5.Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* 6.Static function prototypes
+*******************************************************************************/
+static int fts_ex_mode_switch(enum _ex_mode mode, u8 value)
+{
+    int ret = 0;
+    u8 m_val = 0;
+
+    if (value)
+        m_val = 0x01;
+    else
+        m_val = 0x00;
+
+    switch (mode) {
+    case MODE_GLOVE:
+        ret = fts_write_reg(FTS_REG_GLOVE_MODE_EN, m_val);
+        if (ret < 0) {
+            FTS_ERROR("MODE_GLOVE switch to %d fail", m_val);
+        }
+        break;
+    case MODE_COVER:
+        ret = fts_write_reg(FTS_REG_COVER_MODE_EN, m_val);
+        if (ret < 0) {
+            FTS_ERROR("MODE_COVER switch to %d fail", m_val);
+        }
+        break;
+    case MODE_CHARGER:
+        ret = fts_write_reg(FTS_REG_CHARGER_MODE_EN, m_val);
+        if (ret < 0) {
+            FTS_ERROR("MODE_CHARGER switch to %d fail", m_val);
+        }
+        break;
+    default:
+        FTS_ERROR("mode(%d) unsupport", mode);
+        ret = -EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+static ssize_t fts_glove_mode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    fts_read_reg(FTS_REG_GLOVE_MODE_EN, &val);
+    count = snprintf(buf + count, PAGE_SIZE, "Glove Mode:%s\n",
+                     ts_data->glove_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Glove Reg(0xC0):%d\n", val);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_glove_mode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        if (!ts_data->glove_mode) {
+            FTS_DEBUG("enter glove mode");
+            ret = fts_ex_mode_switch(MODE_GLOVE, ENABLE);
+            if (ret >= 0) {
+                ts_data->glove_mode = ENABLE;
+            }
+        }
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        if (ts_data->glove_mode) {
+            FTS_DEBUG("exit glove mode");
+            ret = fts_ex_mode_switch(MODE_GLOVE, DISABLE);
+            if (ret >= 0) {
+                ts_data->glove_mode = DISABLE;
+            }
+        }
+    }
+
+    FTS_DEBUG("glove mode:%d", ts_data->glove_mode);
+    return count;
+}
+
+
+static ssize_t fts_cover_mode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    fts_read_reg(FTS_REG_COVER_MODE_EN, &val);
+    count = snprintf(buf + count, PAGE_SIZE, "Cover Mode:%s\n",
+                     ts_data->cover_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Cover Reg(0xC1):%d\n", val);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_cover_mode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        if (!ts_data->cover_mode) {
+            FTS_DEBUG("enter cover mode");
+            ret = fts_ex_mode_switch(MODE_COVER, ENABLE);
+            if (ret >= 0) {
+                ts_data->cover_mode = ENABLE;
+            }
+        }
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        if (ts_data->cover_mode) {
+            FTS_DEBUG("exit cover mode");
+            ret = fts_ex_mode_switch(MODE_COVER, DISABLE);
+            if (ret >= 0) {
+                ts_data->cover_mode = DISABLE;
+            }
+        }
+    }
+
+    FTS_DEBUG("cover mode:%d", ts_data->cover_mode);
+    return count;
+}
+
+static ssize_t fts_charger_mode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    fts_read_reg(FTS_REG_CHARGER_MODE_EN, &val);
+    count = snprintf(buf + count, PAGE_SIZE, "Charger Mode:%s\n",
+                     ts_data->charger_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Charger Reg(0x8B):%d\n", val);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_charger_mode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        if (!ts_data->charger_mode) {
+            FTS_DEBUG("enter charger mode");
+            ret = fts_ex_mode_switch(MODE_CHARGER, ENABLE);
+            if (ret >= 0) {
+                ts_data->charger_mode = ENABLE;
+            }
+        }
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        if (ts_data->charger_mode) {
+            FTS_DEBUG("exit charger mode");
+            ret = fts_ex_mode_switch(MODE_CHARGER, DISABLE);
+            if (ret >= 0) {
+                ts_data->charger_mode = DISABLE;
+            }
+        }
+    }
+
+    FTS_DEBUG("charger mode:%d", ts_data->glove_mode);
+    return count;
+}
+
+
+/* read and write charger mode
+ * read example: cat fts_glove_mode        ---read  glove mode
+ * write example:echo 1 > fts_glove_mode   ---write glove mode to 01
+ */
+static DEVICE_ATTR(fts_glove_mode, S_IRUGO | S_IWUSR,
+                   fts_glove_mode_show, fts_glove_mode_store);
+
+static DEVICE_ATTR(fts_cover_mode, S_IRUGO | S_IWUSR,
+                   fts_cover_mode_show, fts_cover_mode_store);
+
+static DEVICE_ATTR(fts_charger_mode, S_IRUGO | S_IWUSR,
+                   fts_charger_mode_show, fts_charger_mode_store);
+
+static struct attribute *fts_touch_mode_attrs[] = {
+    &dev_attr_fts_glove_mode.attr,
+    &dev_attr_fts_cover_mode.attr,
+    &dev_attr_fts_charger_mode.attr,
+    NULL,
+};
+
+static struct attribute_group fts_touch_mode_group = {
+    .attrs = fts_touch_mode_attrs,
+};
+
+int fts_ex_mode_recovery(struct fts_ts_data *ts_data)
+{
+    /* update firmware feature settings. */
+    fts_update_feature_setting(ts_data);
+
+    if (ts_data->cover_mode) {
+        fts_ex_mode_switch(MODE_COVER, ENABLE);
+    }
+
+    if (ts_data->charger_mode) {
+        fts_ex_mode_switch(MODE_CHARGER, ENABLE);
+    }
+
+    return 0;
+}
+
+int fts_ex_mode_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    ts_data->glove_mode = DISABLE;
+    ts_data->cover_mode = DISABLE;
+    ts_data->charger_mode = DISABLE;
+
+    ret = sysfs_create_group(&ts_data->dev->kobj, &fts_touch_mode_group);
+    if (ret < 0) {
+        FTS_ERROR("create sysfs(ex_mode) fail");
+        sysfs_remove_group(&ts_data->dev->kobj, &fts_touch_mode_group);
+        return ret;
+    } else {
+        FTS_DEBUG("create sysfs(ex_mode) succeedfully");
+    }
+
+    return 0;
+}
+
+int fts_ex_mode_exit(struct fts_ts_data *ts_data)
+{
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_touch_mode_group);
+    return 0;
+}
diff --git a/ft3658/focaltech_flash.c b/ft3658/focaltech_flash.c
new file mode 100644
index 0000000..c03c1b3
--- /dev/null
+++ b/ft3658/focaltech_flash.c
@@ -0,0 +1,2080 @@
+/*
+ *
+ * FocalTech fts TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_flash.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+#include "focaltech_flash.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_FW_REQUEST_SUPPORT                      1
+#define FTS_FW_NAME                                 "focaltech_ts_fw"
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+u8 fw_file[] = {
+#include FTS_UPGRADE_FW_FILE
+};
+
+u8 fw_file2[] = {
+#include FTS_UPGRADE_FW2_FILE
+};
+
+u8 fw_file3[] = {
+#include FTS_UPGRADE_FW3_FILE
+};
+
+struct upgrade_module module_list[] = {
+    {FTS_MODULE_ID, FTS_MODULE_NAME, fw_file, sizeof(fw_file)},
+    {FTS_MODULE2_ID, FTS_MODULE2_NAME, fw_file2, sizeof(fw_file2)},
+    {FTS_MODULE3_ID, FTS_MODULE3_NAME, fw_file3, sizeof(fw_file3)},
+};
+
+struct upgrade_func *upgrade_func_list[] = {
+    &upgrade_func_ft5652,
+};
+
+struct fts_upgrade *fwupgrade;
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+static bool fts_fwupg_check_state(
+    struct fts_upgrade *upg, enum FW_STATUS rstate);
+
+/************************************************************************
+* Name: fts_fwupg_get_boot_state
+* Brief: read boot id(rom/pram/bootloader), confirm boot environment
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+static int fts_fwupg_get_boot_state(
+    struct fts_upgrade *upg,
+    enum FW_STATUS *fw_sts)
+{
+    int ret = 0;
+    u8 cmd[4] = { 0 };
+    u32 cmd_len = 0;
+    u8 val[2] = { 0 };
+    struct ft_chip_t *ids = NULL;
+
+    FTS_INFO("**********read boot id**********");
+    if ((!upg) || (!upg->func) || (!upg->ts_data) || (!fw_sts)) {
+        FTS_ERROR("upg/func/ts_data/fw_sts is null");
+        return -EINVAL;
+    }
+
+    if (upg->func->hid_supported)
+        fts_hid2std();
+
+    cmd[0] = FTS_CMD_START1;
+    cmd[1] = FTS_CMD_START2;
+    if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)
+        cmd_len = 1;
+    else
+        cmd_len = 2;
+    ret = fts_write(cmd, cmd_len);
+    if (ret < 0) {
+        FTS_ERROR("write 55 cmd fail");
+        return ret;
+    }
+
+    msleep(FTS_CMD_START_DELAY);
+    cmd[0] = FTS_CMD_READ_ID;
+    cmd[1] = cmd[2] = cmd[3] = 0x00;
+    if (fts_data->ic_info.is_incell ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0))
+        cmd_len = FTS_CMD_READ_ID_LEN_INCELL;
+    else
+        cmd_len = FTS_CMD_READ_ID_LEN;
+    ret = fts_read(cmd, cmd_len, val, 2);
+    if (ret < 0) {
+        FTS_ERROR("write 90 cmd fail");
+        return ret;
+    }
+    FTS_INFO("read boot id:0x%02x%02x", val[0], val[1]);
+
+    ids = &upg->ts_data->ic_info.ids;
+    if ((val[0] == ids->rom_idh) && (val[1] == ids->rom_idl)) {
+        FTS_INFO("tp run in romboot");
+        *fw_sts = FTS_RUN_IN_ROM;
+    } else if ((val[0] == ids->pb_idh) && (val[1] == ids->pb_idl)) {
+        FTS_INFO("tp run in pramboot");
+        *fw_sts = FTS_RUN_IN_PRAM;
+    } else if ((val[0] == ids->bl_idh) && (val[1] == ids->bl_idl)) {
+        FTS_INFO("tp run in bootloader");
+        *fw_sts = FTS_RUN_IN_BOOTLOADER;
+    }
+
+    return 0;
+}
+
+static int fts_fwupg_reset_to_boot(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    u8 reg = FTS_REG_UPGRADE;
+
+    FTS_INFO("send 0xAA and 0x55 to FW, reset to boot environment");
+    if (upg && upg->func && upg->func->is_reset_register_BC) {
+        reg = FTS_REG_UPGRADE2;
+    }
+
+    ret = fts_write_reg(reg, FTS_UPGRADE_AA);
+    if (ret < 0) {
+        FTS_ERROR("write FC=0xAA fail");
+        return ret;
+    }
+    msleep(FTS_DELAY_UPGRADE_AA);
+
+    ret = fts_write_reg(reg, FTS_UPGRADE_55);
+    if (ret < 0) {
+        FTS_ERROR("write FC=0x55 fail");
+        return ret;
+    }
+
+    msleep(FTS_DELAY_UPGRADE_RESET);
+    return 0;
+}
+
+/************************************************************************
+* Name: fts_fwupg_reset_to_romboot
+* Brief: reset to romboot, to load pramboot
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+static int fts_fwupg_reset_to_romboot(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cmd = FTS_CMD_RESET;
+    enum FW_STATUS state = FTS_RUN_IN_ERROR;
+
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("pram/rom/bootloader reset cmd write fail");
+        return ret;
+    }
+    mdelay(10);
+
+    for (i = 0; i < FTS_UPGRADE_LOOP; i++) {
+        ret = fts_fwupg_get_boot_state(upg, &state);
+        if (FTS_RUN_IN_ROM == state)
+            break;
+        mdelay(5);
+    }
+    if (i >= FTS_UPGRADE_LOOP) {
+        FTS_ERROR("reset to romboot fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static u16 fts_crc16_calc_host(u8 *pbuf, u32 length)
+{
+    u16 ecc = 0;
+    u32 i = 0;
+    u32 j = 0;
+
+    for ( i = 0; i < length; i += 2 ) {
+        ecc ^= ((pbuf[i] << 8) | (pbuf[i + 1]));
+        for (j = 0; j < 16; j ++) {
+            if (ecc & 0x01)
+                ecc = (u16)((ecc >> 1) ^ AL2_FCS_COEF);
+            else
+                ecc >>= 1;
+        }
+    }
+
+    return ecc;
+}
+
+static u16 fts_pram_ecc_calc_host(u8 *pbuf, u32 length)
+{
+    return fts_crc16_calc_host(pbuf, length);
+}
+
+static int fts_pram_ecc_cal_algo(
+    struct fts_upgrade *upg,
+    u32 start_addr,
+    u32 ecc_length)
+{
+    int ret = 0;
+    int i = 0;
+    int ecc = 0;
+    u8 val[2] = { 0 };
+    u8 tmp = 0;
+    u8 cmd[FTS_ROMBOOT_CMD_ECC_NEW_LEN] = { 0 };
+
+    FTS_INFO("read out pramboot checksum");
+    if ((!upg) || (!upg->func)) {
+        FTS_ERROR("upg/func is null");
+        return -EINVAL;
+    }
+
+    cmd[0] = FTS_ROMBOOT_CMD_ECC;
+    cmd[1] = BYTE_OFF_16(start_addr);
+    cmd[2] = BYTE_OFF_8(start_addr);
+    cmd[3] = BYTE_OFF_0(start_addr);
+    cmd[4] = BYTE_OFF_16(ecc_length);
+    cmd[5] = BYTE_OFF_8(ecc_length);
+    cmd[6] = BYTE_OFF_0(ecc_length);
+    ret = fts_write(cmd, FTS_ROMBOOT_CMD_ECC_NEW_LEN);
+    if (ret < 0) {
+        FTS_ERROR("write pramboot ecc cal cmd fail");
+        return ret;
+    }
+
+    cmd[0] = FTS_ROMBOOT_CMD_ECC_FINISH;
+    for (i = 0; i < FTS_ECC_FINISH_TIMEOUT; i++) {
+        msleep(1);
+        ret = fts_read(cmd, 1, val, 1);
+        if (ret < 0) {
+            FTS_ERROR("ecc_finish read cmd fail");
+            return ret;
+        }
+        if (upg->func->new_return_value_from_ic ||
+            (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+            tmp = FTS_ROMBOOT_CMD_ECC_FINISH_OK_A5;
+        } else {
+            tmp = FTS_ROMBOOT_CMD_ECC_FINISH_OK_00;
+        }
+        if (tmp == val[0])
+            break;
+    }
+    if (i >= FTS_ECC_FINISH_TIMEOUT) {
+        FTS_ERROR("wait ecc finish fail");
+        return -EIO;
+    }
+
+    cmd[0] = FTS_ROMBOOT_CMD_ECC_READ;
+    ret = fts_read(cmd, 1, val, 2);
+    if (ret < 0) {
+        FTS_ERROR("read pramboot ecc fail");
+        return ret;
+    }
+
+    ecc = ((u16)(val[0] << 8) + val[1]) & 0x0000FFFF;
+    return ecc;
+}
+
+static int fts_pram_ecc_cal_xor(void)
+{
+    int ret = 0;
+    u8 reg_val = 0;
+
+    FTS_INFO("read out pramboot checksum");
+
+    ret = fts_read_reg(FTS_ROMBOOT_CMD_ECC, &reg_val);
+    if (ret < 0) {
+        FTS_ERROR("read pramboot ecc fail");
+        return ret;
+    }
+
+    return (int)reg_val;
+}
+
+static int fts_pram_ecc_cal(struct fts_upgrade *upg, u32 saddr, u32 len)
+{
+    if ((!upg) || (!upg->func)) {
+        FTS_ERROR("upg/func is null");
+        return -EINVAL;
+    }
+
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->pram_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        return fts_pram_ecc_cal_algo(upg, saddr, len);
+    } else {
+        return fts_pram_ecc_cal_xor();
+    }
+}
+
+static int fts_pram_write_buf(struct fts_upgrade *upg, u8 *buf, u32 len)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 j = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u32 packet_number;
+    u32 packet_len = 0;
+    u8 packet_buf[FTS_FLASH_PACKET_LENGTH + FTS_CMD_WRITE_LEN] = { 0 };
+    u8 ecc_tmp = 0;
+    int ecc_in_host = 0;
+    u32 cmdlen = 0;
+
+    FTS_INFO("write pramboot to pram");
+    if ((!upg) || (!upg->func) || !buf) {
+        FTS_ERROR("upg/func/buf is null");
+        return -EINVAL;
+    }
+
+    FTS_INFO("pramboot len=%d", len);
+    if ((len < PRAMBOOT_MIN_SIZE) || (len > PRAMBOOT_MAX_SIZE)) {
+        FTS_ERROR("pramboot length(%d) fail", len);
+        return -EINVAL;
+    }
+
+    packet_number = len / FTS_FLASH_PACKET_LENGTH;
+    remainder = len % FTS_FLASH_PACKET_LENGTH;
+    if (remainder > 0)
+        packet_number++;
+    packet_len = FTS_FLASH_PACKET_LENGTH;
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * FTS_FLASH_PACKET_LENGTH;
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            packet_buf[0] = FTS_ROMBOOT_CMD_SET_PRAM_ADDR;
+            packet_buf[1] = BYTE_OFF_16(offset);
+            packet_buf[2] = BYTE_OFF_8(offset);
+            packet_buf[3] = BYTE_OFF_0(offset);
+
+            ret = fts_write(packet_buf, FTS_ROMBOOT_CMD_SET_PRAM_ADDR_LEN);
+            if (ret < 0) {
+                FTS_ERROR("pramboot set write address(%d) fail", i);
+                return ret;
+            }
+
+            packet_buf[0] = FTS_ROMBOOT_CMD_WRITE;
+            cmdlen = 1;
+        } else {
+            packet_buf[0] = FTS_ROMBOOT_CMD_WRITE;
+            packet_buf[1] = BYTE_OFF_16(offset);
+            packet_buf[2] = BYTE_OFF_8(offset);
+            packet_buf[3] = BYTE_OFF_0(offset);
+
+            packet_buf[4] = BYTE_OFF_8(packet_len);
+            packet_buf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = 6;
+        }
+
+        for (j = 0; j < packet_len; j++) {
+            packet_buf[cmdlen + j] = buf[offset + j];
+            if (ECC_CHECK_MODE_XOR == upg->func->pram_ecc_check_mode) {
+                ecc_tmp ^= packet_buf[cmdlen + j];
+            }
+        }
+
+        ret = fts_write(packet_buf, packet_len + cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("pramboot write data(%d) fail", i);
+            return ret;
+        }
+    }
+
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->pram_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc_in_host = (int)fts_pram_ecc_calc_host(buf, len);
+    } else {
+        ecc_in_host = (int)ecc_tmp;
+    }
+
+    return ecc_in_host;
+}
+
+static int fts_pram_start(void)
+{
+    u8 cmd = FTS_ROMBOOT_CMD_START_APP;
+    int ret = 0;
+
+    FTS_INFO("remap to start pramboot");
+
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("write start pram cmd fail");
+        return ret;
+    }
+    msleep(FTS_DELAY_PRAMBOOT_START);
+
+    return 0;
+}
+
+static int fts_pram_write_remap(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    int ecc_in_host = 0;
+    int ecc_in_tp = 0;
+    u8 *pb_buf = NULL;
+    u32 pb_len = 0;
+
+    FTS_INFO("write pram and remap");
+    if (!upg || !upg->func || !upg->func->pramboot) {
+        FTS_ERROR("upg/func/pramboot is null");
+        return -EINVAL;
+    }
+
+    if (upg->func->pb_length < FTS_MIN_LEN) {
+        FTS_ERROR("pramboot length(%d) fail", upg->func->pb_length);
+        return -EINVAL;
+    }
+
+    pb_buf = upg->func->pramboot;
+    pb_len = upg->func->pb_length;
+
+    /* write pramboot to pram */
+    ecc_in_host = fts_pram_write_buf(upg, pb_buf, pb_len);
+    if (ecc_in_host < 0) {
+        FTS_ERROR( "write pramboot fail");
+        return ecc_in_host;
+    }
+
+    /* read out checksum */
+    ecc_in_tp = fts_pram_ecc_cal(upg, 0, pb_len);
+    if (ecc_in_tp < 0) {
+        FTS_ERROR( "read pramboot ecc fail");
+        return ecc_in_tp;
+    }
+
+    FTS_INFO("pram ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host);
+    /*  pramboot checksum != fw checksum, upgrade fail */
+    if (ecc_in_host != ecc_in_tp) {
+        FTS_ERROR("pramboot ecc check fail");
+        return -EIO;
+    }
+
+    /*start pram*/
+    ret = fts_pram_start();
+    if (ret < 0) {
+        FTS_ERROR("pram start fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_pram_init(void)
+{
+    int ret = 0;
+    u8 reg_val = 0;
+    u8 wbuf[3] = { 0 };
+
+    FTS_INFO("pramboot initialization");
+
+    /* read flash ID */
+    wbuf[0] = FTS_CMD_FLASH_TYPE;
+    ret = fts_read(wbuf, 1, &reg_val, 1);
+    if (ret < 0) {
+        FTS_ERROR("read flash type fail");
+        return ret;
+    }
+
+    /* set flash clk */
+    wbuf[0] = FTS_CMD_FLASH_TYPE;
+    wbuf[1] = reg_val;
+    wbuf[2] = 0x00;
+    ret = fts_write(wbuf, 3);
+    if (ret < 0) {
+        FTS_ERROR("write flash type fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_pram_write_init(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool state = 0;
+    enum FW_STATUS status = FTS_RUN_IN_ERROR;
+
+    FTS_INFO("**********pram write and init**********");
+    if ((NULL == upg) || (NULL == upg->func)) {
+        FTS_ERROR("upgrade/func is null");
+        return -EINVAL;
+    }
+
+    if (!upg->func->pramboot_supported) {
+        FTS_ERROR("ic not support pram");
+        return -EINVAL;
+    }
+
+    FTS_DEBUG("check whether tp is in romboot or not ");
+    /* need reset to romboot when non-romboot state */
+    ret = fts_fwupg_get_boot_state(upg, &status);
+    if (status != FTS_RUN_IN_ROM) {
+        if (FTS_RUN_IN_PRAM == status) {
+            FTS_INFO("tp is in pramboot, need send reset cmd before upgrade");
+            ret = fts_pram_init();
+            if (ret < 0) {
+                FTS_ERROR("pramboot(before) init fail");
+                return ret;
+            }
+        }
+
+        FTS_INFO("tp isn't in romboot, need send reset to romboot");
+        ret = fts_fwupg_reset_to_romboot(upg);
+        if (ret < 0) {
+            FTS_ERROR("reset to romboot fail");
+            return ret;
+        }
+    }
+
+    /* check the length of the pramboot */
+    ret = fts_pram_write_remap(upg);
+    if (ret < 0) {
+        FTS_ERROR("pram write fail, ret=%d", ret);
+        return ret;
+    }
+
+    FTS_DEBUG("after write pramboot, confirm run in pramboot");
+    state = fts_fwupg_check_state(upg, FTS_RUN_IN_PRAM);
+    if (!state) {
+        FTS_ERROR("not in pramboot");
+        return -EIO;
+    }
+
+    ret = fts_pram_init();
+    if (ret < 0) {
+        FTS_ERROR("pramboot init fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static bool fts_fwupg_check_fw_valid(void)
+{
+    int ret = 0;
+
+    ret = fts_wait_tp_to_valid();
+    if (ret < 0) {
+        FTS_INFO("tp fw invaild");
+        return false;
+    }
+
+    FTS_INFO("tp fw vaild");
+    return true;
+}
+
+/************************************************************************
+* Name: fts_fwupg_check_state
+* Brief: confirm tp run in which mode: romboot/pramboot/bootloader
+* Input:
+* Output:
+* Return: return true if state is match, otherwise return false
+***********************************************************************/
+static bool fts_fwupg_check_state(
+    struct fts_upgrade *upg, enum FW_STATUS rstate)
+{
+    int ret = 0;
+    int i = 0;
+    enum FW_STATUS cstate = FTS_RUN_IN_ERROR;
+
+    for (i = 0; i < FTS_UPGRADE_LOOP; i++) {
+        ret = fts_fwupg_get_boot_state(upg, &cstate);
+        /* FTS_DEBUG("fw state=%d, retries=%d", cstate, i); */
+        if (cstate == rstate)
+            return true;
+        msleep(FTS_DELAY_READ_ID);
+    }
+
+    return false;
+}
+
+/************************************************************************
+* Name: fts_fwupg_reset_in_boot
+* Brief: RST CMD(07), reset to romboot(bootloader) in boot environment
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+int fts_fwupg_reset_in_boot(void)
+{
+    int ret = 0;
+    u8 cmd = FTS_CMD_RESET;
+
+    FTS_INFO("reset in boot environment");
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("pram/rom/bootloader reset cmd write fail");
+        return ret;
+    }
+
+    msleep(FTS_DELAY_UPGRADE_RESET);
+    return 0;
+}
+
+/************************************************************************
+* Name: fts_fwupg_enter_into_boot
+* Brief: enter into boot environment, ready for upgrade
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+int fts_fwupg_enter_into_boot(void)
+{
+    int ret = 0;
+    bool fwvalid = false;
+    bool state = false;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO("***********enter into pramboot/bootloader***********");
+    if ((!upg) || (NULL == upg->func)) {
+        FTS_ERROR("upgrade/func is null");
+        return -EINVAL;
+    }
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if (fwvalid) {
+        ret = fts_fwupg_reset_to_boot(upg);
+        if (ret < 0) {
+            FTS_ERROR("enter into romboot/bootloader fail");
+            return ret;
+        }
+    } else if (upg->func->read_boot_id_need_reset) {
+        ret = fts_fwupg_reset_in_boot();
+        if (ret < 0) {
+            FTS_ERROR("reset before read boot id when fw invalid fail");
+            return ret;
+        }
+    }
+
+    if (upg->func->pramboot_supported) {
+        FTS_INFO("pram supported, write pramboot and init");
+        /* pramboot */
+        if (upg->func->write_pramboot_private)
+            ret = upg->func->write_pramboot_private();
+        else
+            ret = fts_pram_write_init(upg);
+        if (ret < 0) {
+            FTS_ERROR("pram write_init fail");
+            return ret;
+        }
+    } else {
+        FTS_DEBUG("pram not supported, confirm in bootloader");
+        /* bootloader */
+        state = fts_fwupg_check_state(upg, FTS_RUN_IN_BOOTLOADER);
+        if (!state) {
+            FTS_ERROR("fw not in bootloader, fail");
+            return -EIO;
+        }
+    }
+
+    return 0;
+}
+
+/************************************************************************
+ * Name: fts_fwupg_check_flash_status
+ * Brief: read status from tp
+ * Input: flash_status: correct value from tp
+ *        retries: read retry times
+ *        retries_delay: retry delay
+ * Output:
+ * Return: return true if flash status check pass, otherwise return false
+***********************************************************************/
+static bool fts_fwupg_check_flash_status(
+    u16 flash_status,
+    int retries,
+    int retries_delay)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cmd = 0;
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    u16 read_status = 0;
+
+    for (i = 0; i < retries; i++) {
+        cmd = FTS_CMD_FLASH_STATUS;
+        ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN);
+        read_status = (((u16)val[0]) << 8) + val[1];
+        if (flash_status == read_status) {
+            /* FTS_DEBUG("[UPGRADE]flash status ok"); */
+            return true;
+        }
+        /* FTS_DEBUG("flash status fail,ok:%04x read:%04x, retries:%d", flash_status, read_status, i); */
+        msleep(retries_delay);
+    }
+
+    return false;
+}
+
+/************************************************************************
+ * Name: fts_fwupg_erase
+ * Brief: erase flash area
+ * Input: delay - delay after erase
+ * Output:
+ * Return: return 0 if success, otherwise return error code
+ ***********************************************************************/
+int fts_fwupg_erase(u32 delay)
+{
+    int ret = 0;
+    u8 cmd = 0;
+    bool flag = false;
+
+    FTS_INFO("**********erase now**********");
+
+    /*send to erase flash*/
+    cmd = FTS_CMD_ERASE_APP;
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("erase cmd fail");
+        return ret;
+    }
+    msleep(delay);
+
+    /* read status 0xF0AA: success */
+    flag = fts_fwupg_check_flash_status(FTS_CMD_FLASH_STATUS_ERASE_OK,
+                                        FTS_RETRIES_REASE,
+                                        FTS_RETRIES_DELAY_REASE);
+    if (!flag) {
+        FTS_ERROR("ecc flash status check fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/************************************************************************
+ * Name: fts_fwupg_ecc_cal
+ * Brief: calculate and get ecc from tp
+ * Input: saddr - start address need calculate ecc
+ *        len - length need calculate ecc
+ * Output:
+ * Return: return data ecc of tp if success, otherwise return error code
+ ***********************************************************************/
+int fts_fwupg_ecc_cal(u32 saddr, u32 len)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 cmdlen = FTS_CMD_ECC_CAL_LEN;
+    u8 wbuf[FTS_CMD_ECC_CAL_LEN] = { 0 };
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    int ecc = 0;
+    int ecc_len = 0;
+    u32 packet_num = 0;
+    u32 packet_len = 0;
+    u32 remainder = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    bool bflag = false;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO( "**********read out checksum**********");
+    if ((NULL == upg) || (NULL == upg->func)) {
+        FTS_ERROR("upgrade/func is null");
+        return -EINVAL;
+    }
+
+    /* check sum init */
+    wbuf[0] = FTS_CMD_ECC_INIT;
+    ret = fts_write(wbuf, 1);
+    if (ret < 0) {
+        FTS_ERROR("ecc init cmd write fail");
+        return ret;
+    }
+
+    if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) {
+        packet_num = 1;
+        remainder = 0;
+        packet_len = len;
+    } else {
+        packet_num = len / FTS_MAX_LEN_ECC_CALC;
+        remainder = len % FTS_MAX_LEN_ECC_CALC;
+        if (remainder)
+            packet_num++;
+        packet_len = FTS_MAX_LEN_ECC_CALC;
+    }
+    FTS_INFO("ecc calc num:%d, remainder:%d", packet_num, remainder);
+
+    /* send commond to start checksum */
+    wbuf[0] = FTS_CMD_ECC_CAL;
+    for (i = 0; i < packet_num; i++) {
+        offset = FTS_MAX_LEN_ECC_CALC * i;
+        addr = saddr + offset;
+        wbuf[1] = BYTE_OFF_16(addr);
+        wbuf[2] = BYTE_OFF_8(addr);
+        wbuf[3] = BYTE_OFF_0(addr);
+
+        if ((upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+            wbuf[4] = BYTE_OFF_16(packet_len);
+            wbuf[5] = BYTE_OFF_8(packet_len);
+            wbuf[6] = BYTE_OFF_0(packet_len);
+            cmdlen = FTS_CMD_ECC_CAL_LEN;
+        } else {
+            if ((i == (packet_num - 1)) && remainder)
+                packet_len = remainder;
+            wbuf[4] = BYTE_OFF_8(packet_len);
+            wbuf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = FTS_CMD_ECC_CAL_LEN - 1;
+        }
+
+        FTS_DEBUG("ecc calc startaddr:0x%04x, len:%d", addr, packet_len);
+        ret = fts_write(wbuf, cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("ecc calc cmd write fail");
+            return ret;
+        }
+
+        msleep(packet_len / 256);
+
+        /* read status if check sum is finished */
+        bflag = fts_fwupg_check_flash_status(FTS_CMD_FLASH_STATUS_ECC_OK,
+                                             FTS_RETRIES_ECC_CAL,
+                                             FTS_RETRIES_DELAY_ECC_CAL);
+        if (!bflag) {
+            FTS_ERROR("ecc flash status read fail");
+            return -EIO;
+        }
+    }
+
+    ecc_len = 1;
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->fw_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc_len = 2;
+    }
+
+    /* read out check sum */
+    wbuf[0] = FTS_CMD_ECC_READ;
+    ret = fts_read(wbuf, 1, val, ecc_len);
+    if (ret < 0) {
+        FTS_ERROR( "ecc read cmd write fail");
+        return ret;
+    }
+
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->fw_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc = (int)((u16)(val[0] << 8) + val[1]);
+    } else {
+        ecc = (int)val[0];
+    }
+
+    return ecc;
+}
+
+/************************************************************************
+ * Name: fts_flash_write_buf
+ * Brief: write buf data to flash address
+ * Input: saddr - start address data write to flash
+ *        buf - data buffer
+ *        len - data length
+ *        delay - delay after write
+ * Output:
+ * Return: return data ecc of host if success, otherwise return error code
+ ***********************************************************************/
+int fts_flash_write_buf(
+    u32 saddr,
+    u8 *buf,
+    u32 len,
+    u32 delay)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 j = 0;
+    u32 packet_number = 0;
+    u32 packet_len = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u32 cmdlen = 0;
+    u8 packet_buf[FTS_FLASH_PACKET_LENGTH + FTS_CMD_WRITE_LEN] = { 0 };
+    u8 ecc_tmp = 0;
+    int ecc_in_host = 0;
+    u8 cmd = 0;
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    u16 read_status = 0;
+    u16 wr_ok = 0;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO( "**********write data to flash**********");
+    if ((!upg) || (!upg->func || !buf || !len)) {
+        FTS_ERROR("upgrade/func/buf/len is invalid");
+        return -EINVAL;
+    }
+
+    FTS_INFO("data buf start addr=0x%x, len=0x%x", saddr, len);
+    packet_number = len / FTS_FLASH_PACKET_LENGTH;
+    remainder = len % FTS_FLASH_PACKET_LENGTH;
+    if (remainder > 0)
+        packet_number++;
+    packet_len = FTS_FLASH_PACKET_LENGTH;
+    FTS_INFO("write data, num:%d remainder:%d", packet_number, remainder);
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * FTS_FLASH_PACKET_LENGTH;
+        addr = saddr + offset;
+
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            packet_buf[0] = FTS_CMD_SET_WFLASH_ADDR;
+            packet_buf[1] = BYTE_OFF_16(addr);
+            packet_buf[2] = BYTE_OFF_8(addr);
+            packet_buf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(packet_buf, FTS_LEN_SET_ADDR);
+            if (ret < 0) {
+                FTS_ERROR("set flash address fail");
+                return ret;
+            }
+
+            packet_buf[0] = FTS_CMD_WRITE;
+            cmdlen = 1;
+        } else {
+            packet_buf[0] = FTS_CMD_WRITE;
+            packet_buf[1] = BYTE_OFF_16(addr);
+            packet_buf[2] = BYTE_OFF_8(addr);
+            packet_buf[3] = BYTE_OFF_0(addr);
+            packet_buf[4] = BYTE_OFF_8(packet_len);
+            packet_buf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = 6;
+        }
+
+        for (j = 0; j < packet_len; j++) {
+            packet_buf[cmdlen + j] = buf[offset + j];
+            ecc_tmp ^= packet_buf[cmdlen + j];
+        }
+
+        ret = fts_write(packet_buf, packet_len + cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("app write fail");
+            return ret;
+        }
+        mdelay(delay);
+
+        /* read status */
+        wr_ok = FTS_CMD_FLASH_STATUS_WRITE_OK + addr / packet_len;
+        for (j = 0; j < FTS_RETRIES_WRITE; j++) {
+            cmd = FTS_CMD_FLASH_STATUS;
+            ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN);
+            read_status = (((u16)val[0]) << 8) + val[1];
+            /*  FTS_INFO("%x %x", wr_ok, read_status); */
+            if (wr_ok == read_status) {
+                break;
+            }
+            mdelay(FTS_RETRIES_DELAY_WRITE);
+        }
+    }
+
+    ecc_in_host = (int)ecc_tmp;
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->fw_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc_in_host = (int)fts_crc16_calc_host(buf, len);
+    }
+
+    return ecc_in_host;
+}
+
+/************************************************************************
+ * Name: fts_flash_read_buf
+ * Brief: read data from flash
+ * Input: saddr - start address data write to flash
+ *        buf - buffer to store data read from flash
+ *        len - read length
+ * Output:
+ * Return: return 0 if success, otherwise return error code
+ *
+ * Warning: can't call this function directly, need call in boot environment
+ ***********************************************************************/
+int fts_flash_read_buf(u32 saddr, u8 *buf, u32 len)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 packet_number = 0;
+    u32 packet_len = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u8 wbuf[FTS_CMD_READ_LEN_SPI] = { 0 };
+    struct fts_upgrade *upg = fwupgrade;
+
+    if (!upg || !buf || !len) {
+        FTS_ERROR("upgrade/buf is NULL or len is 0");
+        return -EINVAL;
+    }
+
+    packet_number = len / FTS_FLASH_PACKET_LENGTH;
+    remainder = len % FTS_FLASH_PACKET_LENGTH;
+    if (remainder > 0) {
+        packet_number++;
+    }
+    packet_len = FTS_FLASH_PACKET_LENGTH;
+    FTS_INFO("read packet_number:%d, remainder:%d", packet_number, remainder);
+
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * FTS_FLASH_PACKET_LENGTH;
+        addr = saddr + offset;
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (upg->ts_data->bus_type == FTS_BUS_TYPE_I2C) {
+            wbuf[0] = FTS_CMD_READ;
+            wbuf[1] = BYTE_OFF_16(addr);
+            wbuf[2] = BYTE_OFF_8(addr);
+            wbuf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(wbuf, FTS_CMD_READ_LEN);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader write 03 command fail");
+                return ret;
+            }
+
+            msleep(FTS_CMD_READ_DELAY); /* must wait, otherwise read wrong data */
+            ret = fts_read(NULL, 0, buf + offset, packet_len);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader read 03 command fail");
+                return ret;
+            }
+        } else if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            wbuf[0] = FTS_CMD_SET_RFLASH_ADDR;
+            wbuf[1] = BYTE_OFF_16(addr);
+            wbuf[2] = BYTE_OFF_8(addr);
+            wbuf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(wbuf, FTS_LEN_SET_ADDR);
+            if (ret < 0) {
+                FTS_ERROR("set flash address fail");
+                return ret;
+            }
+
+            msleep(FTS_CMD_READ_DELAY);
+            wbuf[0] = FTS_CMD_READ;
+            ret = fts_read(wbuf, 1, buf + offset, packet_len);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader read 03(SPI_V2) command fail");
+                return ret;
+            }
+        } else if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI) {
+            wbuf[0] = FTS_CMD_READ;
+            wbuf[1] = BYTE_OFF_16(addr);
+            wbuf[2] = BYTE_OFF_8(addr);
+            wbuf[3] = BYTE_OFF_0(addr);
+            wbuf[4] = BYTE_OFF_8(packet_len);
+            wbuf[5] = BYTE_OFF_0(packet_len);
+            ret = fts_read(wbuf, FTS_CMD_READ_LEN_SPI, \
+                           buf + offset, packet_len);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader read 03(SPI) command fail");
+                return ret;
+            }
+        }
+    }
+
+    return 0;
+}
+
+/************************************************************************
+ * Name: fts_flash_read
+ * Brief:
+ * Input:  addr  - address of flash
+ *         len   - length of read
+ * Output: buf   - data read from flash
+ * Return: return 0 if success, otherwise return error code
+ ***********************************************************************/
+static int fts_flash_read(u32 addr, u8 *buf, u32 len)
+{
+    int ret = 0;
+
+    FTS_INFO("***********read flash***********");
+    if ((NULL == buf) || (0 == len)) {
+        FTS_ERROR("buf is NULL or len is 0");
+        return -EINVAL;
+    }
+
+    ret = fts_fwupg_enter_into_boot();
+    if (ret < 0) {
+        FTS_ERROR("enter into pramboot/bootloader fail");
+        goto read_flash_err;
+    }
+
+    ret = fts_flash_read_buf(addr, buf, len);
+    if (ret < 0) {
+        FTS_ERROR("read flash fail");
+        goto read_flash_err;
+    }
+
+read_flash_err:
+    /* reset to normal boot */
+    ret = fts_fwupg_reset_in_boot();
+    if (ret < 0) {
+        FTS_ERROR("reset to normal boot fail");
+    }
+    return ret;
+}
+
+int fts_upgrade_bin(char *fw_name, bool force)
+{
+    int ret = 0;
+    u32 fw_file_len = 0;
+    u8 *fw_file_buf = NULL;
+    const struct firmware *fw = NULL;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO("start upgrade with fw bin");
+    if ((!upg) || (!upg->func) || !upg->ts_data) {
+        FTS_ERROR("upgrade/func/ts_data is null");
+        return -EINVAL;
+    }
+
+    upg->ts_data->fw_loading = 1;
+    fts_irq_disable();
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    ret = request_firmware(&fw, fw_name, upg->ts_data->dev);
+    if (ret) {
+        FTS_ERROR("read fw bin file(%s) fail, len:%d", fw_name, ret);
+        goto err_bin;
+    }
+
+    fw_file_len = (u32)fw->size;
+    fw_file_buf = (u8 *)fw->data;
+    FTS_INFO("request fw succeeds, file len:%d", fw_file_len);
+    if (force) {
+        if (upg->func->force_upgrade) {
+            ret = upg->func->force_upgrade(fw_file_buf, fw_file_len);
+        } else {
+            FTS_INFO("force_upgrade function is null, no upgrade");
+            goto err_bin;
+        }
+    } else {
+#if FTS_AUTO_LIC_UPGRADE_EN
+        if (upg->func->lic_upgrade) {
+            ret = upg->func->lic_upgrade(fw_file_buf, fw_file_len);
+        } else {
+            FTS_INFO("lic_upgrade function is null, no upgrade");
+        }
+#endif
+        if (upg->func->upgrade) {
+            ret = upg->func->upgrade(fw_file_buf, fw_file_len);
+        } else {
+            FTS_INFO("upgrade function is null, no upgrade");
+        }
+    }
+
+    if (ret < 0) {
+        FTS_ERROR("upgrade fw bin failed");
+        fts_fwupg_reset_in_boot();
+        goto err_bin;
+    }
+
+    FTS_INFO("upgrade fw bin success");
+    ret = 0;
+
+err_bin:
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    fts_get_default_heatmap_mode(upg->ts_data);
+    upg->ts_data->fw_heatmap_mode = upg->ts_data->fw_default_heatmap_mode;
+    /* Update firmware feature settings after flashing firmware. */
+    fts_update_feature_setting(upg->ts_data);
+
+    fts_irq_enable();
+    upg->ts_data->fw_loading = 0;
+
+    if (fw != NULL) {
+        release_firmware(fw);
+        fw = NULL;
+    }
+    return ret;
+}
+
+int fts_enter_test_environment(bool test_state)
+{
+    return 0;
+}
+#if FTS_AUTO_LIC_UPGRADE_EN
+static int fts_lic_get_vid_in_tp(u16 *vid)
+{
+    int ret = 0;
+    u8 val[2] = { 0 };
+
+    if (NULL == vid) {
+        FTS_ERROR("vid is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_VENDOR_ID, &val[0]);
+    if (fts_data->ic_info.is_incell)
+        ret = fts_read_reg(FTS_REG_MODULE_ID, &val[1]);
+    if (ret < 0) {
+        FTS_ERROR("read vid from tp fail");
+        return ret;
+    }
+
+    *vid = *(u16 *)val;
+    return 0;
+}
+
+static int fts_lic_get_vid_in_host(struct fts_upgrade *upg, u16 *vid)
+{
+    u8 val[2] = { 0 };
+    u8 *licbuf = NULL;
+    u32 conf_saddr = 0;
+
+    if (!upg || !upg->func || !upg->lic || !vid) {
+        FTS_ERROR("upgrade/func/get_hlic_ver/lic/vid is null");
+        return -EINVAL;
+    }
+
+    if (upg->lic_length < FTS_MAX_LEN_SECTOR) {
+        FTS_ERROR("lic length(%x) fail", upg->lic_length);
+        return -EINVAL;
+    }
+
+    licbuf  = upg->lic;
+    conf_saddr = upg->func->fwcfgoff;
+    val[0] = licbuf[conf_saddr + FTS_CONIFG_VENDORID_OFF];
+    if (fts_data->ic_info.is_incell)
+        val[1] = licbuf[conf_saddr + FTS_CONIFG_MODULEID_OFF];
+
+    *vid = *(u16 *)val;
+    return 0;
+}
+
+static int fts_lic_get_ver_in_tp(u8 *ver)
+{
+    int ret = 0;
+
+    if (NULL == ver) {
+        FTS_ERROR("ver is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_LIC_VER, ver);
+    if (ret < 0) {
+        FTS_ERROR("read lcd initcode ver from tp fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_lic_get_ver_in_host(struct fts_upgrade *upg, u8 *ver)
+{
+    int ret = 0;
+
+    if (!upg || !upg->func || !upg->func->get_hlic_ver || !upg->lic) {
+        FTS_ERROR("upgrade/func/get_hlic_ver/lic is null");
+        return -EINVAL;
+    }
+
+    ret = upg->func->get_hlic_ver(upg->lic);
+    if (ret < 0) {
+        FTS_ERROR("get host lcd initial code version fail");
+        return ret;
+    }
+
+    *ver = (u8)ret;
+    return ret;
+}
+
+static bool fts_lic_need_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    u8 initcode_ver_in_tp = 0;
+    u8 initcode_ver_in_host = 0;
+    u16 vid_in_tp = 0;
+    u16 vid_in_host = 0;
+    bool fwvalid = false;
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if ( !fwvalid) {
+        FTS_INFO("fw is invalid, no upgrade lcd init code");
+        return false;
+    }
+
+    ret = fts_lic_get_vid_in_host(upg, &vid_in_host);
+    if (ret < 0) {
+        FTS_ERROR("vendor id in host invalid");
+        return false;
+    }
+
+    ret = fts_lic_get_vid_in_tp(&vid_in_tp);
+    if (ret < 0) {
+        FTS_ERROR("vendor id in tp invalid");
+        return false;
+    }
+
+    FTS_DEBUG("vid in tp:0x%04x, host:0x%04x", vid_in_tp, vid_in_host);
+    if (vid_in_tp != vid_in_host) {
+        FTS_INFO("vendor id in tp&host are different, no upgrade lic");
+        return false;
+    }
+
+    ret = fts_lic_get_ver_in_host(upg, &initcode_ver_in_host);
+    if (ret < 0) {
+        FTS_ERROR("init code in host invalid");
+        return false;
+    }
+
+    ret = fts_lic_get_ver_in_tp(&initcode_ver_in_tp);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xE4 fail");
+        return false;
+    }
+
+    FTS_DEBUG("lcd initial code version in tp:%x, host:%x",
+              initcode_ver_in_tp, initcode_ver_in_host);
+    if (0xA5 == initcode_ver_in_tp) {
+        FTS_INFO("lcd init code ver is 0xA5, don't upgade init code");
+        return false;
+    } else if (0xFF == initcode_ver_in_tp) {
+        FTS_DEBUG("lcd init code in tp is invalid, need upgrade init code");
+        return true;
+    } else if (initcode_ver_in_tp < initcode_ver_in_host)
+        return true;
+    else
+        return false;
+}
+
+static int fts_lic_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool hlic_upgrade = false;
+    int upgrade_count = 0;
+    u8 ver = 0;
+
+    FTS_INFO("lcd initial code auto upgrade function");
+    if ((!upg) || (!upg->func) || (!upg->func->lic_upgrade)) {
+        FTS_ERROR("lcd upgrade function is null");
+        return -EINVAL;
+    }
+
+    hlic_upgrade = fts_lic_need_upgrade(upg);
+    FTS_INFO("lcd init code upgrade flag:%d", hlic_upgrade);
+    if (hlic_upgrade) {
+        FTS_INFO("lcd initial code need upgrade, upgrade begin...");
+        do {
+            FTS_INFO("lcd initial code upgrade times:%d", upgrade_count);
+            upgrade_count++;
+
+            ret = upg->func->lic_upgrade(upg->lic, upg->lic_length);
+            if (ret < 0) {
+                fts_fwupg_reset_in_boot();
+            } else {
+                fts_lic_get_ver_in_tp(&ver);
+                FTS_INFO("success upgrade to lcd initcode ver:%02x", ver);
+                break;
+            }
+        } while (upgrade_count < 2);
+    } else {
+        FTS_INFO("lcd initial code don't need upgrade");
+    }
+
+    return ret;
+}
+#endif /* FTS_AUTO_LIC_UPGRADE_EN */
+
+
+static int fts_param_get_ver_in_tp(u8 *ver)
+{
+    int ret = 0;
+
+    if (NULL == ver) {
+        FTS_ERROR("ver is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_IDE_PARA_VER_ID, ver);
+    if (ret < 0) {
+        FTS_ERROR("read fw param ver from tp fail");
+        return ret;
+    }
+
+    if ((0x00 == *ver) || (0xFF == *ver)) {
+        FTS_INFO("param version in tp invalid");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int fts_param_get_ver_in_host(struct fts_upgrade *upg, u8 *ver)
+{
+    if ((!upg) || (!upg->func) || (!upg->fw) || (!ver)) {
+        FTS_ERROR("fts_data/upgrade/func/fw/ver is NULL");
+        return -EINVAL;
+    }
+
+    if (upg->fw_length < upg->func->paramcfgveroff) {
+        FTS_ERROR("fw len(%x) < paramcfg ver offset(%x)",
+                  upg->fw_length, upg->func->paramcfgveroff);
+        return -EINVAL;
+    }
+
+    FTS_INFO("fw paramcfg version offset:%x", upg->func->paramcfgveroff);
+    *ver = upg->fw[upg->func->paramcfgveroff];
+
+    if ((0x00 == *ver) || (0xFF == *ver)) {
+        FTS_INFO("param version in host invalid");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * return: < 0 : error
+ *         == 0: no ide
+ *         == 1: ide
+ */
+static int fts_param_ide_in_host(struct fts_upgrade *upg)
+{
+    u32 off = 0;
+
+    if ((!upg) || (!upg->func) || (!upg->fw)) {
+        FTS_ERROR("fts_data/upgrade/func/fw is NULL");
+        return -EINVAL;
+    }
+
+    if (upg->fw_length < upg->func->paramcfgoff + FTS_FW_IDE_SIG_LEN) {
+        FTS_INFO("fw len(%x) < paramcfg offset(%x), no IDE",
+                 upg->fw_length, upg->func->paramcfgoff + FTS_FW_IDE_SIG_LEN);
+        return 0;
+    }
+
+    off = upg->func->paramcfgoff;
+    if (0 == memcmp(&upg->fw[off], FTS_FW_IDE_SIG, FTS_FW_IDE_SIG_LEN)) {
+        FTS_INFO("fw in host is IDE version");
+        return 1;
+    }
+
+    FTS_INFO("fw in host isn't IDE version");
+    return 0;
+}
+
+/*
+ * return: < 0 : error
+ *         0   : no ide
+ *         1   : ide
+ */
+static int fts_param_ide_in_tp(u8 *val)
+{
+    int ret = 0;
+
+    ret = fts_read_reg(FTS_REG_IDE_PARA_STATUS, val);
+    if (ret < 0) {
+        FTS_ERROR("read IDE PARAM STATUS in tp fail");
+        return ret;
+    }
+
+    if ((*val != 0xFF) && ((*val & 0x80) == 0x80)) {
+        FTS_INFO("fw in tp is IDE version");
+        return 1;
+    }
+
+    FTS_INFO("fw in tp isn't IDE version");
+    return 0;
+}
+
+/************************************************************************
+ * fts_param_need_upgrade - check fw paramcfg need upgrade or not
+ *
+ * Return:  < 0 : error if paramcfg need upgrade
+ *          0   : no need upgrade
+ *          1   : need upgrade app + param
+ *          2   : need upgrade param
+ ***********************************************************************/
+static int fts_param_need_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    u8 val = 0;
+    int ide_in_host = 0;
+    int ide_in_tp = 0;
+    u8 ver_in_host = 0;
+    u8 ver_in_tp = 0;
+    bool fwvalid = false;
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if ( !fwvalid) {
+        FTS_INFO("fw is invalid, upgrade app+param");
+        return 1;
+    }
+
+    ide_in_host = fts_param_ide_in_host(upg);
+    if (ide_in_host < 0) {
+        FTS_INFO("fts_param_ide_in_host fail");
+        return ide_in_host;
+    }
+
+    ide_in_tp = fts_param_ide_in_tp(&val);
+    if (ide_in_tp < 0) {
+        FTS_INFO("fts_param_ide_in_tp fail");
+        return ide_in_tp;
+    }
+
+    if ((0 == ide_in_host) && (0 == ide_in_tp)) {
+        FTS_INFO("fw in host&tp are both no ide");
+        return 0;
+    } else if (ide_in_host != ide_in_tp) {
+        FTS_INFO("fw in host&tp not equal, need upgrade app+param");
+        return 1;
+    } else if ((1 == ide_in_host) && (1 == ide_in_tp)) {
+        FTS_INFO("fw in host&tp are both ide");
+        if ((val & 0x7F) != 0x00) {
+            FTS_INFO("param invalid, need upgrade param");
+            return 2;
+        }
+
+        ret = fts_param_get_ver_in_host(upg, &ver_in_host);
+        if (ret < 0) {
+            FTS_ERROR("param version in host invalid");
+            return ret;
+        }
+
+        ret = fts_param_get_ver_in_tp(&ver_in_tp);
+        if (ret < 0) {
+            FTS_ERROR("get IDE param ver in tp fail");
+            return ret;
+        }
+
+        FTS_INFO("fw paramcfg version in tp:%x, host:%x",
+                 ver_in_tp, ver_in_host);
+        if (ver_in_tp != ver_in_host) {
+            return 2;
+        }
+    }
+
+    return 0;
+}
+
+static int fts_fwupg_get_ver_in_tp(u8 *ver)
+{
+    int ret = 0;
+
+    if (NULL == ver) {
+        FTS_ERROR("ver is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_FW_MAJOR_VER, ver);
+    if (ret < 0) {
+        FTS_ERROR("read fw major ver from tp fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_fwupg_get_ver_in_host(struct fts_upgrade *upg, u8 *ver)
+{
+    if ((!upg) || (!upg->func) || (!upg->fw) || (!ver)) {
+        FTS_ERROR("fts_data/upgrade/func/fw/ver is NULL");
+        return -EINVAL;
+    }
+
+    if (upg->fw_length < upg->func->fwveroff) {
+        FTS_ERROR("fw len(0x%0x) < fw ver offset(0x%x)",
+                  upg->fw_length, upg->func->fwveroff);
+        return -EINVAL;
+    }
+
+    FTS_INFO("fw version offset:0x%x", upg->func->fwveroff);
+    *ver = upg->fw[upg->func->fwveroff];
+    return 0;
+}
+
+static bool fts_fwupg_need_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool fwvalid = false;
+    u8 fw_ver_in_host = 0;
+    u8 fw_ver_in_tp = 0;
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if (fwvalid) {
+        ret = fts_fwupg_get_ver_in_host(upg, &fw_ver_in_host);
+        if (ret < 0) {
+            FTS_ERROR("get fw ver in host fail");
+            return false;
+        }
+
+        ret = fts_fwupg_get_ver_in_tp(&fw_ver_in_tp);
+        if (ret < 0) {
+            FTS_ERROR("get fw ver in tp fail");
+            return false;
+        }
+
+        FTS_INFO("fw major version in tp:%x, host:%x", fw_ver_in_tp, fw_ver_in_host);
+        if (fw_ver_in_tp != fw_ver_in_host) {
+            return true;
+        }
+    } else {
+        FTS_INFO("fw invalid, need upgrade fw");
+        return true;
+    }
+
+    return false;
+}
+
+/************************************************************************
+ * Name: fts_fw_upgrade
+ * Brief: fw upgrade main entry, run in following steps
+ *        1. check fw version(A6), not equal, will upgrade app(+param)
+ *        2. if fw version equal, will check ide, will upgrade app(+param)
+ *        in the follow situation
+ *          a. host&tp IDE's type are not equal, will upgrade app+param
+ *          b. host&tp are both IDE's type, and param's version are not
+ *          equal, will upgrade param
+ * Input:
+ * Output:
+ * Return: return 0 if success, otherwise return error code
+ ***********************************************************************/
+int fts_fwupg_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool upgrade_flag = false;
+    int upgrade_count = 0;
+    u8 ver = 0;
+
+    FTS_INFO("fw auto upgrade function");
+    if ((NULL == upg) || (NULL == upg->func)) {
+        FTS_ERROR("upg/upg->func is null");
+        return -EINVAL;
+    }
+
+    upgrade_flag = fts_fwupg_need_upgrade(upg);
+    FTS_INFO("fw upgrade flag:%d", upgrade_flag);
+    do {
+        upgrade_count++;
+        if (upgrade_flag) {
+            FTS_INFO("upgrade fw app(times:%d)", upgrade_count);
+            if (upg->func->upgrade) {
+                ret = upg->func->upgrade(upg->fw, upg->fw_length);
+                if (ret < 0) {
+                    fts_fwupg_reset_in_boot();
+                } else {
+                    fts_fwupg_get_ver_in_tp(&ver);
+                    FTS_INFO("success upgrade to fw version %02x", ver);
+                    break;
+                }
+            } else {
+                FTS_ERROR("upgrade func/upgrade is null, return immediately");
+                ret = -ENODATA;
+                break;
+            }
+        } else {
+            if (upg->func->param_upgrade) {
+                ret = fts_param_need_upgrade(upg);
+                if (ret <= 0) {
+                    FTS_INFO("param don't need upgrade");
+                    break;
+                } else if (1 == ret) {
+                    FTS_INFO("force upgrade fw app(times:%d)", upgrade_count);
+                    if (upg->func->upgrade) {
+                        ret = upg->func->upgrade(upg->fw, upg->fw_length);
+                        if (ret < 0) {
+                            fts_fwupg_reset_in_boot();
+                        } else {
+                            break;
+                        }
+                    }
+                } else if (2 == ret) {
+                    FTS_INFO("upgrade param area(times:%d)", upgrade_count);
+                    ret = upg->func->param_upgrade(upg->fw, upg->fw_length);
+                    if (ret < 0) {
+                        fts_fwupg_reset_in_boot();
+                    } else {
+                        fts_param_get_ver_in_tp(&ver);
+                        FTS_INFO("success upgrade to fw param version %02x", ver);
+                        break;
+                    }
+                } else
+                    break;
+            } else {
+                break;
+            }
+        }
+    } while (upgrade_count < 2);
+
+    return ret;
+}
+
+/************************************************************************
+ * fts_fwupg_auto_upgrade - upgrade main entry
+ ***********************************************************************/
+static void fts_fwupg_auto_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+
+    FTS_INFO("********************FTS enter upgrade********************");
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return ;
+    }
+
+    ret = fts_fwupg_upgrade(upg);
+    if (ret < 0)
+        FTS_ERROR("**********tp fw(app/param) upgrade failed**********");
+    else
+        FTS_INFO("**********tp fw(app/param) no upgrade/upgrade success**********");
+
+#if FTS_AUTO_LIC_UPGRADE_EN
+    ret = fts_lic_upgrade(upg);
+    if (ret < 0)
+        FTS_ERROR("**********lcd init code upgrade failed**********");
+    else
+        FTS_INFO("**********lcd init code no upgrade/upgrade success**********");
+#endif
+
+    FTS_INFO("********************FTS exit upgrade********************");
+}
+
+static int fts_fwupg_get_vendorid(struct fts_upgrade *upg, int *vid)
+{
+    int ret = 0;
+    bool fwvalid = false;
+    u8 vendor_id = 0;
+    u8 module_id = 0;
+    u32 fwcfg_addr = 0;
+    u8 cfgbuf[FTS_HEADER_LEN] = { 0 };
+
+    FTS_INFO("read vendor id from tp");
+    if ((!upg) || (!upg->func) || (!upg->ts_data) || (!vid)) {
+        FTS_ERROR("upgrade/func/ts_data/vid is null");
+        return -EINVAL;
+    }
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if (fwvalid) {
+        ret = fts_read_reg(FTS_REG_VENDOR_ID, &vendor_id);
+        if (upg->ts_data->ic_info.is_incell)
+            ret = fts_read_reg(FTS_REG_MODULE_ID, &module_id);
+    } else {
+        fwcfg_addr =  upg->func->fwcfgoff;
+        ret = fts_flash_read(fwcfg_addr, cfgbuf, FTS_HEADER_LEN);
+
+        if ((cfgbuf[FTS_CONIFG_VENDORID_OFF] +
+             cfgbuf[FTS_CONIFG_VENDORID_OFF + 1]) == 0xFF)
+            vendor_id = cfgbuf[FTS_CONIFG_VENDORID_OFF];
+        if (upg->ts_data->ic_info.is_incell) {
+            if ((cfgbuf[FTS_CONIFG_MODULEID_OFF] +
+                 cfgbuf[FTS_CONIFG_MODULEID_OFF + 1]) == 0xFF)
+                module_id = cfgbuf[FTS_CONIFG_MODULEID_OFF];
+        }
+    }
+
+    if (ret < 0) {
+        FTS_ERROR("fail to get vendor id from tp");
+        return ret;
+    }
+
+    *vid = (int)((module_id << 8) + vendor_id);
+    return 0;
+}
+
+static int fts_fwupg_get_module_info(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    int i = 0;
+    struct upgrade_module *info = &module_list[0];
+
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return -EINVAL;
+    }
+
+    if (FTS_GET_MODULE_NUM > 1) {
+        /* support multi modules, must read correct module id(vendor id) */
+        ret = fts_fwupg_get_vendorid(upg, &upg->module_id);
+        if (ret < 0) {
+            FTS_ERROR("get vendor id failed");
+            return ret;
+        }
+        FTS_INFO("module id:%04x", upg->module_id);
+        for (i = 0; i < FTS_GET_MODULE_NUM; i++) {
+            info = &module_list[i];
+            if (upg->module_id == info->id) {
+                FTS_INFO("module id match, get module info pass");
+                break;
+            }
+        }
+        if (i >= FTS_GET_MODULE_NUM) {
+            FTS_ERROR("no module id match, don't get file");
+            return -ENODATA;
+        }
+    }
+
+    upg->module_info = info;
+    return 0;
+}
+
+static int fts_get_fw_file_via_request_firmware(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    const struct firmware *fw = NULL;
+    u8 *tmpbuf = NULL;
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+
+    if (!upg || !upg->ts_data || !upg->ts_data->dev) {
+        FTS_ERROR("upg/ts_data/dev is null");
+        return -EINVAL;
+    }
+
+    snprintf(fwname, FILE_NAME_LENGTH, "%s.bin", FTS_FW_NAME);
+
+    ret = request_firmware(&fw, fwname, upg->ts_data->dev);
+    if (0 == ret) {
+        FTS_INFO("firmware(%s) request successfully", fwname);
+        tmpbuf = vmalloc(fw->size);
+        if (NULL == tmpbuf) {
+            FTS_ERROR("fw buffer vmalloc fail");
+            ret = -ENOMEM;
+        } else {
+            memcpy(tmpbuf, fw->data, fw->size);
+            upg->fw = tmpbuf;
+            upg->fw_length = fw->size;
+            upg->fw_from_request = 1;
+        }
+    } else {
+        FTS_INFO("firmware(%s) request fail,ret=%d", fwname, ret);
+    }
+
+    if (fw != NULL) {
+        release_firmware(fw);
+        fw = NULL;
+    }
+
+    return ret;
+}
+
+static int fts_get_fw_file_via_i(struct fts_upgrade *upg)
+{
+    upg->fw = upg->module_info->fw_file;
+    upg->fw_length = upg->module_info->fw_len;
+    upg->fw_from_request = 0;
+
+    return 0;
+}
+
+/*****************************************************************************
+ *  Name: fts_fwupg_get_fw_file
+ *  Brief: get fw image/file,
+ *         If support muitl modules, please set FTS_GET_MODULE_NUM, and FTS_-
+ *         MODULE_ID/FTS_MODULE_NAME;
+ *         If get fw via .i file, please set FTS_FW_REQUEST_SUPPORT=0, and F-
+ *         TS_MODULE_ID; will use module id to distingwish different modules;
+ *         If get fw via reques_firmware(), please set FTS_FW_REQUEST_SUPPORT
+ *         =1, and FTS_MODULE_NAME; fw file name will be composed of "focalt-
+ *         ech_ts_fw_" & FTS_VENDOR_NAME;
+ *
+ *         If have flash, module_id=vendor_id, If non-flash,module_id need
+ *         transfer from LCD driver(gpio or lcm_id or ...);
+ *  Input:
+ *  Output:
+ *  Return: return 0 if success, otherwise return error code
+ *****************************************************************************/
+static int fts_fwupg_get_fw_file(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool get_fw_i_flag = false;
+
+    FTS_DEBUG("get upgrade fw file");
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return -EINVAL;
+    }
+
+    ret = fts_fwupg_get_module_info(upg);
+    if ((ret < 0) || (!upg->module_info)) {
+        FTS_ERROR("get module info fail");
+        return ret;
+    }
+
+    if (FTS_FW_REQUEST_SUPPORT) {
+        ret = fts_get_fw_file_via_request_firmware(upg);
+        if (ret != 0) {
+            get_fw_i_flag = true;
+        }
+    } else {
+        get_fw_i_flag = true;
+    }
+
+    if (get_fw_i_flag) {
+        ret = fts_get_fw_file_via_i(upg);
+    }
+
+    upg->lic = upg->fw;
+    upg->lic_length = upg->fw_length;
+
+    FTS_INFO("upgrade fw file len:%d", upg->fw_length);
+    if (upg->fw_length < FTS_MIN_LEN) {
+        FTS_ERROR("fw file len(%d) fail", upg->fw_length);
+        return -ENODATA;
+    }
+
+    return ret;
+}
+
+static void fts_fwupg_init_ic_detail(struct fts_upgrade *upg)
+{
+    if (upg && upg->func && upg->func->init) {
+        upg->func->init(upg->fw, upg->fw_length);
+    }
+}
+
+/*****************************************************************************
+ *  Name: fts_fwupg_work
+ *  Brief: 1. get fw image/file
+ *         2. ic init if have
+ *         3. call upgrade main function(fts_fwupg_auto_upgrade)
+ *  Input:
+ *  Output:
+ *  Return:
+ *****************************************************************************/
+static void fts_fwupg_work(struct work_struct *work)
+{
+    int ret = 0;
+    struct fts_upgrade *upg = fwupgrade;
+
+#if !FTS_AUTO_UPGRADE_EN
+    FTS_INFO("FTS_AUTO_UPGRADE_EN is disabled, not upgrade when power on");
+    return ;
+#endif
+
+    FTS_INFO("fw upgrade work function");
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return ;
+    }
+
+    upg->ts_data->fw_loading = 1;
+    fts_irq_disable();
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    /* get fw */
+    ret = fts_fwupg_get_fw_file(upg);
+    if (ret < 0) {
+        FTS_ERROR("get file fail, can't upgrade");
+    } else {
+        /* ic init if have */
+        fts_fwupg_init_ic_detail(upg);
+        /* run auto upgrade */
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+        fts_ts_set_bus_ref(upg->ts_data, FTS_TS_BUS_REF_FW_UPDATE, true);
+#endif
+        fts_fwupg_auto_upgrade(upg);
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
+        fts_ts_set_bus_ref(upg->ts_data, FTS_TS_BUS_REF_FW_UPDATE, false);
+#endif
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    fts_get_default_heatmap_mode(upg->ts_data);
+    upg->ts_data->fw_heatmap_mode = upg->ts_data->fw_default_heatmap_mode;
+    /* Update firmware feature settings after flashing firmware. */
+    fts_update_feature_setting(upg->ts_data);
+
+    fts_irq_enable();
+    upg->ts_data->fw_loading = 0;
+}
+
+int fts_fwupg_init(struct fts_ts_data *ts_data)
+{
+    int i = 0;
+    int j = 0;
+    u16 ic_stype = 0;
+    struct upgrade_func *func = upgrade_func_list[0];
+    int func_count = sizeof(upgrade_func_list) / sizeof(upgrade_func_list[0]);
+
+    FTS_INFO("fw upgrade init function");
+
+    if (!ts_data || !ts_data->ts_workqueue) {
+        FTS_ERROR("ts_data/workqueue is NULL, can't run upgrade function");
+        return -EINVAL;
+    }
+
+    if (0 == func_count) {
+        FTS_ERROR("no upgrade function in tp driver");
+        return -ENODATA;
+    }
+
+    fwupgrade = (struct fts_upgrade *)kzalloc(sizeof(*fwupgrade), GFP_KERNEL);
+    if (NULL == fwupgrade) {
+        FTS_ERROR("malloc memory for upgrade fail");
+        return -ENOMEM;
+    }
+
+    ic_stype = ts_data->ic_info.ids.type;
+    if (1 == func_count) {
+        fwupgrade->func = func;
+    } else {
+        for (i = 0; i < func_count; i++) {
+            func = upgrade_func_list[i];
+            for (j = 0; j < FTS_MAX_COMPATIBLE_TYPE; j++) {
+                if (0 == func->ctype[j])
+                    break;
+                else if (func->ctype[j] == ic_stype) {
+                    FTS_INFO("match upgrade function,type:%x", (int)func->ctype[j]);
+                    fwupgrade->func = func;
+                }
+            }
+        }
+    }
+
+    if (NULL == fwupgrade->func) {
+        FTS_ERROR("no upgrade function match, can't upgrade");
+        kfree(fwupgrade);
+        fwupgrade = NULL;
+        return -ENODATA;
+    }
+
+    fwupgrade->ts_data = ts_data;
+    INIT_WORK(&ts_data->fwupg_work, fts_fwupg_work);
+    queue_work(ts_data->ts_workqueue, &ts_data->fwupg_work);
+
+    return 0;
+}
+
+int fts_fwupg_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    if (fwupgrade) {
+        if (fwupgrade->fw_from_request) {
+            vfree(fwupgrade->fw);
+            fwupgrade->fw = NULL;
+        }
+
+        kfree(fwupgrade);
+        fwupgrade = NULL;
+    }
+    FTS_FUNC_EXIT();
+    return 0;
+}
diff --git a/ft3658/focaltech_flash.h b/ft3658/focaltech_flash.h
new file mode 100644
index 0000000..5a370c5
--- /dev/null
+++ b/ft3658/focaltech_flash.h
@@ -0,0 +1,217 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R)£¬All Rights Reserved.
+*
+* File Name: focaltech_flash.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-07
+*
+* Abstract:
+*
+************************************************************************/
+#ifndef __LINUX_FOCALTECH_FLASH_H__
+#define __LINUX_FOCALTECH_FLASH_H__
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_CMD_RESET                               0x07
+#define FTS_ROMBOOT_CMD_SET_PRAM_ADDR               0xAD
+#define FTS_ROMBOOT_CMD_SET_PRAM_ADDR_LEN           4
+#define FTS_ROMBOOT_CMD_WRITE                       0xAE
+#define FTS_ROMBOOT_CMD_START_APP                   0x08
+#define FTS_DELAY_PRAMBOOT_START                    100
+#define FTS_ROMBOOT_CMD_ECC                         0xCC
+#define FTS_PRAM_SADDR                              0x000000
+#define FTS_DRAM_SADDR                              0xD00000
+
+#define FTS_CMD_READ                                0x03
+#define FTS_CMD_READ_DELAY                          1
+#define FTS_CMD_READ_LEN                            4
+#define FTS_CMD_READ_LEN_SPI                        6
+#define FTS_CMD_FLASH_TYPE                          0x05
+#define FTS_CMD_FLASH_MODE                          0x09
+#define FLASH_MODE_WRITE_FLASH_VALUE                0x0A
+#define FLASH_MODE_UPGRADE_VALUE                    0x0B
+#define FLASH_MODE_LIC_VALUE                        0x0C
+#define FLASH_MODE_PARAM_VALUE                      0x0D
+#define FTS_CMD_ERASE_APP                           0x61
+#define FTS_REASE_APP_DELAY                         1350
+#define FTS_ERASE_SECTOR_DELAY                      60
+#define FTS_RETRIES_REASE                           50
+#define FTS_RETRIES_DELAY_REASE                     400
+#define FTS_CMD_FLASH_STATUS                        0x6A
+#define FTS_CMD_FLASH_STATUS_LEN                    2
+#define FTS_CMD_FLASH_STATUS_NOP                    0x0000
+#define FTS_CMD_FLASH_STATUS_ECC_OK                 0xF055
+#define FTS_CMD_FLASH_STATUS_ERASE_OK               0xF0AA
+#define FTS_CMD_FLASH_STATUS_WRITE_OK               0x1000
+#define FTS_CMD_ECC_INIT                            0x64
+#define FTS_CMD_ECC_CAL                             0x65
+#define FTS_CMD_ECC_CAL_LEN                         7
+#define FTS_RETRIES_ECC_CAL                         10
+#define FTS_RETRIES_DELAY_ECC_CAL                   50
+#define FTS_CMD_ECC_READ                            0x66
+#define FTS_CMD_DATA_LEN                            0xB0
+#define FTS_CMD_APP_DATA_LEN_INCELL                 0x7A
+#define FTS_CMD_DATA_LEN_LEN                        4
+#define FTS_CMD_SET_WFLASH_ADDR                     0xAB
+#define FTS_CMD_SET_RFLASH_ADDR                     0xAC
+#define FTS_LEN_SET_ADDR                            4
+#define FTS_CMD_WRITE                               0xBF
+#define FTS_RETRIES_WRITE                           100
+#define FTS_RETRIES_DELAY_WRITE                     1
+#define FTS_CMD_WRITE_LEN                           6
+#define FTS_DELAY_READ_ID                           20
+#define FTS_DELAY_UPGRADE_RESET                     80
+#define PRAMBOOT_MIN_SIZE                           0x120
+#define PRAMBOOT_MAX_SIZE                           (64*1024)
+#define FTS_FLASH_PACKET_LENGTH                     128     /* max=128 */
+#define FTS_MAX_LEN_ECC_CALC                        0xFFFE /* must be even */
+#define FTS_MIN_LEN                                 0x120
+#define FTS_MAX_LEN_FILE                            (256 * 1024)
+#define FTS_MAX_LEN_APP                             (64 * 1024)
+#define FTS_MAX_LEN_SECTOR                          (4 * 1024)
+#define FTS_CONIFG_VENDORID_OFF                     0x04
+#define FTS_CONIFG_MODULEID_OFF                     0x1E
+#define FTS_CONIFG_PROJECTID_OFF                    0x20
+#define FTS_APPINFO_OFF                             0x100
+#define FTS_APPINFO_APPLEN_OFF                      0x00
+#define FTS_APPINFO_APPLEN2_OFF                     0x12
+#define FTS_REG_UPGRADE                             0xFC
+#define FTS_REG_UPGRADE2                            0xBC
+#define FTS_UPGRADE_AA                              0xAA
+#define FTS_UPGRADE_55                              0x55
+#define FTS_DELAY_UPGRADE_AA                        10
+#define FTS_UPGRADE_LOOP                            30
+#define FTS_HEADER_LEN                              32
+#define FTS_FW_BIN_FILEPATH                         "/sdcard/"
+#define FTS_FW_IDE_SIG                              "IDE_"
+#define FTS_FW_IDE_SIG_LEN                          4
+#define MAX_MODULE_VENDOR_NAME_LEN                  16
+
+#define FTS_ROMBOOT_CMD_ECC_NEW_LEN                 7
+#define FTS_ECC_FINISH_TIMEOUT                      100
+#define FTS_ROMBOOT_CMD_ECC_FINISH                  0xCE
+#define FTS_ROMBOOT_CMD_ECC_FINISH_OK_A5            0xA5
+#define FTS_ROMBOOT_CMD_ECC_FINISH_OK_00            0x00
+#define FTS_ROMBOOT_CMD_ECC_READ                    0xCD
+#define AL2_FCS_COEF                ((1 << 15) + (1 << 10) + (1 << 3))
+
+#define FTS_APP_INFO_OFFSET                         0x100
+
+enum FW_STATUS {
+    FTS_RUN_IN_ERROR,
+    FTS_RUN_IN_APP,
+    FTS_RUN_IN_ROM,
+    FTS_RUN_IN_PRAM,
+    FTS_RUN_IN_BOOTLOADER,
+};
+
+enum FW_FLASH_MODE {
+    FLASH_MODE_APP,
+    FLASH_MODE_LIC,
+    FLASH_MODE_PARAM,
+    FLASH_MODE_ALL,
+};
+
+enum ECC_CHECK_MODE {
+    ECC_CHECK_MODE_XOR,
+    ECC_CHECK_MODE_CRC16,
+};
+
+enum UPGRADE_SPEC {
+    UPGRADE_SPEC_V_1_0 = 0x0100,
+};
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+/* IC info */
+struct upgrade_func {
+    u16 ctype[FTS_MAX_COMPATIBLE_TYPE];
+    u32 fwveroff;
+    u32 fwcfgoff;
+    u32 appoff;
+    u32 licoff;
+    u32 paramcfgoff;
+    u32 paramcfgveroff;
+    u32 paramcfg2off;
+    int pram_ecc_check_mode;
+    int fw_ecc_check_mode;
+    int upgspec_version;
+    bool new_return_value_from_ic;
+    bool appoff_handle_in_ic;
+    bool is_reset_register_BC;
+    bool read_boot_id_need_reset;
+    bool hid_supported;
+    bool pramboot_supported;
+    u8 *pramboot;
+    u32 pb_length;
+    int (*init)(u8 *, u32);
+    int (*write_pramboot_private)(void);
+    int (*upgrade)(u8 *, u32);
+    int (*get_hlic_ver)(u8 *);
+    int (*lic_upgrade)(u8 *, u32);
+    int (*param_upgrade)(u8 *, u32);
+    int (*force_upgrade)(u8 *, u32);
+};
+
+struct upgrade_setting_nf {
+    u8 rom_idh;
+    u8 rom_idl;
+    u16 reserved;
+    u32 app2_offset;
+    u32 ecclen_max;
+    u8 eccok_val;
+    u8 upgsts_boot;
+    u8 delay_init;
+    bool spi_pe;
+    bool half_length;
+    bool fd_check;
+    bool drwr_support;
+};
+
+struct upgrade_module {
+    int id;
+    char vendor_name[MAX_MODULE_VENDOR_NAME_LEN];
+    u8 *fw_file;
+    u32 fw_len;
+};
+
+struct fts_upgrade {
+    struct fts_ts_data *ts_data;
+    struct upgrade_module *module_info;
+    struct upgrade_func *func;
+    struct upgrade_setting_nf *setting_nf;
+    int module_id;
+    bool fw_from_request;
+    u8 *fw;
+    u32 fw_length;
+    u8 *lic;
+    u32 lic_length;
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+extern struct upgrade_func upgrade_func_ft5652;
+
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+int fts_fwupg_reset_in_boot(void);
+int fts_fwupg_enter_into_boot(void);
+int fts_fwupg_erase(u32 delay);
+int fts_fwupg_ecc_cal(u32 saddr, u32 len);
+int fts_flash_write_buf(u32 saddr, u8 *buf, u32 len, u32 delay);
+int fts_flash_read_buf(u32 saddr, u8 *buf, u32 len);
+int fts_fwupg_upgrade(struct fts_upgrade *upg);
+#endif
diff --git a/ft3658/focaltech_flash/Makefile b/ft3658/focaltech_flash/Makefile
new file mode 100644
index 0000000..e5e01a2
--- /dev/null
+++ b/ft3658/focaltech_flash/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_upgrade_ft3658u.o
\ No newline at end of file
diff --git a/ft3658/focaltech_flash/focaltech_upgrade_ft3658u.c b/ft3658/focaltech_flash/focaltech_upgrade_ft3658u.c
new file mode 100644
index 0000000..9b424c9
--- /dev/null
+++ b/ft3658/focaltech_flash/focaltech_upgrade_ft3658u.c
@@ -0,0 +1,140 @@
+/*
+ *
+ * FocalTech fts TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_upgrade_ft5652.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2019-11-20
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "../focaltech_flash.h"
+
+#define FTS_DELAY_ERASE_PAGE_2K         80
+#define FTS_SIZE_PAGE_2K                2048
+
+/************************************************************************
+* Name: fts_ft5652_upgrade
+* Brief:
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+static int fts_ft5652_upgrade(u8 *buf, u32 len)
+{
+    int ret = 0;
+    u32 start_addr = 0;
+    u8 cmd[4] = { 0 };
+    u32 delay = 0;
+    int ecc_in_host = 0;
+    int ecc_in_tp = 0;
+
+    if ((NULL == buf) || (len < FTS_MIN_LEN)) {
+        FTS_ERROR("buffer/len(%x) is invalid", len);
+        return -EINVAL;
+    }
+
+    /* enter into upgrade environment */
+    ret = fts_fwupg_enter_into_boot();
+    if (ret < 0) {
+        FTS_ERROR("enter into pramboot/bootloader fail,ret=%d", ret);
+        goto fw_reset;
+    }
+
+    cmd[0] = FTS_CMD_APP_DATA_LEN_INCELL;
+    cmd[1] = BYTE_OFF_16(len);
+    cmd[2] = BYTE_OFF_8(len);
+    cmd[3] = BYTE_OFF_0(len);
+    ret = fts_write(cmd, FTS_CMD_DATA_LEN_LEN);
+    if (ret < 0) {
+        FTS_ERROR("data len cmd write fail");
+        goto fw_reset;
+    }
+
+    cmd[0] = FTS_CMD_FLASH_MODE;
+    cmd[1] = FLASH_MODE_UPGRADE_VALUE;
+    ret = fts_write(cmd, 2);
+    if (ret < 0) {
+        FTS_ERROR("upgrade mode(09) cmd write fail");
+        goto fw_reset;
+    }
+
+    delay = FTS_DELAY_ERASE_PAGE_2K * (len / FTS_SIZE_PAGE_2K);
+    ret = fts_fwupg_erase(delay);
+    if (ret < 0) {
+        FTS_ERROR("erase cmd write fail");
+        goto fw_reset;
+    }
+
+    /* write app */
+    start_addr = upgrade_func_ft5652.appoff;
+    ecc_in_host = fts_flash_write_buf(start_addr, buf, len, 1);
+    if (ecc_in_host < 0 ) {
+        FTS_ERROR("flash write fail");
+        goto fw_reset;
+    }
+
+    /* ecc */
+    ecc_in_tp = fts_fwupg_ecc_cal(start_addr, len);
+    if (ecc_in_tp < 0 ) {
+        FTS_ERROR("ecc read fail");
+        goto fw_reset;
+    }
+
+    FTS_INFO("ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host);
+    if (ecc_in_tp != ecc_in_host) {
+        FTS_ERROR("ecc check fail");
+        goto fw_reset;
+    }
+
+    FTS_INFO("upgrade success, reset to normal boot");
+    ret = fts_fwupg_reset_in_boot();
+    if (ret < 0) {
+        FTS_ERROR("reset to normal boot fail");
+    }
+
+    msleep(200);
+    return 0;
+
+fw_reset:
+    FTS_INFO("upgrade fail, reset to normal boot");
+    ret = fts_fwupg_reset_in_boot();
+    if (ret < 0) {
+        FTS_ERROR("reset to normal boot fail");
+    }
+    return -EIO;
+}
+
+struct upgrade_func upgrade_func_ft5652 = {
+    .ctype = {0x88},
+    .fwveroff = 0x010E,
+    .fwcfgoff = 0x1F80,
+    .appoff = 0x0000,
+    .upgspec_version = UPGRADE_SPEC_V_1_0,
+    .pramboot_supported = false,
+    .hid_supported = true,
+    .upgrade = fts_ft5652_upgrade,
+};
diff --git a/ft3658/focaltech_gesture.c b/ft3658/focaltech_gesture.c
new file mode 100644
index 0000000..811f574
--- /dev/null
+++ b/ft3658/focaltech_gesture.c
@@ -0,0 +1,469 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_gestrue.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/******************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define KEY_GESTURE_U                           KEY_U
+#define KEY_GESTURE_UP                          KEY_UP
+#define KEY_GESTURE_DOWN                        KEY_DOWN
+#define KEY_GESTURE_LEFT                        KEY_LEFT
+#define KEY_GESTURE_RIGHT                       KEY_RIGHT
+#define KEY_GESTURE_O                           KEY_O
+#define KEY_GESTURE_E                           KEY_E
+#define KEY_GESTURE_M                           KEY_M
+#define KEY_GESTURE_L                           KEY_L
+#define KEY_GESTURE_W                           KEY_W
+#define KEY_GESTURE_S                           KEY_S
+#define KEY_GESTURE_V                           KEY_V
+#define KEY_GESTURE_C                           KEY_C
+#define KEY_GESTURE_Z                           KEY_Z
+
+#define GESTURE_LEFT                            0x20
+#define GESTURE_RIGHT                           0x21
+#define GESTURE_UP                              0x22
+#define GESTURE_DOWN                            0x23
+#define GESTURE_DOUBLECLICK                     0x24
+#define GESTURE_SINGLECLICK                     0x25
+#define GESTURE_FOD                             0x26
+#define GESTURE_FOD_UP                          0x27
+#define GESTURE_O                               0x30
+#define GESTURE_W                               0x31
+#define GESTURE_M                               0x32
+#define GESTURE_E                               0x33
+#define GESTURE_L                               0x44
+#define GESTURE_S                               0x46
+#define GESTURE_V                               0x54
+#define GESTURE_Z                               0x41
+#define GESTURE_C                               0x34
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+static ssize_t fts_gesture_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    mutex_lock(&ts_data->input_dev->mutex);
+    fts_read_reg(FTS_REG_GESTURE_EN, &val);
+    count = snprintf(buf, PAGE_SIZE, "Gesture Mode:%s\n",
+                     ts_data->gesture_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Reg(0xD0)=%d\n", val);
+    mutex_unlock(&ts_data->input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_gesture_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct fts_ts_data *ts_data = fts_data;
+
+    mutex_lock(&ts_data->input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_DEBUG("enable gesture");
+        ts_data->gesture_mode = ENABLE;
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_DEBUG("disable gesture");
+        ts_data->gesture_mode = DISABLE;
+    }
+    mutex_unlock(&ts_data->input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_gesture_buf_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    int i = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+    struct fts_gesture_st *gesture = &fts_data->fts_gesture_data;
+
+    mutex_lock(&input_dev->mutex);
+    count = snprintf(buf, PAGE_SIZE, "Gesture ID:%d\n", gesture->gesture_id);
+    count += snprintf(buf + count, PAGE_SIZE, "Gesture PointNum:%d\n",
+                      gesture->point_num);
+    count += snprintf(buf + count, PAGE_SIZE, "Gesture Points Buffer:\n");
+
+    /* save point data,max:6 */
+    for (i = 0; i < FTS_GESTURE_POINTS_MAX; i++) {
+        count += snprintf(buf + count, PAGE_SIZE, "%3d(%4d,%4d) ", i,
+                          gesture->coordinate_x[i], gesture->coordinate_y[i]);
+        if ((i + 1) % 4 == 0)
+            count += snprintf(buf + count, PAGE_SIZE, "\n");
+    }
+    count += snprintf(buf + count, PAGE_SIZE, "\n");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_gesture_buf_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+
+/* sysfs gesture node
+ *   read example: cat  fts_gesture_mode       ---read gesture mode
+ *   write example:echo 1 > fts_gesture_mode   --- write gesture mode to 1
+ *
+ */
+static DEVICE_ATTR(fts_gesture_mode, S_IRUGO | S_IWUSR, fts_gesture_show,
+                   fts_gesture_store);
+/*
+ *   read example: cat fts_gesture_buf        --- read gesture buf
+ */
+static DEVICE_ATTR(fts_gesture_buf, S_IRUGO | S_IWUSR,
+                   fts_gesture_buf_show, fts_gesture_buf_store);
+
+static struct attribute *fts_gesture_mode_attrs[] = {
+    &dev_attr_fts_gesture_mode.attr,
+    &dev_attr_fts_gesture_buf.attr,
+    NULL,
+};
+
+static struct attribute_group fts_gesture_group = {
+    .attrs = fts_gesture_mode_attrs,
+};
+
+static int fts_create_gesture_sysfs(struct device *dev)
+{
+    int ret = 0;
+
+    ret = sysfs_create_group(&dev->kobj, &fts_gesture_group);
+    if (ret) {
+        FTS_ERROR("gesture sys node create fail");
+        sysfs_remove_group(&dev->kobj, &fts_gesture_group);
+        return ret;
+    }
+
+    return 0;
+}
+
+static void fts_gesture_report(struct input_dev *input_dev, int gesture_id)
+{
+    int gesture;
+
+    FTS_DEBUG("gesture_id:0x%x", gesture_id);
+    switch (gesture_id) {
+    case GESTURE_LEFT:
+        gesture = KEY_GESTURE_LEFT;
+        break;
+    case GESTURE_RIGHT:
+        gesture = KEY_GESTURE_RIGHT;
+        break;
+    case GESTURE_UP:
+        gesture = KEY_GESTURE_UP;
+        break;
+    case GESTURE_DOWN:
+        gesture = KEY_GESTURE_DOWN;
+        break;
+    case GESTURE_DOUBLECLICK:
+        gesture = KEY_GESTURE_U;
+        break;
+    case GESTURE_O:
+        gesture = KEY_GESTURE_O;
+        break;
+    case GESTURE_W:
+        gesture = KEY_GESTURE_W;
+        break;
+    case GESTURE_M:
+        gesture = KEY_GESTURE_M;
+        break;
+    case GESTURE_E:
+        gesture = KEY_GESTURE_E;
+        break;
+    case GESTURE_L:
+        gesture = KEY_GESTURE_L;
+        break;
+    case GESTURE_S:
+        gesture = KEY_GESTURE_S;
+        break;
+    case GESTURE_V:
+        gesture = KEY_GESTURE_V;
+        break;
+    case GESTURE_Z:
+        gesture = KEY_GESTURE_Z;
+        break;
+    case GESTURE_C:
+        gesture = KEY_GESTURE_C;
+        break;
+    case GESTURE_SINGLECLICK:
+        gesture = KEY_GESTURE_UP;
+        break;
+    case GESTURE_FOD:
+        gesture = KEY_GESTURE_DOWN;
+        break;
+    case GESTURE_FOD_UP:
+        gesture = KEY_GESTURE_UP;
+        break;
+    default:
+        gesture = -1;
+        break;
+    }
+    /* report event key */
+    if (gesture != -1) {
+        FTS_DEBUG("Gesture Code=%d", gesture);
+        input_report_key(input_dev, gesture, 1);
+        input_sync(input_dev);
+        input_report_key(input_dev, gesture, 0);
+        input_sync(input_dev);
+    }
+}
+
+/*****************************************************************************
+* Name: fts_gesture_readdata
+* Brief: Read information about gesture: enable flag/gesture points..., if ges-
+*        ture enable, save gesture points' information, and report to OS.
+*        It will be called this function every intrrupt when FTS_GESTURE_EN = 1
+*
+*        gesture data length: 1(enable) + 1(reserve) + 2(header) + 6 * 4
+* Input: ts_data   - global struct data
+*        is_report - whether report gesture event or not.
+* Output:
+* Return: 0 - read gesture data successfully, the report data is gesture data
+*         1 - tp not in suspend/gesture not enable in TP FW
+*         -Exx - error
+*****************************************************************************/
+int fts_gesture_readdata(struct fts_ts_data *ts_data, bool is_report)
+{
+    int i = 0;
+    int index = 0;
+    struct input_dev *input_dev = ts_data->input_dev;
+    struct fts_gesture_st *gesture = &fts_data->fts_gesture_data;
+    u8 gesture_buf[FTS_GESTURE_TOTAL_DATA_SIZE] = { 0 };
+    u8 cmd[2] = { 0 };
+
+    cmd[0] = FTS_GESTURE_MAJOR_MINOR;
+
+    if (!ts_data->suspended) {
+        return -EINVAL;
+    }
+
+    fts_read(cmd, 1, gesture_buf, FTS_GESTURE_TOTAL_DATA_SIZE);
+
+    if (gesture_buf[0] != ENABLE) {
+        FTS_DEBUG("gesture not enable in fw, don't process gesture");
+        return -EINVAL;
+    }
+
+    /* init variable before read gesture point */
+    memset(gesture->coordinate_x, 0, FTS_GESTURE_POINTS_MAX * sizeof(u16));
+    memset(gesture->coordinate_y, 0, FTS_GESTURE_POINTS_MAX * sizeof(u16));
+    gesture->point_num = gesture_buf[2];
+    gesture->gesture_id = gesture_buf[3];
+    if (ts_data->log_level >= 1) {
+        FTS_DEBUG("gesture_id=0x%x, point_num=%d",
+            gesture->gesture_id, gesture->point_num);
+    }
+    /* save point data,max:6 */
+    for (i = 0; i < FTS_GESTURE_POINTS_MAX; i++) {
+        index = FTS_GESTURE_DATA_LEN * i;
+        gesture->major[i] =
+            (u16)(gesture_buf[6 + index] * ts_data->pdata->mm2px);
+        gesture->minor[i] =
+            (u16)(gesture_buf[7 + index] * ts_data->pdata->mm2px);
+        gesture->coordinate_x[i] = (u16)(((gesture_buf[8 + index] & 0x0F) << 8)
+                                         + gesture_buf[9 + index]);
+        gesture->coordinate_y[i] = (u16)(((gesture_buf[10 + index] & 0x0F) << 8)
+                                         + (gesture_buf[11 + index]& 0xFF));
+        gesture->orientation[i] =
+            (s16)(gesture_buf[13 + index] * FTS_ORIENTATION_SCALE);
+
+        if (ts_data->log_level >= 1) {
+            FTS_DEBUG("x=%d, y=%d, major=%d, minor=%d, orientation=%d\n",
+                gesture->coordinate_x[i], gesture->coordinate_y[i],
+                gesture->major[i], gesture->minor[i],
+                gesture->orientation[i]);
+        }
+    }
+
+    if (is_report) {
+        mutex_lock(&ts_data->report_mutex);
+        /* report gesture to OS. */
+        fts_gesture_report(input_dev, gesture->gesture_id);
+        mutex_unlock(&ts_data->report_mutex);
+    }
+
+    return 0;
+}
+
+void fts_gesture_recovery(struct fts_ts_data *ts_data)
+{
+    if (ts_data->gesture_mode && ts_data->suspended) {
+        FTS_DEBUG("gesture recovery...");
+        fts_write_reg(0xD1, 0xFF);
+        fts_write_reg(0xD2, 0xFF);
+        fts_write_reg(0xD5, 0xFF);
+        fts_write_reg(0xD6, 0xFF);
+        fts_write_reg(0xD7, 0xFF);
+        fts_write_reg(0xD8, 0xFF);
+        fts_write_reg(FTS_REG_GESTURE_EN, ENABLE);
+    }
+}
+
+int fts_gesture_suspend(struct fts_ts_data *ts_data)
+{
+    int i = 0;
+    u8 state = 0xFF;
+
+    FTS_FUNC_ENTER();
+    if (enable_irq_wake(ts_data->irq)) {
+        FTS_DEBUG("enable_irq_wake(irq:%d) fail", ts_data->irq);
+    }
+
+    for (i = 0; i < 5; i++) {
+        fts_write_reg(0xD1, 0xFF);
+        fts_write_reg(0xD2, 0xFF);
+        fts_write_reg(0xD5, 0xFF);
+        fts_write_reg(0xD6, 0xFF);
+        fts_write_reg(0xD7, 0xFF);
+        fts_write_reg(0xD8, 0xFF);
+        fts_write_reg(FTS_REG_GESTURE_EN, ENABLE);
+        msleep(1);
+        fts_read_reg(FTS_REG_GESTURE_EN, &state);
+        if (state == ENABLE)
+            break;
+    }
+
+    if (i >= 5)
+        FTS_ERROR("make IC enter into gesture(suspend) fail,state:%x", state);
+    else
+        FTS_INFO("Enter into gesture(suspend) successfully");
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_gesture_resume(struct fts_ts_data *ts_data)
+{
+    int i = 0;
+    u8 state = 0xFF;
+
+    FTS_FUNC_ENTER();
+    if (disable_irq_wake(ts_data->irq)) {
+        FTS_DEBUG("disable_irq_wake(irq:%d) fail", ts_data->irq);
+    }
+
+    for (i = 0; i < 5; i++) {
+        fts_write_reg(FTS_REG_GESTURE_EN, DISABLE);
+        msleep(1);
+        fts_read_reg(FTS_REG_GESTURE_EN, &state);
+        if (state == DISABLE)
+            break;
+    }
+
+    if (i >= 5)
+        FTS_ERROR("make IC exit gesture(resume) fail,state:%x", state);
+    else
+        FTS_INFO("resume from gesture successfully");
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_gesture_init(struct fts_ts_data *ts_data)
+{
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    input_set_capability(input_dev, EV_KEY, KEY_POWER);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_U);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_UP);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_DOWN);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_LEFT);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_RIGHT);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_O);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_E);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_M);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_L);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_W);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_S);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_V);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_Z);
+    input_set_capability(input_dev, EV_KEY, KEY_GESTURE_C);
+
+    __set_bit(KEY_GESTURE_RIGHT, input_dev->keybit);
+    __set_bit(KEY_GESTURE_LEFT, input_dev->keybit);
+    __set_bit(KEY_GESTURE_UP, input_dev->keybit);
+    __set_bit(KEY_GESTURE_DOWN, input_dev->keybit);
+    __set_bit(KEY_GESTURE_U, input_dev->keybit);
+    __set_bit(KEY_GESTURE_O, input_dev->keybit);
+    __set_bit(KEY_GESTURE_E, input_dev->keybit);
+    __set_bit(KEY_GESTURE_M, input_dev->keybit);
+    __set_bit(KEY_GESTURE_W, input_dev->keybit);
+    __set_bit(KEY_GESTURE_L, input_dev->keybit);
+    __set_bit(KEY_GESTURE_S, input_dev->keybit);
+    __set_bit(KEY_GESTURE_V, input_dev->keybit);
+    __set_bit(KEY_GESTURE_C, input_dev->keybit);
+    __set_bit(KEY_GESTURE_Z, input_dev->keybit);
+
+    fts_create_gesture_sysfs(ts_data->dev);
+
+    memset(&ts_data->fts_gesture_data, 0, sizeof(struct fts_gesture_st));
+    ts_data->gesture_mode = FTS_GESTURE_EN;
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_gesture_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_gesture_group);
+    FTS_FUNC_EXIT();
+    return 0;
+}
diff --git a/ft3658/focaltech_point_report_check.c b/ft3658/focaltech_point_report_check.c
new file mode 100644
index 0000000..1d1ee51
--- /dev/null
+++ b/ft3658/focaltech_point_report_check.c
@@ -0,0 +1,129 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_point_report_check.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-11-16
+*
+* Abstract: point report check function
+*
+* Version: v1.0
+*
+* Revision History:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+#if FTS_POINT_REPORT_CHECK_EN
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define POINT_REPORT_CHECK_WAIT_TIME                200    /* unit:ms */
+#define PRC_INTR_INTERVALS                          100    /* unit:ms */
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+/*****************************************************************************
+*  Name: fts_prc_func
+*  Brief: fts point report check work func, report whole up of points
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+static void fts_prc_func(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work,
+                                  struct fts_ts_data, prc_work.work);
+    unsigned long cur_jiffies = jiffies;
+    unsigned long intr_timeout = msecs_to_jiffies(PRC_INTR_INTERVALS);
+
+    intr_timeout += ts_data->intr_jiffies;
+    if (time_after(cur_jiffies, intr_timeout)) {
+        fts_release_all_finger();
+        ts_data->prc_mode = 0;
+        //FTS_DEBUG("interval:%lu", (cur_jiffies - ts_data->intr_jiffies) * 1000 / HZ);
+    } else {
+        queue_delayed_work(ts_data->ts_workqueue, &ts_data->prc_work,
+                           msecs_to_jiffies(POINT_REPORT_CHECK_WAIT_TIME));
+        ts_data->prc_mode = 1;
+    }
+}
+
+/*****************************************************************************
+*  Name: fts_prc_queue_work
+*  Brief: fts point report check queue work, call it when interrupt comes
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+void fts_prc_queue_work(struct fts_ts_data *ts_data)
+{
+    ts_data->intr_jiffies = jiffies;
+    if (!ts_data->prc_mode) {
+        queue_delayed_work(ts_data->ts_workqueue, &ts_data->prc_work,
+                           msecs_to_jiffies(POINT_REPORT_CHECK_WAIT_TIME));
+        ts_data->prc_mode = 1;
+    }
+}
+
+/*****************************************************************************
+*  Name: fts_point_report_check_init
+*  Brief:
+*  Input:
+*  Output:
+*  Return: < 0: Fail to create esd check queue
+*****************************************************************************/
+int fts_point_report_check_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    if (ts_data->ts_workqueue) {
+        INIT_DELAYED_WORK(&ts_data->prc_work, fts_prc_func);
+    } else {
+        FTS_ERROR("fts workqueue is NULL, can't run point report check function");
+        return -EINVAL;
+    }
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+/*****************************************************************************
+*  Name: fts_point_report_check_exit
+*  Brief:
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+int fts_point_report_check_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+#endif /* FTS_POINT_REPORT_CHECK_EN */
+
diff --git a/ft3658/focaltech_spi.c b/ft3658/focaltech_spi.c
new file mode 100644
index 0000000..1ba1780
--- /dev/null
+++ b/ft3658/focaltech_spi.c
@@ -0,0 +1,503 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/************************************************************************
+*
+* File Name: focaltech_spi.c
+*
+*    Author: FocalTech Driver Team
+*
+*   Created: 2019-03-21
+*
+*  Abstract: new spi protocol communication with TP
+*
+*   Version: v1.0
+*
+* Revision History:
+*
+************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define SPI_RETRY_NUMBER            3
+#define CS_HIGH_DELAY               150 /* unit: us */
+
+/* The touch data size is 1451 bytes = (cap header(91 bytes) + MS(1088 bytes) +
+ * water_SS(136 bytes) + normal-SS(136 bytes)), so set SPI_BUF_LENGTH larger
+ * than 1451 to prevent SPI buff from being allocated and freed on every touch
+ * data transfer.
+ */
+#define SPI_BUF_LENGTH              1536 /* ALIGN(1451, 256) */
+
+#define DATA_CRC_EN                 0x20
+#define WRITE_CMD                   0x00
+#define READ_CMD                    (0x80 | DATA_CRC_EN)
+
+#define SPI_DUMMY_BYTE              3
+#define SPI_HEADER_LENGTH           6   /*CRC*/
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+/* spi interface */
+static int fts_spi_transfer(u8 *tx_buf, u8 *rx_buf, u32 len)
+{
+    int ret = 0;
+    struct spi_device *spi = fts_data->spi;
+    struct spi_message msg;
+    struct spi_transfer xfer = {
+        .tx_buf = tx_buf,
+        .rx_buf = rx_buf,
+        .len    = len,
+        .bits_per_word = len >= 64 ? 32 : 8,
+    };
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
+    if (fts_data->tbn_owner != TBN_AP) {
+        FTS_ERROR("SPI bus is not available.");
+        return -EACCES;
+    }
+#endif
+    spi_message_init(&msg);
+    spi_message_add_tail(&xfer, &msg);
+
+    ret = spi_sync(spi, &msg);
+    if (ret) {
+        FTS_ERROR("spi_sync fail,ret:%d", ret);
+        return ret;
+    }
+
+    return ret;
+}
+
+static void fts_spi_buf_show(u8 *data, int datalen)
+{
+    int i = 0;
+    int last_print_index = 0;
+    int count = 0;
+    int size = 0;
+    int max_cnt = 256;
+    int tmpbuf_size = 0;
+    char *tmpbuf = NULL;
+
+    if (!data || (datalen <= 0)) {
+        FTS_ERROR("data/datalen is invalid");
+        return;
+    }
+
+    size = (datalen > max_cnt) ? max_cnt : datalen;
+    tmpbuf_size = size * 3;
+    tmpbuf = kzalloc(tmpbuf_size, GFP_KERNEL);
+    if (!tmpbuf) {
+        FTS_ERROR("tmpbuf zalloc fail");
+        return;
+    }
+
+    for (i = 0; i < size; i++) {
+        count += scnprintf(tmpbuf + count, tmpbuf_size - count, "%02X ", data[i]);
+        if (i % 16 == 15) {
+          FTS_DEBUG("%03d, %s", last_print_index, tmpbuf + last_print_index);
+          last_print_index = count;
+        }
+    }
+    if (last_print_index != count)
+        FTS_DEBUG("%03d, %s", last_print_index, tmpbuf + last_print_index);
+
+    if (tmpbuf) {
+        kfree(tmpbuf);
+        tmpbuf = NULL;
+    }
+}
+
+static void crckermit(u8 *data, u32 len, u16 *crc_out)
+{
+    u32 i = 0;
+    u32 j = 0;
+    u16 crc = 0xFFFF;
+
+    for ( i = 0; i < len; i++) {
+        crc ^= data[i];
+        for (j = 0; j < 8; j++) {
+            if (crc & 0x01)
+                crc = (crc >> 1) ^ 0x8408;
+            else
+                crc = (crc >> 1);
+        }
+    }
+
+    *crc_out = crc;
+}
+
+static int rdata_check(u8 *rdata, u32 rlen)
+{
+    u16 crc_calc = 0;
+    u16 crc_read = 0;
+
+    crckermit(rdata, rlen - 2, &crc_calc);
+    crc_read = (u16)(rdata[rlen - 1] << 8) + rdata[rlen - 2];
+    if (crc_calc != crc_read) {
+        FTS_ERROR("crc_calc = 0x%X, crc_read=0x%X",crc_calc, crc_read);
+        fts_spi_buf_show(rdata, rlen);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+int fts_write(u8 *writebuf, u32 writelen)
+{
+    int ret = 0;
+    int i = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 *txbuf = NULL;
+    u8 *rxbuf = NULL;
+    u32 txlen = 0;
+    u32 txlen_need = writelen + SPI_HEADER_LENGTH + ts_data->dummy_byte;
+    u32 datalen = writelen - 1;
+
+    if (!writebuf || !writelen) {
+        FTS_ERROR("writebuf/len is invalid");
+        return -EINVAL;
+    }
+    /* 4 bytes alignment for DMA mode. */
+    if (txlen_need > 64) {
+        txlen_need = ALIGN(txlen_need, 4);
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+    if (txlen_need > SPI_BUF_LENGTH) {
+        txbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == txbuf) {
+            FTS_ERROR("txbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_write;
+        }
+
+        rxbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == rxbuf) {
+            FTS_ERROR("rxbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_write;
+        }
+    } else {
+        txbuf = ts_data->bus_tx_buf;
+        rxbuf = ts_data->bus_rx_buf;
+        memset(txbuf, 0x0, SPI_BUF_LENGTH);
+        memset(rxbuf, 0x0, SPI_BUF_LENGTH);
+    }
+
+    txbuf[txlen++] = writebuf[0];
+    txbuf[txlen++] = WRITE_CMD;
+    txbuf[txlen++] = (datalen >> 8) & 0xFF;
+    txbuf[txlen++] = datalen & 0xFF;
+    if (datalen > 0) {
+        txlen = txlen + SPI_DUMMY_BYTE;
+        memcpy(&txbuf[txlen], &writebuf[1], datalen);
+        txlen = txlen + datalen;
+    }
+    /* 4 bytes alignment for DMA mode. */
+    if (txlen > 64) {
+        txlen = ALIGN(txlen, 4);
+    }
+
+    for (i = 0; i < SPI_RETRY_NUMBER; i++) {
+        ret = fts_spi_transfer(txbuf, rxbuf, txlen);
+        if ((0 == ret) && ((rxbuf[3] & 0xA0) == 0)) {
+            break;
+        } else {
+            FTS_DEBUG("data write(addr:%x),status:%x,retry:%d,ret:%d",
+                      writebuf[0], rxbuf[3], i, ret);
+            if (ret == -EACCES)
+                break;
+            ret = -EIO;
+            udelay(CS_HIGH_DELAY);
+        }
+    }
+    if (ret < 0) {
+        FTS_ERROR("data write(addr:%x) fail,status:%x,ret:%d",
+                  writebuf[0], rxbuf[3], ret);
+    }
+
+err_write:
+    if (txlen_need > SPI_BUF_LENGTH) {
+        if (txbuf) {
+            kfree(txbuf);
+            txbuf = NULL;
+        }
+
+        if (rxbuf) {
+            kfree(rxbuf);
+            rxbuf = NULL;
+        }
+    }
+
+    udelay(CS_HIGH_DELAY);
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+int fts_write_reg(u8 addr, u8 value)
+{
+    u8 writebuf[2] = { 0 };
+
+    writebuf[0] = addr;
+    writebuf[1] = value;
+    return fts_write(writebuf, 2);
+}
+
+int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen)
+{
+    int ret = 0;
+    int i = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 *txbuf = NULL;
+    u8 *rxbuf = NULL;
+    u32 txlen = 0;
+    u32 aligned_txlen = 0;
+    u32 aligned_datalen = 0;
+    u32 txlen_need = datalen + SPI_HEADER_LENGTH + ts_data->dummy_byte;
+    u8 ctrl = READ_CMD;
+    u32 dp = 0;
+
+    if (!cmd || !cmdlen || !data || !datalen) {
+        FTS_ERROR("cmd/cmdlen/data/datalen is invalid");
+        return -EINVAL;
+    }
+    /* 4 bytes alignment for DMA mode. */
+    if (txlen_need > 64) {
+        txlen_need = ALIGN(txlen_need, 4);
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+    if (txlen_need > SPI_BUF_LENGTH) {
+        txbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == txbuf) {
+            FTS_ERROR("txbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_read;
+        }
+
+        rxbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == rxbuf) {
+            FTS_ERROR("rxbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_read;
+        }
+    } else {
+        txbuf = ts_data->bus_tx_buf;
+        rxbuf = ts_data->bus_rx_buf;
+        memset(txbuf, 0x0, SPI_BUF_LENGTH);
+        memset(rxbuf, 0x0, SPI_BUF_LENGTH);
+    }
+
+    txbuf[txlen++] = cmd[0];
+    txbuf[txlen++] = ctrl;
+    txbuf[txlen++] = (datalen >> 8) & 0xFF;
+    txbuf[txlen++] = datalen & 0xFF;
+    dp = txlen + SPI_DUMMY_BYTE;
+    txlen = dp + datalen;
+    if (ctrl & DATA_CRC_EN) {
+        txlen = txlen + 2;
+    }
+    aligned_txlen = txlen;
+    aligned_datalen = datalen;
+    /* 4 bytes alignment for DMA mode. */
+    if (aligned_txlen > 64) {
+        aligned_txlen = ALIGN(aligned_txlen, 4);
+        /* Calculate new datalen for CRC checking code. */
+        aligned_datalen += aligned_txlen - txlen;
+        txbuf[2] = (aligned_datalen >> 8) & 0xFF;
+        txbuf[3] = aligned_datalen & 0xFF;
+    }
+
+    for (i = 0; i < SPI_RETRY_NUMBER; i++) {
+        ret = fts_spi_transfer(txbuf, rxbuf, aligned_txlen);
+        if ((0 == ret) && ((rxbuf[3] & 0xA0) == 0)) {
+            memcpy(data, &rxbuf[dp], datalen);
+            /* crc check */
+            if (ctrl & DATA_CRC_EN) {
+                ret = rdata_check(&rxbuf[dp], aligned_txlen - dp);
+                if (ret < 0) {
+                    FTS_DEBUG("data read(addr:%x) crc abnormal,retry:%d",
+                              cmd[0], i);
+                    udelay(CS_HIGH_DELAY);
+                    continue;
+                }
+            }
+            break;
+        } else {
+            FTS_DEBUG("data read(addr:%x) status:%x,retry:%d,ret:%d",
+                      cmd[0], rxbuf[3], i, ret);
+            if (ret == -EACCES)
+                break;
+            ret = -EIO;
+            udelay(CS_HIGH_DELAY);
+        }
+    }
+
+    if (ret < 0) {
+        FTS_ERROR("data read(addr:%x) %s,status:%x,ret:%d", cmd[0],
+                  (i >= SPI_RETRY_NUMBER) ? "crc abnormal" : "fail",
+                  rxbuf[3], ret);
+    }
+
+err_read:
+    if (txlen_need > SPI_BUF_LENGTH) {
+        if (txbuf) {
+            kfree(txbuf);
+            txbuf = NULL;
+        }
+
+        if (rxbuf) {
+            kfree(rxbuf);
+            rxbuf = NULL;
+        }
+    }
+
+    udelay(CS_HIGH_DELAY);
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+int fts_read_reg(u8 addr, u8 *value)
+{
+    return fts_read(&addr, 1, value, 1);
+}
+
+
+int fts_spi_transfer_direct(u8 *writebuf, u32 writelen, u8 *readbuf, u32 readlen)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 *txbuf = NULL;
+    u8 *rxbuf = NULL;
+    bool read_cmd = (readbuf && readlen) ? 1 : 0;
+    u32 txlen = (read_cmd) ? (writelen + readlen) : writelen;
+
+    if (!writebuf || !writelen) {
+        FTS_ERROR("writebuf/len is invalid");
+        return -EINVAL;
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+    if (txlen > SPI_BUF_LENGTH) {
+        txbuf = kzalloc(txlen, GFP_KERNEL);
+        if (NULL == txbuf) {
+            FTS_ERROR("txbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_spi_dir;
+        }
+
+        rxbuf = kzalloc(txlen, GFP_KERNEL);
+        if (NULL == rxbuf) {
+            FTS_ERROR("rxbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_spi_dir;
+        }
+    } else {
+        txbuf = ts_data->bus_tx_buf;
+        rxbuf = ts_data->bus_rx_buf;
+        memset(txbuf, 0x0, SPI_BUF_LENGTH);
+        memset(rxbuf, 0x0, SPI_BUF_LENGTH);
+    }
+
+    memcpy(txbuf, writebuf, writelen);
+    ret = fts_spi_transfer(txbuf, rxbuf, txlen);
+    if (ret < 0) {
+        FTS_ERROR("data read(addr:%x) fail,status:%x,ret:%d", txbuf[0], rxbuf[3], ret);
+        goto err_spi_dir;
+    }
+
+    if (read_cmd) {
+        memcpy(readbuf, rxbuf, txlen);
+    }
+
+    ret = 0;
+err_spi_dir:
+    if (txlen > SPI_BUF_LENGTH) {
+        if (txbuf) {
+            kfree(txbuf);
+            txbuf = NULL;
+        }
+
+        if (rxbuf) {
+            kfree(rxbuf);
+            rxbuf = NULL;
+        }
+    }
+
+    udelay(CS_HIGH_DELAY);
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+int fts_bus_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    ts_data->bus_tx_buf = kzalloc(SPI_BUF_LENGTH, GFP_KERNEL);
+    if (NULL == ts_data->bus_tx_buf) {
+        FTS_ERROR("failed to allocate memory for bus_tx_buf");
+        return -ENOMEM;
+    }
+
+    ts_data->bus_rx_buf = kzalloc(SPI_BUF_LENGTH, GFP_KERNEL);
+    if (NULL == ts_data->bus_rx_buf) {
+        FTS_ERROR("failed to allocate memory for bus_rx_buf");
+        return -ENOMEM;
+    }
+
+    ts_data->dummy_byte = SPI_DUMMY_BYTE;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_bus_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    if (ts_data && ts_data->bus_tx_buf) {
+        kfree(ts_data->bus_tx_buf);
+        ts_data->bus_tx_buf = NULL;
+    }
+
+    if (ts_data && ts_data->bus_rx_buf) {
+        kfree(ts_data->bus_rx_buf);
+        ts_data->bus_rx_buf = NULL;
+    }
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
diff --git a/ft3658/focaltech_test/Makefile b/ft3658/focaltech_test/Makefile
new file mode 100644
index 0000000..062610e
--- /dev/null
+++ b/ft3658/focaltech_test/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_test.o
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_test_ini.o
+obj-$(CONFIG_TOUCHSCREEN_FTS) += supported_ic/
diff --git a/ft3658/focaltech_test/focaltech_test.c b/ft3658/focaltech_test/focaltech_test.c
new file mode 100644
index 0000000..92d8911
--- /dev/null
+++ b/ft3658/focaltech_test/focaltech_test.c
@@ -0,0 +1,3773 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/************************************************************************
+*
+* File Name: focaltech_test.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Modify:
+*
+* Abstract: create char device and proc node for  the comm between APK and TP
+*
+************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_test.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+struct fts_test *fts_ftest;
+
+struct test_funcs *test_func_list[] = {
+    &test_func_ft5652,
+};
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+void sys_delay(int ms)
+{
+    msleep(ms);
+}
+
+int fts_abs(int value)
+{
+    if (value < 0)
+        value = 0 - value;
+
+    return value;
+}
+
+void *fts_malloc(size_t size)
+{
+    return kzalloc(size, GFP_KERNEL);
+}
+
+void fts_free_proc(void *p)
+{
+    return kfree(p);
+}
+
+void print_buffer(int *buffer, int length, int line_num)
+{
+    int i = 0;
+    int j = 0;
+    int tmpline = 0;
+    char *tmpbuf = NULL;
+    int tmplen = 0;
+    int cnt = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata && tdata->ts_data && (tdata->ts_data->log_level < 3)) {
+        return;
+    }
+
+    if ((NULL == buffer) || (length <= 0)) {
+        FTS_TEST_DBG("buffer/length(%d) fail", length);
+        return;
+    }
+
+    tmpline = line_num ? line_num : length;
+    tmplen = tmpline * 6 + 128;
+    tmpbuf = kzalloc(tmplen, GFP_KERNEL);
+
+    for (i = 0; i < length; i = i + tmpline) {
+        cnt = 0;
+        for (j = 0; j < tmpline; j++) {
+            cnt += snprintf(tmpbuf + cnt, tmplen - cnt, "%5d ", buffer[i + j]);
+            if ((cnt >= tmplen) || ((i + j + 1) >= length))
+                break;
+        }
+        FTS_TEST_DBG("%s", tmpbuf);
+    }
+
+    if (tmpbuf) {
+        kfree(tmpbuf);
+        tmpbuf = NULL;
+    }
+}
+
+/********************************************************************
+ * test read/write interface
+ *******************************************************************/
+static int fts_test_bus_read(
+    u8 *cmd, int cmdlen, u8 *data, int datalen)
+{
+    int ret = 0;
+
+    ret = fts_read(cmd, cmdlen, data, datalen);
+    if (ret < 0)
+        return ret;
+    else
+        return 0;
+}
+
+static int fts_test_bus_write(u8 *writebuf, int writelen)
+{
+    int ret = 0;
+
+    ret = fts_write(writebuf, writelen);
+    if (ret < 0)
+        return ret;
+    else
+        return 0;
+}
+
+int fts_test_read_reg(u8 addr, u8 *val)
+{
+    return fts_test_bus_read(&addr, 1, val, 1);
+}
+
+int fts_test_write_reg(u8 addr, u8 val)
+{
+    int ret;
+    u8 cmd[2] = {0};
+
+    cmd[0] = addr;
+    cmd[1] = val;
+    ret = fts_test_bus_write(cmd, 2);
+
+    return ret;
+}
+
+int fts_test_read(u8 addr, u8 *readbuf, int readlen)
+{
+    int ret = 0;
+    int i = 0;
+    int packet_length = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    int byte_num = readlen;
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+
+    if (byte_num < BYTES_PER_TIME) {
+        packet_length = byte_num;
+    } else {
+        packet_length = BYTES_PER_TIME;
+    }
+    /* FTS_TEST_DBG("packet num:%d, remainder:%d", packet_num, packet_remainder); */
+
+    ret = fts_test_bus_read(&addr, 1, &readbuf[offset], packet_length);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read buffer fail");
+        return ret;
+    }
+    for (i = 1; i < packet_num; i++) {
+        offset += packet_length;
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            packet_length = packet_remainder;
+        }
+
+        ret = fts_test_bus_read(&addr, 1, &readbuf[offset],
+                                packet_length);
+
+        if (ret < 0) {
+            FTS_TEST_ERROR("read buffer fail");
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+int fts_test_write(u8 addr, u8 *writebuf, int writelen)
+{
+    int ret = 0;
+    int i = 0;
+    u8 *data = NULL;
+    int packet_length = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    int byte_num = writelen;
+
+    data = fts_malloc(BYTES_PER_TIME + 1);
+    if (!data) {
+        FTS_TEST_ERROR("malloc memory for bus write data fail");
+        return -ENOMEM;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+
+    if (byte_num < BYTES_PER_TIME) {
+        packet_length = byte_num;
+    } else {
+        packet_length = BYTES_PER_TIME;
+    }
+    /* FTS_TEST_DBG("packet num:%d, remainder:%d", packet_num, packet_remainder); */
+
+    data[0] = addr;
+    for (i = 0; i < packet_num; i++) {
+        if (i != 0) {
+            data[0] = addr + 1;
+        }
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            packet_length = packet_remainder;
+        }
+        memcpy(&data[1], &writebuf[offset], packet_length);
+
+        ret = fts_test_bus_write(data, packet_length + 1);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write buffer fail");
+            fts_free(data);
+            return ret;
+        }
+
+        offset += packet_length;
+    }
+
+    fts_free(data);
+    return 0;
+}
+
+/********************************************************************
+ * test global function enter work/factory mode
+ *******************************************************************/
+int enter_work_mode(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+    u8 mode = 0;
+    int i = 0;
+    int j = 0;
+
+    FTS_TEST_FUNC_ENTER();
+
+    ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+    if ((ret >= 0) && (0x00 == mode))
+        return 0;
+
+    for (i = 0; i < ENTER_WORK_FACTORY_RETRIES; i++) {
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0x00);
+        if (ret >= 0) {
+            sys_delay(FACTORY_TEST_DELAY);
+            for (j = 0; j < 20; j++) {
+                ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+                if ((ret >= 0) && (mode == FTS_REG_WORKMODE_WORK_VALUE)) {
+                    FTS_TEST_INFO("enter work mode success");
+                    ts_data->work_mode = mode;
+                    return 0;
+                } else {
+                    sys_delay(FACTORY_TEST_DELAY);
+                }
+            }
+        }
+
+        sys_delay(50);
+    }
+
+    if (i >= ENTER_WORK_FACTORY_RETRIES) {
+        FTS_TEST_ERROR("Enter work mode fail");
+        return -EIO;
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+int enter_factory_mode(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+    u8 mode = 0;
+    int i = 0;
+    int j = 0;
+
+    ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+    if ((ret >= 0) && (0x40 == mode))
+        return 0;
+
+    for (i = 0; i < ENTER_WORK_FACTORY_RETRIES; i++) {
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0x40);
+        if (ret >= 0) {
+            sys_delay(FACTORY_TEST_DELAY);
+            for (j = 0; j < 20; j++) {
+                ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+                if ((ret >= 0) && (mode == FTS_REG_WORKMODE_FACTORY_VALUE)) {
+                    FTS_TEST_INFO("enter factory mode success");
+                    sys_delay(200);
+                    ts_data->work_mode = mode;
+                    return 0;
+                } else {
+                    sys_delay(FACTORY_TEST_DELAY);
+                }
+            }
+        }
+
+        sys_delay(50);
+    }
+
+    if (i >= ENTER_WORK_FACTORY_RETRIES) {
+        FTS_TEST_ERROR("Enter factory mode fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * read_mass_data - read rawdata/short test data
+ * addr - register addr which read data from
+ * byte_num - read data length, unit:byte
+ * buf - save data
+ *
+ * return 0 if read data succuss, otherwise return error code
+ */
+int read_mass_data(u8 addr, int byte_num, int *buf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 *data = NULL;
+
+    data = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == data) {
+        FTS_TEST_SAVE_ERR("mass data buffer malloc fail\n");
+        return -ENOMEM;
+    }
+
+    /* read rawdata buffer */
+    FTS_TEST_INFO("mass data len:%d", byte_num);
+    ret = fts_test_read(addr, data, byte_num);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read mass data fail\n");
+        goto read_massdata_err;
+    }
+
+    for (i = 0; i < byte_num; i = i + 2) {
+        buf[i >> 1] = (int)(short)((data[i] << 8) + data[i + 1]);
+    }
+
+    ret = 0;
+read_massdata_err:
+    fts_free(data);
+    return ret;
+}
+
+int read_mass_data_u16(u8 addr, int byte_num, int *buf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 *data = NULL;
+
+    data = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == data) {
+        FTS_TEST_SAVE_ERR("mass data buffer malloc fail\n");
+        return -ENOMEM;
+    }
+
+    /* read rawdata buffer */
+    FTS_TEST_INFO("mass data len:%d", byte_num);
+    ret = fts_test_read(addr, data, byte_num);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read mass data fail\n");
+        goto read_massdata_err;
+    }
+
+    for (i = 0; i < byte_num; i = i + 2) {
+        buf[i >> 1] = (int)(u16)((data[i] << 8) + data[i + 1]);
+    }
+
+    ret = 0;
+read_massdata_err:
+    fts_free(data);
+    return ret;
+}
+
+int short_get_adcdata_incell(u8 retval, u8 ch_num, int byte_num, int *adc_buf)
+{
+    int ret = 0;
+    int times = 0;
+    u8 short_state = 0;
+
+    FTS_TEST_FUNC_ENTER();
+
+    /* Start ADC sample */
+    ret = fts_test_write_reg(FACTORY_REG_SHORT_TEST_EN, 0x01);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("start short test fail\n");
+        goto adc_err;
+    }
+
+    sys_delay(ch_num * FACTORY_TEST_DELAY);
+    for (times = 0; times < FACTORY_TEST_RETRY; times++) {
+        ret = fts_test_read_reg(FACTORY_REG_SHORT_TEST_STATE, &short_state);
+        if ((ret >= 0) && (retval == short_state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d",
+                         FACTORY_REG_SHORT_TEST_STATE, short_state, times);
+
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+    }
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("short test timeout, ADC data not OK\n");
+        ret = -EIO;
+        goto adc_err;
+    }
+
+    ret = read_mass_data(FACTORY_REG_SHORT_ADDR, byte_num, adc_buf);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("get short(adc) data fail\n");
+    }
+
+adc_err:
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+/*
+ * wait_state_update - wait fw status update
+ */
+int wait_state_update(u8 retval)
+{
+    int ret = 0;
+    int times = 0;
+    u8 addr = 0;
+    u8 state = 0xFF;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    if (IC_HW_INCELL == tdata->func->hwtype) {
+        addr = FACTORY_REG_PARAM_UPDATE_STATE;
+    } else {
+        addr = FACTORY_REG_PARAM_UPDATE_STATE_TOUCH;
+    }
+
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+        /* Wait register status update */
+        state = 0xFF;
+        ret = fts_test_read_reg(addr, &state);
+        if ((ret >= 0) && (retval == state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", addr, state, times);
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("Wait State Update fail,reg%x=%x\n", addr, state);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * start_scan - start to scan a frame
+ */
+int start_scan(void)
+{
+    int ret = 0;
+    u8 addr = 0;
+    u8 val = 0;
+    u8 finish_val = 0;
+    int times = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    if (SCAN_SC == tdata->func->startscan_mode) {
+        /* sc ic */
+        addr = FACTORY_REG_SCAN_ADDR2;
+        val = 0x01;
+        finish_val = 0x00;
+    } else {
+        addr = DIVIDE_MODE_ADDR;
+        val = 0xC0;
+        finish_val = 0x40;
+    }
+
+    /* write register to start scan */
+    ret = fts_test_write_reg(addr, val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write start scan mode fail\n");
+        return ret;
+    }
+
+    /* Wait for the scan to complete */
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+
+        ret = fts_test_read_reg(addr, &val);
+        if ((ret >= 0) && (val == finish_val)) {
+            break;
+        } else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", addr, val, times);
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("scan timeout\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int read_rawdata(
+    struct fts_test *tdata,
+    u8 off_addr,
+    u8 off_val,
+    u8 rawdata_addr,
+    int byte_num,
+    int *data)
+{
+    int ret = 0;
+
+    /* set line addr or rawdata start addr */
+    ret = fts_test_write_reg(off_addr, off_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write line/start addr fail\n");
+        return ret;
+    }
+
+    if (tdata->func->raw_u16)
+        ret = read_mass_data_u16(rawdata_addr, byte_num, data);
+    else
+        ret = read_mass_data(rawdata_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+int get_rawdata(int *data)
+{
+    int ret = 0;
+    u8 val = 0;
+    u8 addr = 0;
+    u8 rawdata_addr = 0;
+    int byte_num = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    /* enter factory mode */
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* start scanning */
+    ret = start_scan();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scan fail\n");
+        return ret;
+    }
+
+    /* read rawdata */
+    if (IC_HW_INCELL == tdata->func->hwtype) {
+        val = 0xAD;
+        addr = FACTORY_REG_LINE_ADDR;
+        rawdata_addr = FACTORY_REG_RAWDATA_ADDR;
+    } else if (IC_HW_MC_SC == tdata->func->hwtype) {
+        val = 0xAA;
+        addr = FACTORY_REG_LINE_ADDR;
+        rawdata_addr = FACTORY_REG_RAWDATA_ADDR_MC_SC;
+    } else {
+        val = 0x0;
+        addr = FACTORY_REG_RAWDATA_SADDR_SC;
+        rawdata_addr = FACTORY_REG_RAWDATA_ADDR_SC;
+    }
+
+    byte_num = tdata->node.node_num * 2;
+    ret = read_rawdata(tdata, addr, val, rawdata_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+/*
+ * chip_clb - auto clb
+ */
+int chip_clb(void)
+{
+    int ret = 0;
+    u8 val = 0;
+    int times = 0;
+
+    /* start clb */
+    ret = fts_test_write_reg(FACTORY_REG_CLB, 0x04);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("write start clb fail\n");
+        return ret;
+    }
+
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+        ret = fts_test_read_reg(FACTORY_REG_CLB, &val);
+        if ((0 == ret) && (0x02 == val)) {
+            /* clb ok */
+            break;
+        } else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", FACTORY_REG_CLB, val, times);
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("chip clb timeout\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * get_cb_incell - get cb data for incell IC
+ */
+int get_cb_incell(u16 saddr, int byte_num, int *cb_buf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cb_addr = 0;
+    u8 addr_h = 0;
+    u8 addr_l = 0;
+    int read_num = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    int addr = 0;
+    u8 *data = NULL;
+
+    data = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == data) {
+        FTS_TEST_SAVE_ERR("cb buffer malloc fail\n");
+        return -ENOMEM;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+    read_num = BYTES_PER_TIME;
+
+    FTS_TEST_INFO("cb packet:%d,remainder:%d", packet_num, packet_remainder);
+    cb_addr = FACTORY_REG_CB_ADDR;
+    for (i = 0; i < packet_num; i++) {
+        offset = read_num * i;
+        addr = saddr + offset;
+        addr_h = (addr >> 8) & 0xFF;
+        addr_l = addr & 0xFF;
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            read_num = packet_remainder;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_CB_ADDR_H, addr_h);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("write cb addr high fail\n");
+            goto TEST_CB_ERR;
+        }
+        ret = fts_test_write_reg(FACTORY_REG_CB_ADDR_L, addr_l);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("write cb addr low fail\n");
+            goto TEST_CB_ERR;
+        }
+
+        ret = fts_test_read(cb_addr, data + offset, read_num);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read cb fail\n");
+            goto TEST_CB_ERR;
+        }
+    }
+
+    for (i = 0; i < byte_num; i++) {
+        cb_buf[i] = data[i];
+    }
+
+TEST_CB_ERR:
+    fts_free(data);
+    return ret;
+}
+
+int get_cb_sc(int byte_num, int *cb_buf, enum byte_mode mode)
+{
+    int ret = 0;
+    int i = 0;
+    int read_num = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    u8 cb_addr = 0;
+    u8 off_addr = 0;
+    u8 off_h_addr = 0;
+    struct fts_test *tdata = fts_ftest;
+    u8 *cb = NULL;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    cb = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == cb) {
+        FTS_TEST_SAVE_ERR("malloc memory for cb buffer fail\n");
+        return -ENOMEM;
+    }
+
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        cb_addr = FACTORY_REG_MC_SC_CB_ADDR;
+        off_addr = FACTORY_REG_MC_SC_CB_ADDR_OFF;
+        off_h_addr = FACTORY_REG_MC_SC_CB_H_ADDR_OFF;
+    } else if (IC_HW_SC == tdata->func->hwtype) {
+        cb_addr = FACTORY_REG_SC_CB_ADDR;
+        off_addr = FACTORY_REG_SC_CB_ADDR_OFF;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+    read_num = BYTES_PER_TIME;
+    offset = 0;
+
+    FTS_TEST_INFO("cb packet:%d,remainder:%d", packet_num, packet_remainder);
+    for (i = 0; i < packet_num; i++) {
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            read_num = packet_remainder;
+        }
+
+        ret = fts_test_write_reg(off_addr, offset);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("write cb addr offset fail\n");
+            goto cb_err;
+        }
+
+        if (tdata->func->cb_high_support) {
+            ret = fts_test_write_reg(off_h_addr, offset >> 8);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("write cb_h addr offset fail\n");
+                goto cb_err;
+            }
+        }
+
+        ret = fts_test_read(cb_addr, cb + offset, read_num);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read cb fail\n");
+            goto cb_err;
+        }
+
+        offset += read_num;
+    }
+
+    if (DATA_ONE_BYTE == mode) {
+        for (i = 0; i < byte_num; i++) {
+            cb_buf[i] = cb[i];
+        }
+    } else if (DATA_TWO_BYTE == mode) {
+        for (i = 0; i < byte_num; i = i + 2) {
+            cb_buf[i >> 1] = (int)(((int)(cb[i]) << 8) + cb[i + 1]);
+        }
+    }
+
+    ret = 0;
+cb_err:
+    fts_free(cb);
+    return ret;
+}
+
+bool compare_data(int *data, int min, int max, int min_vk, int max_vk, bool key)
+{
+    int i = 0;
+    bool result = true;
+    struct fts_test *tdata = fts_ftest;
+    int rx = tdata->node.rx_num;
+    int node_va = tdata->node.node_num - tdata->node.key_num;
+
+    if (!data || !tdata->node_valid) {
+        FTS_TEST_SAVE_ERR("data/node_valid is null\n");
+        return false;
+    }
+
+    for (i = 0; i < node_va; i++) {
+        if (0 == tdata->node_valid[i])
+            continue;
+
+        if ((data[i] < min) || (data[i] > max)) {
+            FTS_TEST_SAVE_ERR("test fail,node(%4d,%4d)=%5d,range=(%5d,%5d)\n",
+                              i / rx + 1, i % rx + 1, data[i], min, max);
+            result = false;
+        }
+    }
+
+    if (key) {
+        for (i = node_va; i < tdata->node.node_num; i++) {
+            if (0 == tdata->node_valid[i])
+                continue;
+
+            if ((data[i] < min_vk) || (data[i] > max_vk)) {
+                FTS_TEST_SAVE_ERR("test fail,node(%4d,%4d)=%5d,range=(%5d,%5d)\n",
+                                  i / rx + 1, i % rx + 1,
+                                  data[i], min_vk, max_vk);
+                result = false;
+            }
+        }
+    }
+
+    return result;
+}
+
+bool compare_array(int *data, int *min, int *max, bool key)
+{
+    int i = 0;
+    bool result = true;
+    struct fts_test *tdata = fts_ftest;
+    int rx = tdata->node.rx_num;
+    int node_num = tdata->node.node_num;
+
+    if (!data || !min || !max || !tdata->node_valid) {
+        FTS_TEST_SAVE_ERR("data/min/max/node_valid is null\n");
+        return false;
+    }
+
+    if (!key) {
+        node_num -= tdata->node.key_num;
+    }
+    for (i = 0; i < node_num; i++) {
+        if (0 == tdata->node_valid[i])
+            continue;
+
+        if ((data[i] < min[i]) || (data[i] > max[i])) {
+            FTS_TEST_SAVE_ERR("test fail,node(%4d,%4d)=%5d,range=(%5d,%5d)\n",
+                              i / rx + 1, i % rx + 1, data[i], min[i], max[i]);
+            result = false;
+        }
+    }
+
+    return result;
+}
+
+/*
+ * show_data - show and save test data to testresult.txt
+ */
+void show_data(int *data, bool key)
+{
+#if TXT_SUPPORT
+    int i = 0;
+    int j = 0;
+    struct fts_test *tdata = fts_ftest;
+    int node_num = tdata->node.node_num;
+    int tx_num = tdata->node.tx_num;
+    int rx_num = tdata->node.rx_num;
+
+    FTS_TEST_FUNC_ENTER();
+    for (i = 0; i < tx_num; i++) {
+        FTS_TEST_SAVE_INFO("Ch/Tx_%02d:  ", i + 1);
+        for (j = 0; j < rx_num; j++) {
+            FTS_TEST_SAVE_INFO("%5d, ", data[i * rx_num + j]);
+        }
+        FTS_TEST_SAVE_INFO("\n");
+    }
+
+    if (key) {
+        FTS_TEST_SAVE_INFO("Ch/Tx_%02d:  ", tx_num + 1);
+        for (i = tx_num * rx_num; i < node_num; i++) {
+            FTS_TEST_SAVE_INFO("%5d, ",  data[i]);
+        }
+        FTS_TEST_SAVE_INFO("\n");
+    }
+    FTS_TEST_FUNC_EXIT();
+#endif
+}
+
+/* mc_sc only */
+/* Only V3 Pattern has mapping & no-mapping */
+int mapping_switch(u8 mapping)
+{
+    int ret = 0;
+    u8 val = 0xFF;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata->v3_pattern) {
+        ret = fts_test_read_reg(FACTORY_REG_NOMAPPING, &val);
+        if (ret < 0) {
+            FTS_TEST_ERROR("read 0x54 register fail");
+            return ret;
+        }
+
+        if (val != mapping) {
+            ret = fts_test_write_reg(FACTORY_REG_NOMAPPING, mapping);
+            if (ret < 0) {
+                FTS_TEST_ERROR("write 0x54 register fail");
+                return ret;
+            }
+            sys_delay(FACTORY_TEST_DELAY);
+        }
+    }
+
+    return 0;
+}
+
+bool get_fw_wp(u8 wp_ch_sel, enum wp_type water_proof_type)
+{
+    bool fw_wp_state = false;
+
+    switch (water_proof_type) {
+    case WATER_PROOF_ON:
+        /* bit5: 0-check in wp on, 1-not check */
+        fw_wp_state = !(wp_ch_sel & 0x20);
+        break;
+    case WATER_PROOF_ON_TX:
+        /* Bit6:  0-check Rx+Tx in wp mode  1-check one channel
+           Bit2:  0-check Tx in wp mode;  1-check Rx in wp mode
+        */
+        fw_wp_state = (!(wp_ch_sel & 0x40) || !(wp_ch_sel & 0x04));
+        break;
+    case WATER_PROOF_ON_RX:
+        fw_wp_state = (!(wp_ch_sel & 0x40) || (wp_ch_sel & 0x04));
+        break;
+    case WATER_PROOF_OFF:
+        /* bit7: 0-check in wp off, 1-not check */
+        fw_wp_state = !(wp_ch_sel & 0x80);
+        break;
+    case WATER_PROOF_OFF_TX:
+        /* Bit1-0:  00-check Tx in non-wp mode
+                    01-check Rx in non-wp mode
+                    10:check Rx+Tx in non-wp mode
+        */
+        fw_wp_state = ((0x0 == (wp_ch_sel & 0x03)) || (0x02 == (wp_ch_sel & 0x03)));
+        break;
+    case WATER_PROOF_OFF_RX:
+        fw_wp_state = ((0x01 == (wp_ch_sel & 0x03)) || (0x02 == (wp_ch_sel & 0x03)));
+        break;
+    default:
+        break;
+    }
+
+    return fw_wp_state;
+}
+
+int get_cb_mc_sc(u8 wp, int byte_num, int *cb_buf, enum byte_mode mode)
+{
+    int ret = 0;
+
+    /* 1:waterproof 0:non-waterproof */
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, wp);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set mc_sc mode fail\n");
+        return ret;
+    }
+
+    if (fts_ftest->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            return ret;
+        }
+    }
+
+    /* read cb */
+    ret = get_cb_sc(byte_num, cb_buf, mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get sc cb fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+int get_rawdata_mc_sc(enum wp_type wp, int *data)
+{
+    int ret = 0;
+    u8 val = 0;
+    u8 addr = 0;
+    u8 rawdata_addr = 0;
+    int byte_num = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    byte_num = tdata->sc_node.node_num * 2;
+    addr = FACTORY_REG_LINE_ADDR;
+    rawdata_addr = FACTORY_REG_RAWDATA_ADDR_MC_SC;
+    if (WATER_PROOF_ON == wp) {
+        val = 0xAC;
+    } else if (WATER_PROOF_OFF == wp) {
+        val = 0xAB;
+    } else if (HIGH_SENSITIVITY == wp) {
+        val = 0xA0;
+    } else if (HOV == wp) {
+        val = 0xA1;
+        byte_num = 4 * 2;
+    }
+
+    ret = read_rawdata(tdata, addr, val, rawdata_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+int get_rawdata_mc(u8 fre, u8 fir, int *rawdata)
+{
+    int ret = 0;
+    int i = 0;
+
+    if (NULL == rawdata ) {
+        FTS_TEST_SAVE_ERR("rawdata buffer is null\n");
+        return -EINVAL;
+    }
+
+    /* set frequency high/low */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* fir enable/disable */
+    ret = fts_test_write_reg(FACTORY_REG_FIR, fir);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* get rawdata */
+    for (i = 0; i < 3; i++) {
+        /* lost 3 frames, in order to obtain stable data */
+        ret = get_rawdata(rawdata);
+    }
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    return 0;
+}
+
+int short_get_adc_data_mc(u8 retval, int byte_num, int *adc_buf, u8 mode)
+{
+    int ret = 0;
+    int i = 0;
+    u8 short_state = 0;
+    u8 short_state_reg = 0;
+    u8 short_en_reg = 0;
+    u8 short_data_reg = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+    if (tdata->func->mc_sc_short_v2) {
+        short_en_reg = FACTROY_REG_SHORT2_TEST_EN;
+        short_state_reg = FACTROY_REG_SHORT2_TEST_STATE;
+        short_data_reg = FACTORY_REG_SHORT2_ADDR_MC;
+    } else {
+        short_en_reg = FACTROY_REG_SHORT_TEST_EN;
+        short_state_reg = FACTROY_REG_SHORT_TEST_EN;
+        short_data_reg = FACTORY_REG_SHORT_ADDR_MC;
+    }
+
+    /* select short test mode & start test */
+    ret = fts_test_write_reg(short_en_reg, mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short test mode fail\n");
+        goto test_err;
+    }
+
+    for (i = 0; i < FACTORY_TEST_RETRY; i++) {
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+
+        ret = fts_test_read_reg(short_state_reg, &short_state);
+        if ((ret >= 0) && (retval == short_state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", short_state_reg, short_state, i);
+    }
+    if (i >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("short test timeout, ADC data not OK\n");
+        ret = -EIO;
+        goto test_err;
+    }
+
+    ret = read_mass_data(short_data_reg, byte_num, adc_buf);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get short(adc) data fail\n");
+    }
+
+    FTS_TEST_DBG("adc data:\n");
+    print_buffer(adc_buf, byte_num / 2, 0);
+test_err:
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+bool compare_mc_sc(bool tx_check, bool rx_check, int *data, int *min, int *max)
+{
+    int i = 0;
+    bool result = true;
+    struct fts_test *tdata = fts_ftest;
+
+    if (rx_check) {
+        for (i = 0; i < tdata->sc_node.rx_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((data[i] < min[i]) || (data[i] > max[i])) {
+                FTS_TEST_SAVE_ERR("test fail,rx%d=%5d,range=(%5d,%5d)\n",
+                                  i + 1, data[i], min[i], max[i]);
+                result = false;
+            }
+        }
+    }
+
+    if (tx_check) {
+        for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((data[i] < min[i]) || (data[i] > max[i])) {
+                FTS_TEST_SAVE_INFO("test fail,tx%d=%5d,range=(%5d,%5d)\n",
+                                   i - tdata->sc_node.rx_num + 1,
+                                   data[i], min[i], max[i]);
+                result = false;
+            }
+        }
+    }
+
+    return result;
+}
+
+void show_data_mc_sc(int *data)
+{
+    int i = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_SAVE_INFO("SCap Rx: ");
+    for (i = 0; i < tdata->sc_node.rx_num; i++) {
+        FTS_TEST_SAVE_INFO( "%5d, ", data[i]);
+    }
+    FTS_TEST_SAVE_INFO("\n");
+
+    FTS_TEST_SAVE_INFO("SCap Tx: ");
+    for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++) {
+        FTS_TEST_SAVE_INFO( "%5d, ", data[i]);
+    }
+    FTS_TEST_SAVE_INFO("\n");
+}
+/* mc_sc end*/
+
+#if CSV_SUPPORT || TXT_SUPPORT
+static int fts_test_save_test_data(char *file_name, char *data_buf, int len)
+{
+#ifdef FTS_VFS_EN
+    struct file *pfile = NULL;
+    char filepath[FILE_NAME_LENGTH] = { 0 };
+
+    loff_t pos;
+    mm_segment_t old_fs;
+
+    FTS_TEST_FUNC_ENTER();
+    memset(filepath, 0, sizeof(filepath));
+    snprintf(filepath, FILE_NAME_LENGTH, "%s%s", FTS_INI_FILE_PATH, file_name);
+    FTS_INFO("save test data to %s", filepath);
+    if (NULL == pfile) {
+        pfile = filp_open(filepath, O_TRUNC | O_CREAT | O_RDWR, 0);
+    }
+    if (IS_ERR(pfile)) {
+        FTS_TEST_ERROR("error occured while opening file %s.",  filepath);
+        return -EIO;
+    }
+
+    old_fs = get_fs();
+    set_fs(KERNEL_DS);
+    pos = 0;
+    vfs_write(pfile, data_buf, len, &pos);
+    filp_close(pfile, NULL);
+    set_fs(old_fs);
+
+    FTS_TEST_FUNC_EXIT();
+#endif
+    return 0;
+}
+
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+void fts_test_save_fail_result(
+    struct fts_test *tdata, char *prefix, char *suffix, char *buf, int len)
+{
+    char file_name[128];
+
+    if (false == tdata->result) {
+        snprintf(file_name, 128, "%s_%ld_%ld%s", prefix,
+                 (long)tdata->tv.tv_sec, (long)tdata->tv.tv_usec, suffix);
+        fts_test_save_test_data(file_name, buf, len);
+    }
+}
+#endif
+#endif
+
+static int fts_test_malloc_free_data_txt(struct fts_test *tdata, bool allocate)
+{
+#if TXT_SUPPORT
+    if (true == allocate) {
+        tdata->testresult = vmalloc(TXT_BUFFER_LEN);
+        if (NULL == tdata->testresult) {
+            FTS_TEST_ERROR("tdata->testresult malloc fail\n");
+            return -ENOMEM;
+        }
+
+        tdata->testresult_len = 0;
+        FTS_TEST_SAVE_INFO("FW version:V%02x_D%02x\n", tdata->fw_major_ver,
+            tdata->fw_minor_verver);
+        FTS_TEST_SAVE_INFO("tx_num:%d, rx_num:%d, key_num:%d\n",
+                           tdata->node.tx_num, tdata->node.rx_num,
+                           tdata->node.key_num);
+    } else {
+        if (tdata->testresult) {
+            vfree(tdata->testresult);
+            tdata->testresult = NULL;
+        }
+    }
+#endif
+    return 0;
+}
+
+#if CSV_SUPPORT
+static int fts_test_get_item_count_scap_csv(int index)
+{
+    int ret = 0;
+    int i = 0;
+    int select = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 scap_select[4] = { 0 };
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        return index;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        return index;
+    }
+
+    scap_select[0] = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    scap_select[1] = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    scap_select[2] = (hc_sel & 0x03) ? 1 : 0;
+    scap_select[3] = (hc_sel & 0x04) ? 1 : 0;
+
+    for (i = 0; i < 4; i++) {
+        if (scap_select[i])
+            select++;
+        if (select == index)
+            break;
+    }
+
+    return (i + 1);
+}
+#endif
+
+static void fts_test_save_data_csv(struct fts_test *tdata)
+{
+#if CSV_SUPPORT
+    int i = 0;
+    int j = 0;
+    int index = 0;
+    int k = 0;
+    int tx = 0;
+    int rx = 0;
+    int node_num = 0;
+    int offset = 0;
+    int start_line = 11;
+    int data_count = 0;
+    char *csv_buffer = NULL;
+    char *line2_buffer = NULL;
+    int csv_length = 0;
+    int line2_length = 0;
+    int csv_item_count = 0;
+    struct fts_test_data *td = &tdata->testdata;
+    struct item_info *info = NULL;
+
+    FTS_TEST_INFO("save data in csv format");
+    csv_buffer = vmalloc(CSV_BUFFER_LEN);
+    if (!csv_buffer) {
+        FTS_TEST_ERROR("csv_buffer malloc fail\n");
+        return ;
+    }
+
+    line2_buffer = vmalloc(CSV_LINE2_BUFFER_LEN);
+    if (!line2_buffer) {
+        FTS_TEST_ERROR("line2_buffer malloc fail\n");
+        goto csv_save_err;
+    }
+
+    FTS_TEST_INFO("test item count:%d", td->item_count);
+    /* line 1 */
+    csv_length += snprintf(csv_buffer + csv_length, \
+                           CSV_BUFFER_LEN - csv_length, \
+                           "ECC, 85, 170, IC Name, %s, IC Code, %x\n", \
+                           tdata->ini.ic_name, \
+                           (tdata->ini.ic_code >> IC_CODE_OFFSET));
+
+    /* line 2 */
+    for (i = 0; i < td->item_count; i++) {
+        info = &td->info[i];
+        if (info->mc_sc) {
+            node_num = tdata->sc_node.node_num;
+            /* set max len of tx/rx to column */
+            rx = (tdata->sc_node.tx_num > tdata->sc_node.rx_num)
+                 ? tdata->sc_node.tx_num : tdata->sc_node.rx_num;
+        } else {
+            if (info->key_support && (tdata->node.key_num > 0))
+                node_num = (tdata->node.tx_num + 1) * tdata->node.rx_num;
+            else
+                node_num = tdata->node.tx_num * tdata->node.rx_num;
+            rx = tdata->node.rx_num;
+        }
+
+        if (info->datalen > node_num) {
+            data_count = (info->datalen - 1 ) / node_num + 1;
+            tx = (node_num - 1 ) / rx + 1;
+        } else {
+            data_count = 1;
+            tx = ((info->datalen - 1) / rx) + 1;
+        }
+
+        for (j = 1; j <= data_count; j++) {
+            index = j;
+
+            if (tdata->func->hwtype == IC_HW_MC_SC) {
+                /*MC_SC, rawdata index will be 2*/
+                if ((info->code == CODE_M_RAWDATA_TEST) && (data_count == 1)) {
+                    index = 2;
+                }
+
+                /*MC_SC, SCAP index will be 1~4*/
+                if ((info->code == CODE_M_SCAP_CB_TEST)
+                    || (info->code == CODE_M_SCAP_RAWDATA_TEST)) {
+                    index = fts_test_get_item_count_scap_csv(j);
+                }
+            }
+
+            line2_length += snprintf(line2_buffer + line2_length, \
+                                     CSV_LINE2_BUFFER_LEN - line2_length, \
+                                     "%s, %d, %d, %d, %d, %d, ", \
+                                     info->name, info->code, tx, rx,
+                                     start_line, index);
+            start_line += tx;
+            csv_item_count++;
+        }
+    }
+
+    csv_length += snprintf(csv_buffer + csv_length, \
+                           CSV_BUFFER_LEN - csv_length, \
+                           "TestItem Num, %d, ", \
+                           csv_item_count);
+
+    if (line2_length > 0) {
+        csv_length += snprintf(csv_buffer + csv_length, \
+                               CSV_BUFFER_LEN - csv_length, \
+                               "%s", line2_buffer);
+    }
+
+    /* line 3 ~ 10  "\n" */
+    csv_length += snprintf(csv_buffer + csv_length, \
+                           CSV_BUFFER_LEN - csv_length, \
+                           "\n\n\n\n\n\n\n\n\n");
+
+    /* line 11 ~ data area */
+    for (i = 0; i < td->item_count; i++) {
+        info = &td->info[i];
+        if (!info->data) {
+            FTS_TEST_ERROR("test item data is null");
+            goto csv_save_err;
+        }
+
+        if (info->mc_sc) {
+            offset = 0;
+            for (j = 0; j < info->datalen;) {
+                for (k = 0; k < tdata->sc_node.node_num; k++) {
+                    csv_length += snprintf(csv_buffer + csv_length, \
+                                           CSV_BUFFER_LEN - csv_length, \
+                                           "%d, ", info->data[offset + k]);
+                    if ((k + 1) == tdata->sc_node.rx_num) {
+                        csv_length += snprintf(csv_buffer + csv_length, \
+                                               CSV_BUFFER_LEN - csv_length, \
+                                               "\n");
+                    }
+                }
+                csv_length += snprintf(csv_buffer + csv_length, \
+                                       CSV_BUFFER_LEN - csv_length, \
+                                       "\n");
+                offset += k;
+                j += k;
+            }
+        } else {
+            for (j = 0; j < info->datalen; j++) {
+                csv_length += snprintf(csv_buffer + csv_length, \
+                                       CSV_BUFFER_LEN - csv_length, \
+                                       "%d, ", info->data[j]);
+                if (((j + 1) % tdata->node.rx_num) == 0) {
+                    csv_length += snprintf(csv_buffer + csv_length, \
+                                           CSV_BUFFER_LEN - csv_length,
+                                           "\n");
+                }
+            }
+        }
+    }
+    FTS_TEST_INFO("csv length:%d", csv_length);
+    fts_test_save_test_data(FTS_CSV_FILE_NAME, csv_buffer, csv_length);
+
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+    fts_test_save_fail_result(tdata, "testdata_fail", ".csv",
+                              csv_buffer, csv_length);
+#endif
+
+
+csv_save_err:
+    if (line2_buffer) {
+        vfree(line2_buffer);
+        line2_buffer = NULL;
+    }
+
+    if (csv_buffer) {
+        vfree(csv_buffer);
+        csv_buffer = NULL;
+    }
+#endif
+}
+
+static void fts_test_save_result_txt(struct fts_test *tdata)
+{
+#if TXT_SUPPORT
+    if (!tdata || !tdata->testresult) {
+        FTS_TEST_ERROR("test result is null");
+        return;
+    }
+
+    FTS_TEST_INFO("test result length in txt:%d", tdata->testresult_len);
+    fts_test_save_test_data(FTS_TXT_FILE_NAME, tdata->testresult,
+                            tdata->testresult_len);
+
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+    fts_test_save_fail_result(tdata, "testresult_fail", ".txt",
+                              tdata->testresult, tdata->testresult_len);
+#endif
+
+#endif
+}
+
+/*****************************************************************************
+* Name: fts_test_save_data
+* Brief: Save test data.
+*        If multi-data of MC, length of data package must be tx*rx,(tx+1)*rx
+*        If multi-data of MC-SC, length of data package should be (tx+rx)*2
+*        Need fill 0 when no actual data
+* Input:
+* Output:
+* Return:
+*****************************************************************************/
+void fts_test_save_data(char *name, int code, int *data, int datacnt,
+                        bool mc_sc, bool key, bool result)
+{
+    int datalen = datacnt;
+    struct fts_test *tdata = fts_ftest;
+    struct fts_test_data *td = &tdata->testdata;
+    struct item_info *info = &td->info[td->item_count];
+
+    if (!name || !data) {
+        FTS_TEST_ERROR("name/data is null");
+        return ;
+    }
+
+    strlcpy(info->name, name, TEST_ITEM_NAME_MAX - 1);
+    info->code = code;
+    info->mc_sc = mc_sc;
+    info->key_support = key;
+    info->result = result;
+    if (datalen <= 0) {
+        if (mc_sc) {
+            datalen = tdata->sc_node.node_num * 2;
+        } else {
+            if (key && (tdata->node.key_num > 0))
+                datalen = (tdata->node.tx_num + 1) * tdata->node.rx_num;
+            else
+                datalen = tdata->node.tx_num * tdata->node.rx_num;
+
+        }
+    }
+
+    FTS_TEST_DBG("name:%s,len:%d", name, datalen);
+    info->data = fts_malloc(datalen * sizeof(int));
+    if (!info->data) {
+        FTS_TEST_ERROR("malloc memory for item(%d) data fail", td->item_count);
+        info->datalen = 0;
+        return ;
+    }
+    memcpy(info->data, data, datalen * sizeof(int));
+    info->datalen = datalen;
+
+    td->item_count++;
+}
+
+static void fts_test_free_data(struct fts_test *tdata)
+{
+    int i = 0;
+    struct fts_test_data *td = &tdata->testdata;
+
+    for (i = 0; i < td->item_count; i++) {
+        if (td->info[i].data) {
+            fts_free(td->info[i].data);
+        }
+    }
+}
+
+static int fts_test_malloc_free_incell(struct fts_test *tdata, bool allocate)
+{
+    struct incell_threshold *thr = &tdata->ic.incell.thr;
+    int buflen = tdata->node.node_num * sizeof(int);
+
+    if (true == allocate) {
+        FTS_TEST_INFO("buflen:%d", buflen);
+        fts_malloc_r(thr->rawdata_min, buflen);
+        fts_malloc_r(thr->rawdata_max, buflen);
+        if (tdata->func->rawdata2_support) {
+            fts_malloc_r(thr->rawdata2_min, buflen);
+            fts_malloc_r(thr->rawdata2_max, buflen);
+        }
+        fts_malloc_r(thr->cb_min, buflen);
+        fts_malloc_r(thr->cb_max, buflen);
+    } else {
+        fts_free(thr->rawdata_min);
+        fts_free(thr->rawdata_max);
+        if (tdata->func->rawdata2_support) {
+            fts_free(thr->rawdata2_min);
+            fts_free(thr->rawdata2_max);
+        }
+        fts_free(thr->cb_min);
+        fts_free(thr->cb_max);
+    }
+
+    return 0;
+}
+
+static int fts_test_malloc_free_mc_sc(struct fts_test *tdata, bool allocate)
+{
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+    int buflen = tdata->node.node_num * sizeof(int);
+    int buflen_sc = tdata->sc_node.node_num * sizeof(int);
+
+    if (true == allocate) {
+        fts_malloc_r(thr->rawdata_h_min, buflen);
+        fts_malloc_r(thr->rawdata_h_max, buflen);
+        if (tdata->func->rawdata2_support) {
+            fts_malloc_r(thr->rawdata_l_min, buflen);
+            fts_malloc_r(thr->rawdata_l_max, buflen);
+        }
+        fts_malloc_r(thr->tx_linearity_max, buflen);
+        fts_malloc_r(thr->tx_linearity_min, buflen);
+        fts_malloc_r(thr->rx_linearity_max, buflen);
+        fts_malloc_r(thr->rx_linearity_min, buflen);
+
+        fts_malloc_r(thr->scap_cb_off_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_off_max, buflen_sc);
+        fts_malloc_r(thr->scap_cb_on_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_on_max, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hi_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hi_max, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hov_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hov_max, buflen_sc);
+
+        fts_malloc_r(thr->scap_rawdata_off_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_off_max, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_on_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_on_max, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hi_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hi_max, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hov_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hov_max, buflen_sc);
+
+        fts_malloc_r(thr->panel_differ_min, buflen);
+        fts_malloc_r(thr->panel_differ_max, buflen);
+    } else {
+        fts_free(thr->rawdata_h_min);
+        fts_free(thr->rawdata_h_max);
+        if (tdata->func->rawdata2_support) {
+            fts_free(thr->rawdata_l_min);
+            fts_free(thr->rawdata_l_max);
+        }
+        fts_free(thr->tx_linearity_max);
+        fts_free(thr->tx_linearity_min);
+        fts_free(thr->rx_linearity_max);
+        fts_free(thr->rx_linearity_min);
+
+        fts_free(thr->scap_cb_off_min);
+        fts_free(thr->scap_cb_off_max);
+        fts_free(thr->scap_cb_on_min);
+        fts_free(thr->scap_cb_on_max);
+        fts_free(thr->scap_cb_hi_min);
+        fts_free(thr->scap_cb_hi_max);
+        fts_free(thr->scap_cb_hov_min);
+        fts_free(thr->scap_cb_hov_max);
+
+        fts_free(thr->scap_rawdata_off_min);
+        fts_free(thr->scap_rawdata_off_max);
+        fts_free(thr->scap_rawdata_on_min);
+        fts_free(thr->scap_rawdata_on_max);
+        fts_free(thr->scap_rawdata_hi_min);
+        fts_free(thr->scap_rawdata_hi_max);
+        fts_free(thr->scap_rawdata_hov_min);
+        fts_free(thr->scap_rawdata_hov_max);
+
+        fts_free(thr->panel_differ_min);
+        fts_free(thr->panel_differ_max);
+    }
+
+    return 0;
+}
+
+static int fts_test_malloc_free_sc(struct fts_test *tdata, bool allocate)
+{
+    struct sc_threshold *thr = &tdata->ic.sc.thr;
+    int buflen = tdata->node.node_num * sizeof(int);
+
+    if (true == allocate) {
+        fts_malloc_r(thr->rawdata_min, buflen);
+        fts_malloc_r(thr->rawdata_max, buflen);
+        fts_malloc_r(thr->cb_min, buflen);
+        fts_malloc_r(thr->cb_max, buflen);
+        fts_malloc_r(thr->dcb_sort, buflen);
+        fts_malloc_r(thr->dcb_base, buflen);
+    } else {
+        fts_free(thr->rawdata_min);
+        fts_free(thr->rawdata_max);
+        fts_free(thr->cb_min);
+        fts_free(thr->cb_max);
+        fts_free(thr->dcb_sort);
+        fts_free(thr->dcb_base);
+    }
+
+    return 0;
+}
+
+static int fts_test_malloc_free_thr(struct fts_test *tdata, bool allocate)
+{
+    int ret = 0;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("tdata/func is NULL\n");
+        return -EINVAL;
+    }
+
+    if (true == allocate) {
+        fts_malloc_r(tdata->node_valid, tdata->node.node_num * sizeof(int));
+        fts_malloc_r(tdata->node_valid_sc, tdata->sc_node.node_num * sizeof(int));
+    } else {
+        fts_free(tdata->node_valid);
+        fts_free(tdata->node_valid_sc);
+    }
+
+    switch (tdata->func->hwtype) {
+    case IC_HW_INCELL:
+        ret = fts_test_malloc_free_incell(tdata, allocate);
+        break;
+    case IC_HW_MC_SC:
+        ret = fts_test_malloc_free_mc_sc(tdata, allocate);
+        break;
+    case IC_HW_SC:
+        ret = fts_test_malloc_free_sc(tdata, allocate);
+        break;
+    default:
+        FTS_TEST_SAVE_ERR("test ic type(%d) fail\n", tdata->func->hwtype);
+        ret = -EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+/* default enable all test item */
+static void fts_test_init_item(struct fts_test *tdata)
+{
+    switch (tdata->func->hwtype) {
+    case IC_HW_INCELL:
+        tdata->ic.incell.u.tmp = 0xFFFFFFFF;
+        break;
+    case IC_HW_MC_SC:
+        tdata->ic.mc_sc.u.tmp = 0xFFFFFFFF;
+        break;
+    case IC_HW_SC:
+        tdata->ic.sc.u.tmp = 0xFFFFFFFF;
+        break;
+    }
+}
+
+static int get_tx_rx_num(u8 tx_rx_reg, u8 *ch_num, u8 ch_num_max)
+{
+    int ret = 0;
+    int i = 0;
+
+    for (i = 0; i < 3; i++) {
+        ret = fts_test_read_reg(tx_rx_reg, ch_num);
+        if ((ret < 0) || (*ch_num > ch_num_max)) {
+            sys_delay(50);
+        } else
+            break;
+    }
+
+    if (i >= 3) {
+        FTS_TEST_ERROR("get channel num fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+static int get_key_num(int *key_num_en, int max_key_num)
+{
+    int ret = 0;
+    u8 key_en = 0;
+
+    if (!max_key_num) {
+        FTS_TEST_DBG("not support key, don't read key num register");
+        return 0;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_LEFT_KEY, &key_en);
+    if (ret >= 0) {
+        if (key_en & 0x01) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x02) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x04) {
+            (*key_num_en)++;
+        }
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_RIGHT_KEY, &key_en);
+    if (ret >= 0) {
+        if (key_en & 0x01) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x02) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x04) {
+            (*key_num_en)++;
+        }
+    }
+
+    if (*key_num_en > max_key_num) {
+        FTS_TEST_ERROR("get key num, fw:%d > max:%d", *key_num_en, max_key_num);
+        return -EIO;
+    }
+
+    return ret;
+}
+
+static int get_channel_num(struct fts_test *tdata)
+{
+    int ret = 0;
+    u8 tx_num = 0;
+    u8 rx_num = 0;
+    int key_num = 0;
+
+    /* node structure */
+    if (IC_HW_SC == tdata->func->hwtype) {
+        ret = get_tx_rx_num(FACTORY_REG_CH_NUM_SC, &tx_num, NUM_MAX_SC);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get channel number fail");
+            return ret;
+        }
+
+        ret = get_tx_rx_num(FACTORY_REG_KEY_NUM_SC, &rx_num, KEY_NUM_MAX);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get key number fail");
+            return ret;
+        }
+
+        tdata->node.tx_num = 1;
+        tdata->node.rx_num = tx_num;
+        tdata->node.channel_num = tx_num;
+        tdata->node.node_num = tx_num;
+        key_num = rx_num;
+    } else {
+        ret = get_tx_rx_num(FACTORY_REG_CHX_NUM, &tx_num, TX_NUM_MAX);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get tx_num fail");
+            return ret;
+        }
+
+        ret = get_tx_rx_num(FACTORY_REG_CHY_NUM, &rx_num, RX_NUM_MAX);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get rx_num fail");
+            return ret;
+        }
+
+        if (IC_HW_INCELL == tdata->func->hwtype) {
+            ret = get_key_num(&key_num, tdata->func->key_num_total);
+            if (ret < 0) {
+                FTS_TEST_ERROR("get key_num fail");
+                return ret;
+            }
+        } else if (IC_HW_MC_SC == tdata->func->hwtype) {
+            key_num = tdata->func->key_num_total;
+        }
+        tdata->node.tx_num = tx_num;
+        tdata->node.rx_num = rx_num;
+        if (IC_HW_INCELL == tdata->func->hwtype)
+            tdata->node.channel_num = tx_num * rx_num;
+        else if (IC_HW_MC_SC == tdata->func->hwtype)
+            tdata->node.channel_num = tx_num + rx_num;
+        tdata->node.node_num = tx_num * rx_num;
+    }
+
+    /* key */
+    tdata->node.key_num = key_num;
+    tdata->node.node_num += tdata->node.key_num;
+
+    /* sc node structure */
+    tdata->sc_node = tdata->node;
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        if (tdata->v3_pattern) {
+            ret = get_tx_rx_num(FACTORY_REG_CHX_NUM_NOMAP, &tx_num, TX_NUM_MAX);
+            if (ret < 0) {
+                FTS_TEST_ERROR("get no-mappint tx_num fail");
+                return ret;
+            }
+
+            ret = get_tx_rx_num(FACTORY_REG_CHY_NUM_NOMAP, &rx_num, TX_NUM_MAX);
+            if (ret < 0) {
+                FTS_TEST_ERROR("get no-mapping rx_num fail");
+                return ret;
+            }
+
+            tdata->sc_node.tx_num = tx_num;
+            tdata->sc_node.rx_num = rx_num;
+        }
+        tdata->sc_node.channel_num = tx_num + rx_num;
+        tdata->sc_node.node_num = tx_num + rx_num;
+    }
+
+    if (tdata->node.tx_num > TX_NUM_MAX) {
+        FTS_TEST_ERROR("tx num(%d) fail", tdata->node.tx_num);
+        return -EIO;
+    }
+
+    if (tdata->node.rx_num > RX_NUM_MAX) {
+        FTS_TEST_ERROR("rx num(%d) fail", tdata->node.rx_num);
+        return -EIO;
+    }
+
+    FTS_TEST_INFO("node_num:%d, tx:%d, rx:%d, key:%d",
+                  tdata->node.node_num, tdata->node.tx_num,
+                  tdata->node.rx_num, tdata->node.key_num);
+    return 0;
+}
+
+static int fts_test_init_basicinfo(struct fts_test *tdata)
+{
+    int ret = 0;
+    u8 val = 0;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("tdata/func is NULL\n");
+        return -EINVAL;
+    }
+
+    fts_test_read_reg(REG_FW_MAJOR_VER, &val);
+    if (ret < 0) {
+        FTS_ERROR("read fw major version fail,ret=%d\n", ret);
+        return ret;
+    }
+    tdata->fw_major_ver = val;
+
+    fts_test_read_reg(REG_FW_MINOR_VER, &val);
+    if (ret < 0) {
+        FTS_ERROR("read fw minor version fail,ret=%d\n", ret);
+        return ret;
+    }
+    tdata->fw_minor_ver = val;
+
+    if (IC_HW_INCELL == tdata->func->hwtype) {
+        fts_test_read_reg(REG_VA_TOUCH_THR, &val);
+        tdata->va_touch_thr = val;
+        fts_test_read_reg(REG_VKEY_TOUCH_THR, &val);
+        tdata->vk_touch_thr = val;
+    }
+
+    /* enter factory mode */
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail\n");
+        return ret;
+    }
+
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        fts_test_read_reg(FACTORY_REG_PATTERN, &val);
+        tdata->v3_pattern = (1 == val) ? true : false;
+        fts_test_read_reg(FACTORY_REG_NOMAPPING, &val);
+        tdata->mapping = val;
+    }
+
+    /* enter into factory mode and read tx/rx num */
+    ret = get_channel_num(tdata);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get channel number fail\n");
+        return ret;
+    }
+
+    return ret;
+}
+
+static int fts_test_main_init(void)
+{
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+    /* Init fts_test_data to 0 before test,  */
+    memset(&tdata->testdata, 0, sizeof(struct fts_test_data));
+
+    /* get basic information: tx/rx num ... */
+    ret = fts_test_init_basicinfo(tdata);
+    if (ret < 0) {
+        FTS_TEST_ERROR("test init basicinfo fail");
+        return ret;
+    }
+
+    /* allocate memory for test threshold */
+    ret = fts_test_malloc_free_thr(tdata, true);
+    if (ret < 0) {
+        FTS_TEST_ERROR("test malloc for threshold fail");
+        return ret;
+    }
+
+    /* default enable all test item */
+    fts_test_init_item(tdata);
+
+    ret = fts_test_malloc_free_data_txt(tdata, true);
+    if (ret < 0) {
+        FTS_TEST_ERROR("allocate memory for test data(txt) fail");
+        return ret;
+    }
+
+    /* allocate test data buffer */
+    tdata->buffer_length = (tdata->node.tx_num + 1) * tdata->node.rx_num;
+    tdata->buffer_length *= sizeof(int) * 2;
+    FTS_TEST_INFO("test buffer length:%d", tdata->buffer_length);
+    tdata->buffer = (int *)fts_malloc(tdata->buffer_length);
+    if (NULL == tdata->buffer) {
+        FTS_TEST_ERROR("test buffer(%d) malloc fail", tdata->buffer_length);
+        return -ENOMEM;
+    }
+    memset(tdata->buffer, 0, tdata->buffer_length);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_test_main_exit(void)
+{
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+    fts_test_save_data_csv(tdata);
+    fts_test_save_result_txt(tdata);
+
+    /* free memory */
+    fts_test_malloc_free_data_txt(tdata, false);
+    fts_test_malloc_free_thr(tdata, false);
+
+    /* free test data */
+    fts_test_free_data(tdata);
+
+    /*free test data buffer*/
+    fts_free(tdata->buffer);
+
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+
+/*
+ * fts_test_get_testparams - get test parameter from ini
+ */
+static int fts_test_get_testparams(char *config_name)
+{
+    int ret = 0;
+
+    ret = fts_test_get_testparam_from_ini(config_name);
+
+    return ret;
+}
+
+static int fts_test_start(void)
+{
+    int testresult = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata && tdata->func && tdata->func->start_test) {
+        tdata->testdata.item_count = 0;
+        testresult = tdata->func->start_test();
+    } else {
+        FTS_TEST_ERROR("test func/start_test func is null");
+    }
+
+    return testresult;
+}
+
+/*
+ * fts_test_entry - test main entry
+ *
+ * warning - need disable irq & esdcheck before call this function
+ *
+ */
+static int fts_test_entry(char *ini_file_name)
+{
+    int ret = 0;
+
+    /* test initialize */
+    ret = fts_test_main_init();
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto test_err;
+    }
+
+    /*Read parse configuration file*/
+    FTS_TEST_SAVE_INFO("ini_file_name:%s\n", ini_file_name);
+    ret = fts_test_get_testparams(ini_file_name);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get testparam fail");
+        goto test_err;
+    }
+
+    /* Start testing according to the test configuration */
+    if (true == fts_test_start()) {
+        FTS_TEST_SAVE_INFO("=======Tp test pass.");
+        if (fts_ftest->s) seq_printf(fts_ftest->s, "=======Tp test pass.\n");
+        fts_ftest->result = true;
+    } else {
+        FTS_TEST_SAVE_INFO("=======Tp test failure.");
+        if (fts_ftest->s) seq_printf(fts_ftest->s, "=======Tp test failure.\n");
+        fts_ftest->result = false;
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+        do_gettimeofday(&(fts_ftest->tv));
+#endif
+    }
+
+test_err:
+    fts_test_main_exit();
+    enter_work_mode();
+    return ret;
+}
+
+static ssize_t fts_test_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+    ssize_t size = 0;
+
+    mutex_lock(&input_dev->mutex);
+    size += snprintf(buf + size, PAGE_SIZE, "FTS_INI_FILE_PATH:%s\n",
+                     FTS_INI_FILE_PATH);
+    size += snprintf(buf + size, PAGE_SIZE, "FTS_CSV_FILE_NAME:%s\n",
+                     FTS_CSV_FILE_NAME);
+    size += snprintf(buf + size, PAGE_SIZE, "FTS_TXT_FILE_NAME:%s\n",
+                     FTS_TXT_FILE_NAME);
+    mutex_unlock(&input_dev->mutex);
+
+    return size;
+}
+
+static ssize_t fts_test_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev;
+
+    if (ts_data->suspended) {
+        FTS_INFO("In suspend, no test, return now");
+        return -EINVAL;
+    }
+
+    input_dev = ts_data->input_dev;
+    memset(fwname, 0, sizeof(fwname));
+    snprintf(fwname, FILE_NAME_LENGTH, "%s", buf);
+    fwname[count - 1] = '\0';
+    FTS_TEST_DBG("fwname:%s.", fwname);
+
+    mutex_lock(&input_dev->mutex);
+    fts_irq_disable();
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    fts_ftest->s = NULL;
+    ret = fts_enter_test_environment(1);
+    if (ret < 0) {
+        FTS_ERROR("enter test environment fail");
+    } else {
+        fts_test_entry(fwname);
+    }
+    ret = fts_enter_test_environment(0);
+    if (ret < 0) {
+        FTS_ERROR("enter normal environment fail");
+    }
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    fts_irq_enable();
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/*  test from test.ini
+ *  example:echo "***.ini" > fts_test
+ */
+static DEVICE_ATTR(fts_test, S_IRUGO | S_IWUSR, fts_test_show, fts_test_store);
+
+static struct attribute *fts_test_attributes[] = {
+    &dev_attr_fts_test.attr,
+    NULL
+};
+
+static struct attribute_group fts_test_attribute_group = {
+    .attrs = fts_test_attributes
+};
+
+static int fts_test_func_init(struct fts_ts_data *ts_data)
+{
+    int i = 0;
+    int j = 0;
+    u16 ic_stype = ts_data->ic_info.ids.type;
+    struct test_funcs *func = test_func_list[0];
+    int func_count = sizeof(test_func_list) / sizeof(test_func_list[0]);
+
+    FTS_TEST_FUNC_ENTER();
+    if (0 == func_count) {
+        FTS_TEST_SAVE_ERR("test functions list is NULL, fail\n");
+        return -ENODATA;
+    }
+
+    fts_ftest = (struct fts_test *)kzalloc(sizeof(*fts_ftest), GFP_KERNEL);
+    if (NULL == fts_ftest) {
+        FTS_TEST_ERROR("malloc memory for test fail");
+        return -ENOMEM;
+    }
+
+    for (i = 0; i < func_count; i++) {
+        func = test_func_list[i];
+        for (j = 0; j < FTS_MAX_COMPATIBLE_TYPE; j++) {
+            if (0 == func->ctype[j])
+                break;
+            else if (func->ctype[j] == ic_stype) {
+                FTS_TEST_INFO("match test function,type:%x",
+                    (int)func->ctype[j]);
+                fts_ftest->func = func;
+            }
+        }
+    }
+    if (NULL == fts_ftest->func) {
+        FTS_TEST_ERROR("no test function match, can't test");
+        return -ENODATA;
+    }
+
+    fts_ftest->ts_data = ts_data;
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+/*run_os_test*/
+#define RUN_OS_TEST_INI_FILE        "focaltech_testconf.ini"
+static int proc_run_os_test_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_test *tdata = (struct fts_test *)s->private;
+    struct fts_ts_data *ts_data = NULL;
+    struct input_dev *input_dev = NULL;
+
+    if (s->size <= (PAGE_SIZE * 4)) {
+        s->count = s->size;
+        FTS_TEST_ERROR("Buffer size:%d, return ", (int)s->size);
+        return 0;
+    }
+
+    if (!tdata) {
+        FTS_TEST_ERROR("test data is null, return");
+        return -EINVAL;
+    }
+
+    ts_data = tdata->ts_data;
+    input_dev = ts_data->input_dev;
+    if (ts_data->suspended) {
+        FTS_TEST_ERROR("In suspend, no test, return");
+        return -EINVAL;
+    }
+
+    mutex_lock(&input_dev->mutex);
+    fts_irq_disable();
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    tdata->s = s;
+    ret = fts_enter_test_environment(1);
+    if (ret < 0) {
+        FTS_ERROR("enter test environment fail");
+    } else {
+        fts_test_entry(RUN_OS_TEST_INI_FILE);
+    }
+    ret = fts_enter_test_environment(0);
+    if (ret < 0) {
+        FTS_ERROR("enter normal environment fail");
+    }
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    fts_irq_enable();
+    mutex_unlock(&input_dev->mutex);
+
+    return 0;
+}
+
+static int proc_run_os_test_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_run_os_test_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_run_os_test_fops = {
+    .proc_open   = proc_run_os_test_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_run_os_test_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_run_os_test_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/*FW Version test*/
+static int proc_test_fwver_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 fw_major_ver = 0;
+    u8 fw_minor_ver = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = fts_read_reg(REG_FW_MAJOR_VER, &fw_major_ver);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read major version fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_read_reg(REG_FW_MINOR_VER, &fw_minor_ver);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read minor version fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    seq_printf(s, "FWVER:V%02x_D%02x\n", fw_major_ver, fw_minor_ver);
+
+exit:
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_fwver_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_fwver_show, inode->i_private);
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_fwver_fops = {
+    .proc_open   = proc_test_fwver_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_fwver_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_fwver_open,
+    .read  = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/*Channel Num test*/
+static int proc_test_chnum_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 tx = 0;
+    u8 rx = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    seq_printf(s, "TX:%02d, RX:%02d\n", tx, rx);
+
+exit:
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_chnum_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_chnum_show, inode->i_private);
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_chnum_fops = {
+    .proc_open   = proc_test_chnum_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_chnum_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_chnum_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* HW Reset_Pin Test */
+static int proc_test_hw_reset_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 reg88_val = 0xFF;
+    u8 tmp_val = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = fts_read_reg(FTS_TMP_REG_88, &reg88_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails");
+        goto exit;
+    }
+
+    tmp_val = reg88_val - 1;
+    ret = fts_write_reg(FTS_TMP_REG_88, tmp_val);
+    if (ret < 0) {
+        FTS_ERROR("write reg88 fails");
+        goto exit;
+    }
+
+    fts_reset_proc(200);
+
+    ret = fts_read_reg(FTS_TMP_REG_88, &tmp_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails");
+        goto exit;
+    }
+
+    if (tmp_val == reg88_val)
+        seq_printf(s, "Reset Pin test PASS.\n");
+    else
+        seq_printf(s, "Reset Pin test FAIL.\n");
+
+exit:
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_hw_reset_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_hw_reset_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_hw_reset_fops = {
+    .proc_open   = proc_test_hw_reset_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_hw_reset_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_hw_reset_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* SW Reset Test */
+static int proc_test_sw_reset_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 reg88_val = 0;
+    u8 tmp_val = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = fts_read_reg(FTS_TMP_REG_88, &reg88_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails");
+        goto exit;
+    }
+
+    ret = fts_write_reg(FTS_TMP_REG_88, 0x22);
+    if (ret < 0) {
+        FTS_ERROR("write reg88 fails for SW reset");
+        goto exit;
+    }
+
+    ret = fts_write_reg(FTS_TMP_REG_SOFT_RESET, 0xAA);
+    if (ret < 0) {
+        FTS_ERROR("write 0xAA to reg 0xFC fails");
+        goto exit;
+    }
+
+    ret = fts_write_reg(FTS_TMP_REG_SOFT_RESET, 0x66);
+    if (ret < 0) {
+        FTS_ERROR("write 0x66 to reg 0xFC fails");
+        goto exit;
+    }
+    sys_delay(40);
+    ret = fts_read_reg(FTS_TMP_REG_88, &tmp_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails for SW reset");
+        goto exit;
+    }
+
+    if (tmp_val == reg88_val)
+        seq_printf(s, "SW Reset test PASS.\n");
+    else
+        seq_printf(s, "SW Reset test FAIL.\n");
+
+exit:
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_sw_reset_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_sw_reset_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_sw_reset_fops = {
+    .proc_open   = proc_test_sw_reset_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_sw_reset_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_sw_reset_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* INT_Pin Test */
+int int_test_has_interrupt = 0;
+static int proc_test_int_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    fts_irq_enable();
+    sys_delay(10);
+    int_test_has_interrupt = 0;
+    ret = fts_write_reg(FACTORY_REG_SCAN_ADDR2, 0x01);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    sys_delay(1000);
+
+    if (int_test_has_interrupt)
+        seq_printf(s, "INT Pin test PASS.\n");
+    else
+        seq_printf(s, "INT Pin test FAIL.\n");
+
+exit:
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_int_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_int_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_int_fops = {
+    .proc_open   = proc_test_int_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_int_fops = {
+    .open   = proc_test_int_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+
+extern int fts_test_get_raw(int *raw, u8 tx, u8 rx);
+extern int fts_test_get_baseline(int *raw,int *base_raw, u8 tx, u8 rx);
+extern int fts_test_get_strength(u8 *base_raw, u16 base_raw_size);
+extern int fts_test_get_uniformity_data(int *rawdata_linearity, u8 tx, u8 rx);
+extern int fts_test_get_scap_raw(int *scap_raw, u8 tx, u8 rx, int *fwcheck);
+extern int fts_test_get_scap_cb(int *scap_cb, u8 tx, u8 rx, int *fwcheck);
+extern int fts_test_get_short(int *short_data, u8 tx, u8 rx);
+extern int fts_test_get_noise(int *noise, u8 tx, u8 rx);
+extern int fts_test_get_panel_differ(int *panel_differ, u8 tx, u8 rx);
+
+/* Rawdata test */
+static int proc_test_raw_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *raw = NULL;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+    /* get Tx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    /* get Rx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    raw = fts_malloc(node_num * sizeof(int));
+    if (!raw) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_raw(raw, tx, rx);
+
+    /* output raw data */
+    seq_printf(s, "     ");
+    for (i = 0; i < rx; i++)
+        seq_printf(s, " RX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        if ((i % rx) == 0)
+            seq_printf(s, "\nTX%02d:%5d,", (i / rx  + 1), raw[i]);
+        else
+            seq_printf(s, "%5d,", raw[i]);
+    }
+
+    seq_printf(s, "\n\n");
+
+exit:
+    if (raw)
+        fts_free(raw);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_raw_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_raw_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_raw_fops = {
+    .proc_open   = proc_test_raw_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_raw_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_raw_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Baseline test */
+static int proc_test_baseline_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *raw = NULL;
+    int *base_raw = NULL;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    raw = fts_malloc(node_num * sizeof(int));
+    if (!raw) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    base_raw = fts_malloc(node_num * sizeof(int));
+    if (!base_raw) {
+        FTS_ERROR("malloc memory for base_raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get baseline data */
+    fts_test_get_baseline(raw, base_raw, tx, rx);
+
+    /* output baseline data */
+    seq_printf(s, "     ");
+    for (i = 0; i < rx; i++)
+        seq_printf(s, " RX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        if ((i % rx) == 0)
+            seq_printf(s, "\nTX%02d:%5d,", (i / rx  + 1), (raw[i]-base_raw[i]));
+        else
+            seq_printf(s, "%5d,", (raw[i]-base_raw[i]));
+    }
+
+    seq_printf(s, "\n\n");
+
+exit:
+    if (base_raw)
+        fts_free(base_raw);
+
+    if (raw)
+        fts_free(raw);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return 0;
+}
+
+static int proc_test_baseline_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_baseline_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_baseline_fops = {
+    .proc_open   = proc_test_baseline_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_baseline_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_baseline_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Strength test for full size */
+/* Transpose raw */
+void transpose_raw(u8 *src, u8 *dist, int tx, int rx, bool big_endian) {
+    int i = 0;
+    int j = 0;
+
+    for (i = 0; i < tx; i++) {
+        for (j = 0; j < rx; j++) {
+            if (big_endian) {
+                /* keep big_endian. */
+                dist[(j * tx + i) * 2] = src[(i * rx + j) * 2];
+                dist[(j * tx + i) * 2 + 1] = src[(i * rx + j) * 2 + 1];
+           } else {
+                /* transfer to big_endian. */
+                dist[(j * tx + i) * 2] = src[(i * rx + j) * 2 + 1];
+                dist[(j * tx + i) * 2 + 1] = src[(i * rx + j) * 2];
+           }
+        }
+    }
+}
+
+static int proc_test_strength_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    int self_node = 0;
+    u8 tx = ts_data->pdata->tx_ch_num;
+    u8 rx = ts_data->pdata->rx_ch_num;
+    /* The format of uncompressed heatmap from touch chip.
+     *
+     * |- cap header (91) -|- Water-SS -|- Normal-SS -|- Normal-MS -|
+     * |-        91       -|-   68*2   -|-   68*2    -|-  16*34*2  -|
+     */
+    int ss_cap_on_idx = FTS_CAP_DATA_LEN;
+    int ss_cap_off_idx = ss_cap_on_idx + FTS_SELF_DATA_LEN * sizeof(u16);
+    int ms_cap_idx = ss_cap_off_idx + FTS_SELF_DATA_LEN * sizeof(u16);
+    short base_result = 0;
+
+    u8 *base_raw = NULL;
+    u8 *trans_raw = NULL;
+    int base_raw_size = 0;
+    int base = 0;
+    u8 tp_finger_cnt = 0;
+    int tp_events_x = 0;
+    int tp_events_y = 0;
+    u8 tp_events_id = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_work_mode();
+    if (ret < 0) {
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    self_node = tx + rx;
+
+    base_raw_size = FTS_FULL_TOUCH_RAW_SIZE(tx, rx);
+    FTS_DEBUG("base_raw size = %d", base_raw_size);
+    base_raw = fts_malloc(base_raw_size);
+    if (!base_raw) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    trans_raw = fts_malloc(node_num * sizeof(u16));
+    if (!trans_raw) {
+        FTS_ERROR("malloc memory for transpose raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get strength data. */
+    ret = fts_test_get_strength(base_raw, base_raw_size);
+    if (ret < 0) {
+        FTS_ERROR("get strength fails");
+        goto exit;
+    }
+
+    tp_finger_cnt = base_raw[1];
+    if (tp_finger_cnt > FTS_MAX_POINTS_SUPPORT) {
+        FTS_ERROR("The finger count(%d) is over than max fingers(%d)",
+            tp_finger_cnt, FTS_MAX_POINTS_SUPPORT);
+        tp_finger_cnt = FTS_MAX_POINTS_SUPPORT;
+    }
+
+    /*---------Output touch point-----------*/
+    for (i = 0; i < tp_finger_cnt; i++) {
+         base = FTS_ONE_TCH_LEN * i;
+         tp_events_x = ((base_raw[2 + base] & 0x0F) << 8) +
+                       (base_raw[3 + base] & 0xFF);
+         tp_events_y = ((base_raw[4 + base] & 0x0F) << 8) +
+                       (base_raw[5 + base] & 0xFF);
+         tp_events_id = (base_raw[4 + base] & 0xF0) >> 4;
+         seq_printf(s, "Finger ID = %d, x = %d, y = %d\n", tp_events_id,
+                    tp_events_x, tp_events_y);
+    }
+
+    seq_printf(s, "     ");
+    /* transpose data buffer. */
+    FTS_DEBUG("index of MS = %d", ms_cap_idx);
+    transpose_raw(base_raw + ms_cap_idx, trans_raw, tx, rx, true);
+    for (i = 0; i < tx; i++)
+        seq_printf(s, " TX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        base_result = (int)(trans_raw[(i * 2)] << 8) +
+                      (int)trans_raw[(i * 2) + 1];
+        if ((i % tx) == 0)
+            seq_printf(s, "\nRX%02d:%5d,", (i / tx + 1), base_result);
+        else
+            seq_printf(s, "%5d,", base_result);
+    }
+    /*---------END touch point-----------*/
+
+    /*---------output self of strength data-----------*/
+    seq_printf(s, "\n");
+    seq_printf(s, "Scap raw(proof on):\n");
+    FTS_DEBUG("index of SS_ON = %d", ss_cap_on_idx);
+    for (i = 0; i < self_node; i++) {
+        base_result = (int)(base_raw[(i * 2) + ss_cap_on_idx] << 8) +
+                      (int)base_raw[(i * 2) + ss_cap_on_idx + 1];
+
+        if (i == 0)
+            seq_printf(s, "RX:");
+
+        if (i == rx) {
+            FTS_DEBUG("index(tx) = %d", (ss_cap_on_idx + (i * 2)));
+            seq_printf(s, "\n");
+            seq_printf(s, "TX:");
+        }
+        seq_printf(s, "%d,", base_result);
+    }
+    seq_printf(s, "\n\n");
+    seq_printf(s, "Scap raw(proof off):\n");
+    FTS_DEBUG("index of SS_OFF = %d", ss_cap_off_idx);
+    for (i = 0; i < self_node; i++) {
+        base_result = (int)(base_raw[(i * 2) + ss_cap_off_idx] << 8) +
+                      (int)base_raw[(i * 2) + ss_cap_off_idx + 1];
+
+        if (i == 0)
+            seq_printf(s, "RX:");
+
+        if (i == rx){
+            seq_printf(s, "\n");
+            seq_printf(s, "TX:");
+        }
+        seq_printf(s, "%d,", base_result);
+    }
+
+    seq_printf(s, "\n\n");
+    /*---------END self of strength data-----------*/
+
+exit:
+    if (trans_raw)
+        fts_free(trans_raw);
+
+    if (base_raw)
+        fts_free(base_raw);
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_strength_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_strength_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_strength_fops = {
+    .proc_open    = proc_test_strength_open,
+    .proc_read    = seq_read,
+    .proc_lseek   = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_strength_fops = {
+    .owner   = THIS_MODULE,
+    .open    = proc_test_strength_open,
+    .read    = seq_read,
+    .llseek  = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Rawdata_Uniformity test */
+static int proc_test_uniformity_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *uniformity = NULL;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    uniformity = fts_malloc(node_num * 2 * sizeof(int));
+    if (!uniformity) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_uniformity_data(uniformity, tx, rx);
+
+    /* output raw data */
+    seq_printf(s, "Rawdata Uniformity TX:\n");
+    for (i = 0; i < node_num; i++) {
+        if ((i + 1) % rx)
+            seq_printf(s, "%d,", uniformity[i]);
+        else
+            seq_printf(s, "%d,\n", uniformity[i]);
+    }
+
+    seq_printf(s, "Rawdata Uniformity RX:\n");
+    for (i = 0; i < node_num; i++) {
+        if ((i + 1) % rx)
+            seq_printf(s, "%d,", uniformity[node_num + i]);
+        else
+            seq_printf(s, "%d,\n", uniformity[node_num + i]);
+    }
+
+exit:
+    if (uniformity)
+        fts_free(uniformity);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return 0;
+}
+
+static int proc_test_uniformity_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_uniformity_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_uniformity_fops = {
+    .proc_open   = proc_test_uniformity_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_uniformity_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_uniformity_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Scap Rawdata test */
+static int proc_test_sraw_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    int *sraw = NULL;
+    int fwcheck = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    sraw = fts_malloc(node_num * 3 * sizeof(int));
+    if (!sraw) {
+        FTS_ERROR("malloc memory for sraw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_scap_raw(sraw, tx, rx, &fwcheck);
+    seq_printf(s, "Scap raw checked:%X\n", fwcheck);
+
+    /* output raw data */
+    if ((fwcheck & 0x01) || (fwcheck & 0x02)) {
+        seq_printf(s, "Scap raw(proof on):\n");
+        seq_printf(s, "RX:");
+        for (i = 0; i < rx; i++) {
+            seq_printf(s, "%d,", sraw[i]);
+        }
+        seq_printf(s, "\n");
+
+        seq_printf(s, "TX:");
+        for (i = rx; i < node_num; i++) {
+            seq_printf(s, "%d,", sraw[i]);
+        }
+        seq_printf(s, "\n");
+    }
+
+    if ((fwcheck & 0x04) || (fwcheck & 0x08)) {
+        seq_printf(s, "Scap raw(proof off):\n");
+        seq_printf(s, "RX:");
+        for (i = node_num; i < node_num + rx; i++) {
+            seq_printf(s, "%d,", sraw[i]);
+        }
+        seq_printf(s, "\n");
+
+        seq_printf(s, "TX:");
+        for (i = node_num + rx; i < node_num * 2; i++) {
+            seq_printf(s, "%d,", sraw[i]);
+        }
+        seq_printf(s, "\n");
+    }
+
+    if ((fwcheck & 0x10) || (fwcheck & 0x20)) {
+        seq_printf(s, "Scap raw(high):\n");
+        seq_printf(s, "RX:");
+        for (i = node_num * 2; i < node_num * 2 + rx; i++) {
+            seq_printf(s, "%d,", sraw[i]);
+        }
+        seq_printf(s, "\n");
+
+        seq_printf(s, "TX:");
+        for (i = node_num * 2 + rx; i < node_num * 3; i++) {
+            seq_printf(s, "%d,", sraw[i]);
+        }
+        seq_printf(s, "\n");
+    }
+
+exit:
+    if (sraw)
+        fts_free(sraw);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_sraw_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_sraw_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_sraw_fops = {
+    .proc_open   = proc_test_sraw_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_sraw_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_sraw_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Scap CB test */
+static int proc_test_scb_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    int *scb = NULL;
+    int fwcheck = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    scb = fts_malloc(node_num * 3 * sizeof(int));
+    if (!scb) {
+        FTS_ERROR("malloc memory for scb fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_scap_cb(scb, tx, rx, &fwcheck);
+    seq_printf(s, "Scap cb checked:%X\n", fwcheck);
+
+    /* output raw data */
+    if ((fwcheck & 0x01) || (fwcheck & 0x02)) {
+        seq_printf(s, "Scap raw(proof on):\n");
+        seq_printf(s, "RX:");
+        for (i = 0; i < rx; i++) {
+            seq_printf(s, "%d,", scb[i]);
+        }
+        seq_printf(s, "\n");
+
+        seq_printf(s, "TX:");
+        for (i = rx; i < node_num; i++) {
+            seq_printf(s, "%d,", scb[i]);
+        }
+        seq_printf(s, "\n");
+    }
+
+    if ((fwcheck & 0x04) || (fwcheck & 0x08)) {
+        seq_printf(s, "Scap raw(proof off):\n");
+        seq_printf(s, "RX:");
+        for (i = node_num; i < node_num + rx; i++) {
+            seq_printf(s, "%d,", scb[i]);
+        }
+        seq_printf(s, "\n");
+
+        seq_printf(s, "TX:");
+        for (i = node_num + rx; i < node_num * 2; i++) {
+            seq_printf(s, "%d,", scb[i]);
+        }
+        seq_printf(s, "\n");
+    }
+
+    if ((fwcheck & 0x10) || (fwcheck & 0x20)) {
+        seq_printf(s, "Scap raw(high):\n");
+
+        seq_printf(s, "RX:");
+        for (i = node_num * 2; i < node_num * 2 + rx; i++) {
+            seq_printf(s, "%d,", scb[i]);
+        }
+        seq_printf(s, "\n");
+
+        seq_printf(s, "TX:");
+        for (i = node_num * 2 + rx; i < node_num * 3; i++) {
+            seq_printf(s, "%d,", scb[i]);
+        }
+        seq_printf(s, "\n");
+    }
+
+exit:
+    if (scb)
+        fts_free(scb);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_scb_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_scb_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_scb_fops = {
+    .proc_open   = proc_test_scb_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_scb_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_scb_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Noise test */
+static int proc_test_noise_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *noise = NULL;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    noise = fts_malloc(node_num * sizeof(int));
+    if (!noise) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /*get raw data*/
+    fts_test_get_noise(noise, tx, rx);
+
+    /*output raw data*/
+    seq_printf(s, "     ");
+    for (i = 0; i < rx; i++)
+        seq_printf(s, " RX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        if ((i % rx) == 0)
+            seq_printf(s, "\nTX%02d:%5d,", (i / rx + 1), noise[i]);
+        else
+            seq_printf(s, "%5d,", noise[i]);
+    }
+
+    seq_printf(s, "\n\n");
+
+exit:
+    if (noise)
+        fts_free(noise);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_noise_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_noise_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_noise_fops = {
+    .proc_open   = proc_test_noise_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_noise_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_noise_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Short test */
+static int proc_test_short_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *short_data = NULL;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+    /* get Tx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    /* get Rx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    short_data = fts_malloc(node_num * sizeof(int));
+    if (!short_data) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_short(short_data, tx, rx);
+
+    /* output short data */
+    seq_printf(s, "TX:");
+    for (i = 0; i < tx; i++) {
+        seq_printf(s, "%d,", short_data[i]);
+    }
+    seq_printf(s, "\n");
+
+    seq_printf(s, "RX:");
+    for (i = tx; i < node_num; i++) {
+        seq_printf(s, "%d,", short_data[i]);
+    }
+    seq_printf(s, "\n\n");
+
+exit:
+    if (short_data)
+        fts_free(short_data);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_short_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_short_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_short_fops = {
+    .proc_open   = proc_test_short_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_short_fops = {
+    .open   = proc_test_short_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Panel_Differ test */
+static int proc_test_panel_differ_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *panel_differ = NULL;
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, true);
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    panel_differ = fts_malloc(node_num * sizeof(int));
+    if (!panel_differ) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /*get panel_differ data*/
+    fts_test_get_panel_differ(panel_differ, tx, rx);
+
+    /*output panel_differ data*/
+    seq_printf(s, "     ");
+    for (i = 0; i < rx; i++)
+        seq_printf(s, " RX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        if ((i % rx) == 0)
+            seq_printf(s, "\nTX%02d:%5d,", (i / rx + 1), panel_differ[i]);
+        else
+            seq_printf(s, "%5d,", panel_differ[i]);
+    }
+
+    seq_printf(s, "\n\n");
+
+exit:
+    if (panel_differ)
+        fts_free(panel_differ);
+
+    enter_work_mode();
+
+    fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_SYSFS, false);
+    return ret;
+}
+
+static int proc_test_panel_differ_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_panel_differ_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_panel_differ_fops = {
+    .proc_open   = proc_test_panel_differ_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_panel_differ_fops = {
+    .open   = proc_test_panel_differ_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+#define FTS_PROC_TEST_DIR       "selftest"
+
+struct proc_dir_entry *fts_proc_test_dir;
+struct proc_dir_entry *proc_run_os_test;
+struct proc_dir_entry *proc_test_fwver;
+struct proc_dir_entry *proc_test_chnum;
+struct proc_dir_entry *proc_test_reset_pin;
+struct proc_dir_entry *proc_test_sw_reset;
+
+struct proc_dir_entry *proc_test_int_pin;
+struct proc_dir_entry *proc_test_raw;
+struct proc_dir_entry *proc_test_baseline;
+struct proc_dir_entry *proc_test_strength;
+struct proc_dir_entry *proc_test_uniformity;
+struct proc_dir_entry *proc_test_sraw;
+struct proc_dir_entry *proc_test_scb;
+struct proc_dir_entry *proc_test_noise;
+struct proc_dir_entry *proc_test_short;
+struct proc_dir_entry *proc_test_panel_differ;
+
+static int fts_create_test_procs(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    proc_run_os_test = proc_create_data("run_os_test", S_IRUSR,
+        fts_proc_test_dir, &proc_run_os_test_fops, fts_ftest);
+    if (!proc_run_os_test) {
+        FTS_ERROR("create proc_run_os_test entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_fwver = proc_create("FW_Version", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_fwver_fops);
+    if (!proc_test_fwver) {
+        FTS_ERROR("create proc_test_fwver entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_chnum = proc_create("Channel_Num", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_chnum_fops);
+    if (!proc_test_chnum) {
+        FTS_ERROR("create proc_test_chnum entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_reset_pin = proc_create("Reset_Pin", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_hw_reset_fops);
+    if (!proc_test_reset_pin) {
+        FTS_ERROR("create proc_test_reset_pin entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_sw_reset = proc_create("SW_Reset", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_sw_reset_fops);
+    if (!proc_test_sw_reset) {
+        FTS_ERROR("create proc_test_sw_reset entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_int_pin = proc_create("INT_PIN", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_int_fops);
+    if (!proc_test_int_pin) {
+        FTS_ERROR("create proc_test_int_pin entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_raw = proc_create_data("Rawdata", S_IRUSR,
+        fts_proc_test_dir, &proc_test_raw_fops, ts_data);
+    if (!proc_test_raw) {
+        FTS_ERROR("create proc_test_raw entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_baseline = proc_create_data("Baseline", S_IRUSR,
+        fts_proc_test_dir, &proc_test_baseline_fops, ts_data);
+    if (!proc_test_baseline) {
+        FTS_ERROR("create proc_test_baseline entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_strength = proc_create_data("Strength", S_IRUSR,
+        fts_proc_test_dir, &proc_test_strength_fops, ts_data);
+    if (!proc_test_strength) {
+        FTS_ERROR("create proc_test_strength entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_uniformity = proc_create_data("Rawdata_Uniformity", S_IRUSR,
+        fts_proc_test_dir, &proc_test_uniformity_fops, ts_data);
+    if (!proc_test_uniformity) {
+        FTS_ERROR("create proc_test_uniformity entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_sraw = proc_create_data("Scap_Rawdata", S_IRUSR,
+        fts_proc_test_dir, &proc_test_sraw_fops, ts_data);
+    if (!proc_test_sraw) {
+        FTS_ERROR("create proc_test_sraw entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_scb = proc_create_data("Scap_CB", S_IRUSR,
+        fts_proc_test_dir, &proc_test_scb_fops, ts_data);
+    if (!proc_test_scb) {
+        FTS_ERROR("create proc_test_scb entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_noise = proc_create_data("Noise", S_IRUSR,
+        fts_proc_test_dir, &proc_test_noise_fops, ts_data);
+    if (!proc_test_noise) {
+        FTS_ERROR("create proc_test_noise entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_short = proc_create_data("Short", S_IRUSR,
+        fts_proc_test_dir, &proc_test_short_fops, ts_data);
+    if (!proc_test_short) {
+        FTS_ERROR("create proc_test_short entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_panel_differ = proc_create_data("Panel_Differ", S_IRUSR,
+        fts_proc_test_dir, &proc_test_panel_differ_fops, ts_data);
+    if (!proc_test_panel_differ) {
+        FTS_ERROR("create proc_test_panel_differ entry fail");
+        return -ENOMEM;
+    }
+
+    FTS_INFO("create test procs succeeds");
+    return ret;
+}
+
+int fts_test_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    FTS_TEST_FUNC_ENTER();
+    /* get test function, must be the first step */
+    ret = fts_test_func_init(ts_data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("test functions init fail");
+        return ret;
+    }
+
+    ret = sysfs_create_group(&ts_data->dev->kobj, &fts_test_attribute_group);
+    if (0 != ret) {
+        FTS_TEST_ERROR("sysfs(test) create fail");
+        sysfs_remove_group(&ts_data->dev->kobj, &fts_test_attribute_group);
+    } else {
+        FTS_TEST_DBG("sysfs(test) create successfully");
+    }
+
+    fts_proc_test_dir = proc_mkdir(FTS_PROC_TEST_DIR,
+        ts_data->proc_touch_entry);
+    if (!fts_proc_test_dir) {
+        FTS_ERROR("create %s fails", FTS_PROC_TEST_DIR);
+        return -ENOMEM;
+    }
+
+    ret = fts_create_test_procs(ts_data);
+    if (ret) {
+        FTS_TEST_ERROR("create test procs fail");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+
+    return ret;
+}
+
+int fts_test_exit(struct fts_ts_data *ts_data)
+{
+    FTS_TEST_FUNC_ENTER();
+
+    if (fts_proc_test_dir)
+        proc_remove(fts_proc_test_dir);
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_test_attribute_group);
+    fts_free(fts_ftest);
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
diff --git a/ft3658/focaltech_test/focaltech_test.h b/ft3658/focaltech_test/focaltech_test.h
new file mode 100644
index 0000000..0977fd3
--- /dev/null
+++ b/ft3658/focaltech_test/focaltech_test.h
@@ -0,0 +1,664 @@
+/************************************************************************
+* Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+*
+* File Name: focaltech_test.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Abstract: test entry for all IC
+*
+************************************************************************/
+#ifndef _TEST_LIB_H
+#define _TEST_LIB_H
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <asm/uaccess.h>
+#include <linux/i2c.h>//iic
+#include <linux/delay.h>//msleep
+#include <linux/string.h>
+#include <asm/unistd.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include "../focaltech_core.h"
+#include "focaltech_test_ini.h"
+
+/*****************************************************************************
+* Macro definitions using #define
+*****************************************************************************/
+#define FTS_INI_FILE_PATH                       "/mnt/sdcard/"
+#define FTS_CSV_FILE_NAME                       "testdata.csv"
+#define FTS_TXT_FILE_NAME                       "testresult.txt"
+#define false 0
+#define true  1
+#define TEST_ICSERIES_LEN                       (8)
+#define TEST_ICSERIES(x)                        ((x) >> TEST_ICSERIES_LEN)
+
+#define TEST_OPEN_MAX_VALUE                     (255)
+#define BYTES_PER_TIME                          (32)  /* max:128 */
+/* CSV & TXT */
+#define CSV_LINE2_BUFFER_LEN                    (1024)
+#define CSV_BUFFER_LEN                          (1024*80*5)
+#define TXT_BUFFER_LEN                          (1024*80*5)
+
+#define TEST_SAVE_FAIL_RESULT                   0
+
+/*-----------------------------------------------------------
+Test Status
+-----------------------------------------------------------*/
+#define RESULT_NULL                             0
+#define RESULT_PASS                             1
+#define RESULT_NG                               2
+
+#define TX_NUM_MAX                              60
+#define RX_NUM_MAX                              100
+#define SC_NUM_MAX                  ((TX_NUM_MAX) + (RX_NUM_MAX))
+#define NUM_MAX_SC                              (144)
+#define KEY_NUM_MAX                             6
+#define TEST_ITEM_COUNT_MAX                     32
+#define TEST_ITEM_NAME_MAX                      32
+#define TEST_SHORT_RES_MAX                      0xFFFF
+
+/*
+ * factory test registers
+ */
+#define ENTER_WORK_FACTORY_RETRIES              5
+
+#define START_SCAN_RETRIES_INCELL               20
+#define START_SCAN_RETRIES_DELAY_INCELL         16
+#define FACTORY_TEST_RETRY                      50
+#define FACTORY_TEST_DELAY                      18
+#define FACTORY_TEST_RETRY_DELAY                100
+
+#define DIVIDE_MODE_ADDR                        0x00
+#define REG_FW_MAJOR_VER                        0xA6
+#define REG_FW_MINOR_VER                        0xAD
+#define REG_VA_TOUCH_THR                        0x80
+#define REG_VKEY_TOUCH_THR                      0x82
+
+#define FACTORY_REG_LINE_ADDR                   0x01
+#define FACTORY_REG_CHX_NUM                     0x02
+#define FACTORY_REG_CHY_NUM                     0x03
+#define FACTORY_REG_CLB                         0x04
+#define FACTORY_REG_DATA_SELECT                 0x06
+#define FACTORY_REG_RAWBUF_SELECT               0x09
+#define FACTORY_REG_KEY_CBWIDTH                 0x0B
+#define FACTORY_REG_PARAM_UPDATE_STATE          0x0E
+#define FACTORY_REG_PARAM_UPDATE_STATE_TOUCH    0xB5
+#define FACTORY_REG_SHORT_TEST_EN               0x0F
+#define FACTORY_REG_SHORT_TEST_STATE            0x10
+#define FACTORY_REG_LCD_NOISE_START             0x11
+#define FACTORY_REG_LCD_NOISE_FRAME             0x12
+#define FACTORY_REG_LCD_NOISE_TEST_STATE        0x13
+#define FACTORY_REG_LCD_NOISE_TTHR              0x14
+#define FACTORY_REG_OPEN_START                  0x15
+#define FACTORY_REG_OPEN_STATE                  0x16
+#define FACTORY_REG_OPEN_ADDR                   0xCF
+#define FACTORY_REG_OPEN_IDLE                   0x03
+#define FACTORY_REG_OPEN_BUSY                   0x01
+#define FACTORY_REG_CB_ADDR_H                   0x18
+#define FACTORY_REG_CB_ADDR_L                   0x19
+#define FACTORY_REG_ORDER_ADDR_H                0x1A
+#define FACTORY_REG_ORDER_ADDR_L                0x1B
+#define FACTORY_REG_LCD_NOISE_STATE             0x1E
+#define FACTORY_REG_KEYSHORT_EN                 0x2E
+#define FACTORY_REG_KEYSHORT_STATE              0x2F
+
+#define FACTORY_REG_LEFT_KEY                    0x1E
+#define FACTORY_REG_RIGHT_KEY                   0x1F
+#define FACTORY_REG_OPEN_REG20                  0x20
+#define FACTORY_REG_OPEN_REG21                  0x21
+#define FACTORY_REG_OPEN_REG22                  0x22
+#define FACTORY_REG_OPEN_REG23                  0x23
+#define FACTORY_REG_OPEN_REG2E                  0x2E
+#define FACTORY_REG_OPEN_REG86                  0x86
+#define FACTORY_REG_K1                          0x31
+#define FACTORY_REG_K2                          0x32
+#define FACTORY_REG_RAWDATA_ADDR                0x6A
+#define FACTORY_REG_ORDER_ADDR                  0x6C
+#define FACTORY_REG_CB_ADDR                     0x6E
+#define FACTORY_REG_SHORT_ADDR                  0x89
+#define FACTORY_REG_RAWDATA_TEST_EN             0x9E
+#define FACTORY_REG_CB_TEST_EN                  0x9F
+#define FACTORY_REG_OPEN_TEST_EN                0xA0
+#define FACTORY_REG_RAWDATA_TARGET              0xCA
+
+
+/* mc_sc */
+#define FACTORY_REG_FRE_LIST                    0x0A
+#define FACTORY_REG_DATA_TYPE                   0x5B
+#define FACTORY_REG_NORMALIZE                   0x16
+#define FACTORY_REG_RAWDATA_ADDR_MC_SC          0x36
+#define FACTORY_REG_PATTERN                     0x53
+#define FACTORY_REG_NOMAPPING                   0x54
+#define FACTORY_REG_CHX_NUM_NOMAP               0x55
+#define FACTORY_REG_CHY_NUM_NOMAP               0x56
+#define FACTORY_REG_WC_SEL                      0x09
+#define FACTORY_REG_HC_SEL                      0x0F
+#define FACTORY_REG_MC_SC_MODE                  0x44
+#define FACTORY_REG_MC_SC_CB_ADDR_OFF           0x45
+#define FACTORY_REG_MC_SC_CB_H_ADDR_OFF         0x49
+#define FACTORY_REG_MC_SC_CB_ADDR               0x4E
+#define FACTROY_REG_SHORT_TEST_EN               0x07
+#define FACTROY_REG_SHORT_CA                    0x01
+#define FACTROY_REG_SHORT_CC                    0x02
+#define FACTROY_REG_SHORT_CG                    0x03
+#define FACTROY_REG_SHORT_OFFSET                0x04
+#define FACTROY_REG_SHORT_AB_CH                 0x58
+#define FACTROY_REG_SHORT_RES_LEVEL             0x5A
+#define FACTORY_REG_SHORT_ADDR_MC               0xF4
+#define FACTORY_REG_FIR                         0xFB
+
+/* noise */
+#define FACTORY_REG_MAXDIFF_EN                  0x1A
+#define FACTORY_REG_MAXDIFF_FLAG                0x1B
+#define FACTORY_REG_FRAME_NUM_H                 0x1C
+#define FACTORY_REG_FRAME_NUM_L                 0x1D
+#define FACTORY_REG_NOISE_ADDR                  0xCE
+
+#define FACTROY_REG_SHORT2_TEST_EN              0xC0
+#define FACTROY_REG_SHORT2_CA                   0x01
+#define FACTROY_REG_SHORT2_CC                   0x02
+#define FACTROY_REG_SHORT2_CG                   0x03
+#define FACTROY_REG_SHORT2_OFFSET               0x04
+#define FACTROY_REG_SHORT2_RES_LEVEL            0xC1
+#define FACTROY_REG_SHORT2_DEALY                0xC2
+#define FACTROY_REG_SHORT2_TEST_STATE           0xC3
+#define FACTORY_REG_SHORT2_ADDR_MC              0xC4
+#define FACTROY_REG_SHORT2_AB_CH                0xC6
+
+/* sc */
+#define FACTORY_REG_SCAN_ADDR2                  0x08
+#define FACTORY_REG_CH_NUM_SC                   0x0A
+#define FACTORY_REG_KEY_NUM_SC                  0x0B
+#define FACTORY_REG_SC_CB_ADDR_OFF              0x33
+#define FACTORY_REG_SC_CB_ADDR                  0x39
+#define FACTORY_REG_RAWDATA_SADDR_SC            0x34
+#define FACTORY_REG_RAWDATA_ADDR_SC             0x35
+#define FACTORY_REG_CB_SEL                      0x41
+#define FACTORY_REG_FMODE                       0xAE
+
+#define TEST_RETVAL_00                          0x00
+#define TEST_RETVAL_AA                          0xAA
+
+#define FTS_MAX_SORT_SC                         32768
+#define FTS_MIN_SORT_SC                         0
+
+#define FTS_TMP_REG_88                          0x88    //for Register R/W test
+#define FTS_TMP_REG_SOFT_RESET                  0xFC
+
+/*****************************************************************************
+* enumerations, structures and unions
+*****************************************************************************/
+struct item_info {
+    char name[TEST_ITEM_NAME_MAX];
+    int code;
+    int *data;
+    int datalen;
+    int result;
+    int mc_sc;
+    int key_support;
+};
+
+struct fts_test_data {
+    int item_count;
+    struct item_info info[TEST_ITEM_COUNT_MAX];
+};
+
+/* incell */
+struct incell_testitem {
+    u32 short_test                  : 1;
+    u32 open_test                   : 1;
+    u32 cb_test                     : 1;
+    u32 rawdata_test                : 1;
+    u32 lcdnoise_test               : 1;
+    u32 keyshort_test               : 1;
+    u32 mux_open_test               : 1;
+};
+
+struct incell_threshold_b {
+    int short_res_min;
+    int short_res_vk_min;
+    int open_cb_min;
+    int open_k1_check;
+    int open_k1_value;
+    int open_k2_check;
+    int open_k2_value;
+    int cb_min;
+    int cb_max;
+    int cb_vkey_check;
+    int cb_min_vk;
+    int cb_max_vk;
+    int rawdata_min;
+    int rawdata_max;
+    int rawdata_vkey_check;
+    int rawdata_min_vk;
+    int rawdata_max_vk;
+    int lcdnoise_frame;
+    int lcdnoise_coefficient;
+    int lcdnoise_coefficient_vkey;
+    int open_diff_min;
+    int open_nmos;
+    int keyshort_k1;
+    int keyshort_cb_max;
+    int rawdata2_min;
+    int rawdata2_max;
+    int mux_open_cb_min;
+    int open_delta_V;
+};
+
+struct incell_threshold {
+    struct incell_threshold_b basic;
+    int *rawdata_min;
+    int *rawdata_max;
+    int *rawdata2_min;
+    int *rawdata2_max;
+    int *cb_min;
+    int *cb_max;
+};
+
+struct incell_test {
+    struct incell_threshold thr;
+    union {
+        int tmp;
+        struct incell_testitem item;
+    } u;
+};
+
+/* mc_sc */
+enum mapping_type {
+    MAPPING = 0,
+    NO_MAPPING = 1,
+};
+
+struct mc_sc_testitem {
+    u32 rawdata_test                : 1;
+    u32 rawdata_uniformity_test     : 1;
+    u32 scap_cb_test                : 1;
+    u32 scap_rawdata_test           : 1;
+    u32 short_test                  : 1;
+    u32 panel_differ_test           : 1;
+};
+
+struct mc_sc_threshold_b {
+    int rawdata_h_min;
+    int rawdata_h_max;
+    int rawdata_set_hfreq;
+    int rawdata_l_min;
+    int rawdata_l_max;
+    int rawdata_set_lfreq;
+    int uniformity_check_tx;
+    int uniformity_check_rx;
+    int uniformity_check_min_max;
+    int uniformity_tx_hole;
+    int uniformity_rx_hole;
+    int uniformity_min_max_hole;
+    int scap_cb_off_min;
+    int scap_cb_off_max;
+    int scap_cb_wp_off_check;
+    int scap_cb_on_min;
+    int scap_cb_on_max;
+    int scap_cb_wp_on_check;
+    int scap_rawdata_off_min;
+    int scap_rawdata_off_max;
+    int scap_rawdata_wp_off_check;
+    int scap_rawdata_on_min;
+    int scap_rawdata_on_max;
+    int scap_rawdata_wp_on_check;
+    int short_cg;
+    int short_cc;
+    int panel_differ_min;
+    int panel_differ_max;
+    int scap_cb_hi_min;
+    int scap_cb_hi_max;
+    int scap_cb_hi_check;
+    int scap_rawdata_hi_min;
+    int scap_rawdata_hi_max;
+    int scap_rawdata_hi_check;
+    int scap_cb_hov_min;
+    int scap_cb_hov_max;
+    int scap_cb_hov_check;
+    int scap_rawdata_hov_min;
+    int scap_rawdata_hov_max;
+    int scap_rawdata_hov_check;
+};
+
+struct mc_sc_threshold {
+    struct mc_sc_threshold_b basic;
+    int *rawdata_h_min;
+    int *rawdata_h_max;
+    int *rawdata_l_min;
+    int *rawdata_l_max;
+    int *tx_linearity_max;
+    int *tx_linearity_min;
+    int *rx_linearity_max;
+    int *rx_linearity_min;
+    int *scap_cb_off_min;
+    int *scap_cb_off_max;
+    int *scap_cb_on_min;
+    int *scap_cb_on_max;
+    int *scap_cb_hi_min;
+    int *scap_cb_hi_max;
+    int *scap_cb_hov_min;
+    int *scap_cb_hov_max;
+    int *scap_rawdata_off_min;
+    int *scap_rawdata_off_max;
+    int *scap_rawdata_on_min;
+    int *scap_rawdata_on_max;
+    int *scap_rawdata_hi_min;
+    int *scap_rawdata_hi_max;
+    int *scap_rawdata_hov_min;
+    int *scap_rawdata_hov_max;
+    int *panel_differ_min;
+    int *panel_differ_max;
+};
+
+struct mc_sc_test {
+    struct mc_sc_threshold thr;
+    union {
+        u32 tmp;
+        struct mc_sc_testitem item;
+    } u;
+};
+
+/* sc */
+struct sc_testitem {
+    u32 rawdata_test                : 1;
+    u32 cb_test                     : 1;
+    u32 delta_cb_test               : 1;
+    u32 short_test                  : 1;
+};
+
+struct sc_threshold_b {
+    int rawdata_min;
+    int rawdata_max;
+    int cb_min;
+    int cb_max;
+    int dcb_base;
+    int dcb_differ_max;
+    int dcb_key_check;
+    int dcb_key_differ_max;
+    int dcb_ds1;
+    int dcb_ds2;
+    int dcb_ds3;
+    int dcb_ds4;
+    int dcb_ds5;
+    int dcb_ds6;
+    int dcb_critical_check;
+    int dcb_cs1;
+    int dcb_cs2;
+    int dcb_cs3;
+    int dcb_cs4;
+    int dcb_cs5;
+    int dcb_cs6;
+    int short_min;
+};
+
+struct sc_threshold {
+    struct sc_threshold_b basic;
+    int *rawdata_min;
+    int *rawdata_max;
+    int *cb_min;
+    int *cb_max;
+    int *dcb_sort;
+    int *dcb_base;
+};
+
+struct sc_test {
+    struct sc_threshold thr;
+    union {
+        u32 tmp;
+        struct sc_testitem item;
+    } u;
+};
+
+enum test_hw_type {
+    IC_HW_INCELL = 1,
+    IC_HW_MC_SC,
+    IC_HW_SC,
+};
+
+enum test_scan_mode {
+    SCAN_NORMAL = 0,
+    SCAN_SC,
+};
+
+struct fts_test_node {
+    int channel_num;
+    int tx_num;
+    int rx_num;
+    int node_num;
+    int key_num;
+};
+
+struct fts_test {
+    struct fts_ts_data *ts_data;
+    struct fts_test_node node;
+    struct fts_test_node sc_node;
+    u8 fw_major_ver;
+    u8 fw_minor_ver;
+    u8 va_touch_thr;
+    u8 vk_touch_thr;
+    bool key_support;
+    bool v3_pattern;
+    u8 mapping;
+    u8 normalize;
+    int test_num;
+    int *buffer;
+    int buffer_length;
+    int *node_valid;
+    int *node_valid_sc;
+    int basic_thr_count;
+    int code1;
+    int code2;
+    int offset;
+    union {
+        struct incell_test incell;
+        struct mc_sc_test mc_sc;
+        struct sc_test sc;
+    } ic;
+
+    struct seq_file *s;
+    struct test_funcs *func;
+    struct fts_test_data testdata;
+    char *testresult;
+    int testresult_len;
+    int result;
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+    struct timeval tv;
+#endif
+    struct ini_data ini;
+};
+
+struct test_funcs {
+    u16 ctype[FTS_MAX_COMPATIBLE_TYPE];
+    enum test_hw_type hwtype;
+    int startscan_mode;
+    int key_num_total;
+    bool rawdata2_support;
+    bool force_touch;
+    bool mc_sc_short_v2;
+    bool raw_u16;
+    bool cb_high_support;
+    bool param_update_support;
+    int (*param_init)(void);
+    int (*init)(void);
+    int (*start_test)(void);
+};
+
+enum byte_mode {
+    DATA_ONE_BYTE,
+    DATA_TWO_BYTE,
+};
+/* mc_sc */
+enum normalize_type {
+    NORMALIZE_OVERALL,
+    NORMALIZE_AUTO,
+};
+
+enum wp_type {
+    WATER_PROOF_OFF = 0,
+    WATER_PROOF_ON = 1,
+    HIGH_SENSITIVITY = 2,
+    HOV = 3,
+    WATER_PROOF_ON_TX = 100,
+    WATER_PROOF_ON_RX,
+    WATER_PROOF_OFF_TX,
+    WATER_PROOF_OFF_RX,
+};
+/* mc end */
+
+/* sc */
+enum factory_mode {
+    FACTORY_NORMAL,
+    FACTORY_TESTMODE_1,
+    FACTORY_TESTMODE_2,
+};
+
+enum dcb_sort_num {
+    DCB_SORT_MIN = 1,
+    DCB_SORT_MAX = 6,
+};
+
+struct dcb_sort_d {
+    int ch_num;
+    int deviation;
+    int critical;
+    int min;
+    int max;
+};
+/* sc end */
+
+enum csv_itemcode_incell {
+    CODE_ENTER_FACTORY_MODE = 0,
+    CODE_RAWDATA_TEST = 7,
+    CODE_CB_TEST = 12,
+    CODE_SHORT_TEST = 15,
+    CODE_OPEN_TEST = 25,
+    CODE_LCD_NOISE_TEST = 27,
+    CODE_MUX_OPEN_TEST = 41,
+};
+
+enum csv_itemcode_mc_sc {
+    CODE_M_RAWDATA_TEST = 7,
+    CODE_M_SCAP_CB_TEST = 9,
+    CODE_M_SCAP_RAWDATA_TEST = 10,
+    CODE_M_WEAK_SHORT_CIRCUIT_TEST = 15,
+    CODE_M_RAWDATA_UNIFORMITY_TEST = 16,
+    CODE_M_PANELDIFFER_TEST = 20,
+};
+
+enum csv_itemcode_sc {
+    CODE_S_RAWDATA_TEST = 7,
+    CODE_S_CB_TEST = 13,
+    CODE_S_DCB_TEST = 14,
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+extern struct test_funcs test_func_ft5652;
+
+extern struct fts_test *fts_ftest;
+
+void sys_delay(int ms);
+int fts_abs(int value);
+void print_buffer(int *buffer, int length, int line_num);
+int fts_test_read_reg(u8 addr, u8 *val);
+int fts_test_write_reg(u8 addr, u8 val);
+int fts_test_read(u8 addr, u8 *readbuf, int readlen);
+int fts_test_write(u8 addr, u8 *writebuf, int writelen);
+int enter_work_mode(void);
+int enter_factory_mode(void);
+int read_mass_data(u8 addr, int byte_num, int *buf);
+int chip_clb(void);
+int wait_state_update(u8 retval);
+int get_cb_incell(u16 saddr, int byte_num, int *cb_buf);
+int short_get_adcdata_incell(u8 retval, u8 ch_num, int byte_num, int *adc_buf);
+int start_scan(void);
+int get_rawdata(int *data);
+int get_cb_sc(int byte_num, int *cb_buf, enum byte_mode mode);
+bool compare_data(int *data, int min, int max, int min_vk, int max_vk, bool key);
+bool compare_array(int *data, int *min, int *max, bool key);
+void show_data(int *data, bool key);
+/* mc_sc */
+int mapping_switch(u8 mapping);
+bool get_fw_wp(u8 wp_channel_select, enum wp_type water_proof_type);
+int get_cb_mc_sc(u8 wp, int byte_num, int *cb_buf, enum byte_mode mode);
+int get_rawdata_mc_sc(enum wp_type wp, int *data);
+int get_rawdata_mc(u8 fre, u8 fir, int *rawdata);
+int short_get_adc_data_mc(u8 retval, int byte_num, int *adc_buf, u8 mode);
+bool compare_mc_sc(bool, bool, int *, int *, int *);
+void show_data_mc_sc(int *data);
+void *fts_malloc(size_t size);
+void fts_free_proc(void *p);
+void fts_test_save_data(char *name, int code, int *data, int datacnt,
+                        bool mc_sc, bool key, bool result);
+
+#define fts_malloc_r(p, size) do {\
+    if (NULL == p) {\
+        p = fts_malloc(size);\
+        if (NULL == p) {\
+            return -ENOMEM;\
+        }\
+    }\
+} while(0)
+
+#define fts_free(p) do {\
+    if (p) {\
+        fts_free_proc(p);\
+        p = NULL;\
+    }\
+} while(0)
+
+#define CSV_SUPPORT             0
+#define TXT_SUPPORT             0
+
+#define FTS_TEST_DBG(fmt, args...) do { \
+    printk("[FTS_TS/D][TEST]%s:"fmt"\n",  __func__, ##args); \
+} while (0)
+
+#define FTS_TEST_FUNC_ENTER() do { \
+    printk("[FTS_TS/D][TEST]%s: Enter\n", __func__); \
+} while (0)
+
+#define FTS_TEST_FUNC_EXIT()  do { \
+    printk("[FTS_TS/D][TEST]%s: Exit(%d)\n", __func__, __LINE__); \
+} while (0)
+
+#define FTS_TEST_INFO(fmt, args...) do { \
+    printk("[FTS_TS/I][TEST]%s:"fmt"\n", __func__, ##args); \
+} while (0)
+
+#define FTS_TEST_ERROR(fmt, args...) do { \
+    printk(KERN_ERR "[FTS_TS/E][TEST]%s:"fmt"\n", __func__, ##args); \
+} while (0)
+
+#define FTS_TEST_SAVE_INFO(fmt, args...) do { \
+    if (fts_ftest->testresult) { \
+        fts_ftest->testresult_len += snprintf( \
+        fts_ftest->testresult + fts_ftest->testresult_len, \
+        TXT_BUFFER_LEN, \
+        fmt, ##args);\
+    } \
+    printk("[FTS_TS/I][TEST]%s:"fmt"\n", __func__, ##args);\
+} while (0)
+
+#define FTS_TEST_SAVE_ERR(fmt, args...)  do { \
+    if (fts_ftest->testresult && (fts_ftest->testresult_len < TXT_BUFFER_LEN)) { \
+        fts_ftest->testresult_len += snprintf( \
+        fts_ftest->testresult + fts_ftest->testresult_len, \
+        TXT_BUFFER_LEN, \
+        fmt, ##args);\
+    } \
+    printk(KERN_ERR "[FTS_TS/E][TEST]%s:"fmt"\n", __func__, ##args);\
+} while (0)
+#endif
diff --git a/ft3658/focaltech_test/focaltech_test_ini.c b/ft3658/focaltech_test/focaltech_test_ini.c
new file mode 100644
index 0000000..7c30968
--- /dev/null
+++ b/ft3658/focaltech_test/focaltech_test_ini.c
@@ -0,0 +1,1299 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R)£¬All Rights Reserved.
+*
+* File Name: focaltech_test_ini.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Abstract: parsing function of INI file
+*
+************************************************************************/
+#include "focaltech_test.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_INI_REQUEST_SUPPORT              1
+
+struct ini_ic_type ic_types[] = {
+    {"FT5X46",  0x54000002},
+    {"FT5X46i", 0x54010002},
+    {"FT5526",  0x54020002},
+    {"FT3X17",  0x54030002},
+    {"FT5436",  0x54040002},
+    {"FT3X27",  0x54050002},
+    {"FT5526i", 0x54060002},
+    {"FT5416",  0x54070002},
+    {"FT5426",  0x54080002},
+    {"FT5435",  0x54090002},
+    {"FT7681",  0x540A0002},
+    {"FT7661",  0x540B0002},
+    {"FT7511",  0x540C0002},
+    {"FT7421",  0x540D0002},
+    {"FT7311",  0x54100002},
+
+    {"FT5526_003", 0x40020082},
+    {"FT5426_003", 0x40030082},
+    {"FT3427G_003", 0x40040082},
+    {"FT3427_003", 0x40050082},
+    {"FT5446_003", 0x40000082},
+    {"FT5446_Q03", 0x40000082},
+    {"FT5446_P03", 0x55060081},
+    {"FT5446DQS-W01", 0x40000082},
+
+    {"FT5452",  0x55000081},
+    {"FT3518",  0x55010081},
+    {"FT3558",  0x55020081},
+    {"FT3528",  0x55030081},
+    {"FT5536",  0x55040081},
+    {"FT3418",  0x55070081},
+    {"FT5536L", 0x55080081},
+
+    {"FT5472",  0x8F000083},
+    {"FT5446U", 0x8F010083},
+    {"FT5456U", 0x8F020083},
+    {"FT3417U", 0x8F030083},
+    {"FT5426U", 0x8F040083},
+    {"FT3428",  0x8F050083},
+    {"FT3437U", 0x8F060083},
+
+    {"FT5822",  0x58000001},
+    {"FT5626",  0x58010001},
+    {"FT5726",  0x58020001},
+    {"FT5826B", 0x58030001},
+    {"FT3617",  0x58040001},
+    {"FT3717",  0x58050001},
+    {"FT7811",  0x58060001},
+    {"FT5826S", 0x58070001},
+    {"FT3517U", 0x58090001},
+    {"FT3557",  0x580A0001},
+
+    {"FT6X36",  0x63000003},
+    {"FT3X07",  0x63010003},
+    {"FT6416",  0x63020003},
+    {"FT6336G/U", 0x63030003},
+    {"FT7401",  0x63040003},
+    {"FT3407U", 0x63050003},
+    {"FT6236U", 0x63060003},
+    {"FT6436U", 0x63070003},
+
+    {"FT3267",  0x63080004},
+    {"FT3367",  0x63090004},
+
+    {"FT6216",  0x64000084},
+    {"FT7302",  0x64010084},
+    {"FT7202",  0x64020084},
+    {"FT3308",  0x64030084},
+    {"FT6446",  0x64040084},
+
+    {"FT8607",  0x81000009},
+    {"FT8716",  0x82000005},
+    {"FT8716U", 0x44000005},
+    {"FT8716F", 0x8A000005},
+    {"FT8613",  0x4500000C},
+
+    {"FT8736",  0x83000006},
+
+    {"FT8201",  0x87010010},
+    {"FT7250",  0x8702001A},
+
+    {"FT8006U", 0x8900000B},
+    {"FT8006S", 0x8901000B},
+    {"FT8006S-AA", 0x9B000019},
+    {"FT8016", 0x9B01001D},
+
+    {"FT8719",  0x8E00000D},
+    {"FT8615",  0x9100000F},
+
+    {"FT8739",  0x8D00000E},
+
+    {"FT8006P", 0x93000011},
+    {"FT7120",  0x9E00001B},
+
+    {"FT7251",  0x8C000012},
+    {"FT7252",  0x92000013},
+
+    {"FT8613S", 0x94000014},
+
+    {"FT8756",  0x95000015},
+    {"FT8656",  0x95010018},
+
+    {"FT8302",  0x97000016},
+
+    {"FT8009",  0x98000017},
+
+    {"FT8720",  0x9C00001C},
+
+    {"FT3068",  0x65010085},
+    {"FT3168",  0x65020085},
+    {"FT3067",  0x65030085},
+    {"FT3268",  0x65040085},
+    {"FT6346U", 0x65050085},
+    {"FT6146",  0x65060085},
+    {"FT6346G", 0x65070085},
+
+    {"FT5726_V03", 0x580C0086},
+    {"FT5726_003", 0x580C0086},
+
+    {"FT3618",  0x59010087},
+    {"FT5646",  0x59020087},
+    {"FT3A58",  0x59030087},
+    {"FT3B58",  0x59040087},
+    {"FT3D58",  0x59050087},
+    {"FT5A36",  0x59060087},
+    {"FT5B36",  0x59070087},
+    {"FT5D36",  0x59080087},
+    {"FT5A46",  0x59090087},
+    {"FT5B46",  0x590A0087},
+    {"FT5D46",  0x590B0087},
+    {"FT5936",  0x590C0087},
+    {"FT5946",  0x590D0087},
+
+    {"FT3658U", 0x5A010088},
+
+    {"FT2388",  0x9D00001E},
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+/* Works only for digits and letters, but small and fast */
+#define TOLOWER(x) ((x) | 0x20)
+static int fts_strncmp(const char *cs, const char *ct, int count)
+{
+    u8 c1 = 0, c2 = 0;
+
+    while (count) {
+        if  ((*cs == '\0') || (*ct == '\0'))
+            return -1;
+        c1 = TOLOWER(*cs++);
+        c2 = TOLOWER(*ct++);
+        if (c1 != c2)
+            return c1 < c2 ? -1 : 1;
+        if (!c1)
+            break;
+        count--;
+    }
+
+    return 0;
+}
+
+static int fts_isspace(int x)
+{
+    if (x == ' ' || x == '\t' || x == '\n' || x == '\f' || x == '\b' || x == '\r')
+        return 1;
+    else
+        return 0;
+}
+
+static int fts_isdigit(int x)
+{
+    if (x <= '9' && x >= '0')
+        return 1;
+    else
+        return 0;
+}
+
+static long fts_atol(char *nptr)
+{
+    int c; /* current char */
+    long total; /* current total */
+    int sign; /* if ''-'', then negative, otherwise positive */
+    /* skip whitespace */
+    while ( fts_isspace((int)(unsigned char)*nptr) )
+        ++nptr;
+    c = (int)(unsigned char) * nptr++;
+    sign = c; /* save sign indication */
+    if (c == '-' || c == '+')
+        c = (int)(unsigned char) * nptr++; /* skip sign */
+    total = 0;
+    while (fts_isdigit(c)) {
+        total = 10 * total + (c - '0'); /* accumulate digit */
+        c = (int)(unsigned char) * nptr++; /* get next char */
+    }
+    if (sign == '-')
+        return -total;
+    else
+        return total; /* return result, negated if necessary */
+}
+
+static int fts_atoi(char *nptr)
+{
+    return (int)fts_atol(nptr);
+}
+
+static int fts_test_get_ini_via_request_firmware(struct ini_data *ini, char *fwname)
+{
+    int ret = 0;
+    const struct firmware *fw = NULL;
+    struct device *dev = &fts_data->input_dev->dev;
+
+#if !FTS_INI_REQUEST_SUPPORT
+    return -EINVAL;
+#endif
+
+    ret = request_firmware(&fw, fwname, dev);
+    if (0 == ret) {
+        FTS_TEST_INFO("firmware request(%s) success", fwname);
+        ini->data = vmalloc(fw->size + 1);
+        if (ini->data == NULL) {
+            FTS_TEST_ERROR("ini->data buffer vmalloc fail");
+            ret = -ENOMEM;
+        } else {
+            memcpy(ini->data, fw->data, fw->size);
+            ini->data[fw->size] = '\n';
+            ini->length = fw->size + 1;
+        }
+    } else {
+        FTS_TEST_INFO("firmware request(%s) fail,ret=%d", fwname, ret);
+    }
+
+    if (fw != NULL) {
+        release_firmware(fw);
+        fw = NULL;
+    }
+
+    return ret;
+}
+
+
+static void str_space_remove(char *str)
+{
+    char *t = str;
+    char *s = str;
+
+    while (*t != '\0') {
+        if (*t != ' ') {
+            *s = *t;
+            s++;
+        }
+        t++;
+    }
+
+    *s = '\0';
+}
+
+static void print_ini_data(struct ini_data *ini)
+{
+    int i = 0;
+    int j = 0;
+    struct ini_section *section = NULL;
+    struct ini_keyword *keyword = NULL;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata && tdata->ts_data && (tdata->ts_data->log_level < 10)) {
+        return;
+    }
+
+    if (!ini || !ini->tmp) {
+        FTS_TEST_DBG("ini is null");
+        return;
+    }
+
+    FTS_TEST_DBG("section num:%d, keyword num total:%d",
+                 ini->section_num, ini->keyword_num_total);
+    for (i = 0; i < ini->section_num; i++) {
+        section = &ini->section[i];
+        FTS_TEST_DBG("section name:[%s] keyword num:%d",
+                     section->name, section->keyword_num);
+        for (j = 0; j < section->keyword_num; j++) {
+            keyword = &section->keyword[j];
+            FTS_TEST_DBG("%s=%s", keyword->name, keyword->value);
+        }
+    }
+}
+
+static int ini_get_line(char *filedata, char *line_data, int *line_len)
+{
+    int i = 0;
+    int line_length = 0;
+    int type;
+
+    /* get a line data */
+    for (i = 0; i < MAX_INI_LINE_LEN; i++) {
+        if (('\n' == filedata[i]) || ('\r' == filedata[i])) {
+            line_data[line_length++] = '\0';
+            if (('\n' == filedata[i + 1]) || ('\r' == filedata[i + 1])) {
+                line_length++;
+            }
+            break;
+        } else {
+            line_data[line_length++] = filedata[i];
+        }
+    }
+
+    if (i >= MAX_INI_LINE_LEN) {
+        FTS_TEST_ERROR("line length(%d)>max(%d)", line_length, MAX_INI_LINE_LEN);
+        return -ENODATA;
+    }
+
+    /* remove space */
+    str_space_remove(line_data);
+
+    /* confirm line type */
+    if (('\0' == line_data[0]) || ('#' == line_data[0])) {
+        type = LINE_OTHER;
+    } else if ('[' == line_data[0]) {
+        type = LINE_SECTION;
+    } else {
+        type = LINE_KEYWORD; /* key word */
+    }
+
+    *line_len = line_length;
+    return type;
+}
+
+static int ini_parse_keyword(struct ini_data *ini, char *line_buffer)
+{
+    int i = 0;
+    int offset = 0;
+    int length = strlen(line_buffer);
+    struct ini_section *section = NULL;
+
+    for (i = 0; i < length; i++) {
+        if (line_buffer[i] == '=')
+            break;
+    }
+
+    if ((i == 0) || (i >= length)) {
+        FTS_TEST_ERROR("mark(=)in keyword line fail");
+        return -ENODATA;
+    }
+
+    if ((ini->section_num > 0) && (ini->section_num < MAX_INI_SECTION_NUM)) {
+        section = &ini->section[ini->section_num - 1];
+    }
+
+    if (NULL == section) {
+        FTS_TEST_ERROR("section is null");
+        return -ENODATA;
+    }
+
+    offset = ini->keyword_num_total;
+    if (offset > MAX_KEYWORD_NUM) {
+        FTS_TEST_ERROR("keyword num(%d)>max(%d),please check MAX_KEYWORD_NUM",
+                       ini->keyword_num_total, MAX_KEYWORD_NUM);
+        return -ENODATA;
+    }
+    memcpy(ini->tmp[offset].name, &line_buffer[0], i);
+    ini->tmp[offset].name[i] = '\0';
+    memcpy(ini->tmp[offset].value, &line_buffer[i + 1], length - i - 1);
+    ini->tmp[offset].value[length - i - 1] = '\0';
+    section->keyword_num++;
+    ini->keyword_num_total++;
+
+    return 0;
+}
+
+static int ini_parse_section(struct ini_data *ini, char *line_buffer)
+{
+    int length = strlen(line_buffer);
+    struct ini_section *section = NULL;
+
+    if ((length <= 2) || (length > MAX_KEYWORD_NAME_LEN)) {
+        FTS_TEST_ERROR("section line length fail");
+        return -EINVAL;
+    }
+
+    if ((ini->section_num < 0) || (ini->section_num >= MAX_INI_SECTION_NUM)) {
+        FTS_TEST_ERROR("section_num(%d) fail", ini->section_num);
+        return -EINVAL;
+    }
+    section = &ini->section[ini->section_num];
+    memcpy(section->name, line_buffer + 1, length - 2);
+    section->name[length - 2] = '\0';
+    FTS_TEST_INFO("section:%s, keyword offset:%d",
+                  section->name, ini->keyword_num_total);
+    section->keyword = (struct ini_keyword *)&ini->tmp[ini->keyword_num_total];
+    section->keyword_num = 0;
+    ini->section_num++;
+    if (ini->section_num > MAX_INI_SECTION_NUM) {
+        FTS_TEST_ERROR("section num(%d)>max(%d), please check MAX_INI_SECTION_NUM",
+                       ini->section_num, MAX_INI_SECTION_NUM);
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+static int ini_init_inidata(struct ini_data *ini)
+{
+    int pos = 0;
+    int ret = 0;
+    char line_buffer[MAX_INI_LINE_LEN] = { 0 };
+    int line_len = 0;
+
+    if (!ini || !ini->data || !ini->tmp) {
+        FTS_TEST_DBG("ini/data/tmp is null");
+        return -EINVAL;
+    }
+
+    while (pos < ini->length) {
+        ret = ini_get_line(ini->data + pos, line_buffer, &line_len);
+        if (ret < 0) {
+            FTS_TEST_ERROR("ini_get_line fail");
+            return ret;
+        } else if (ret == LINE_KEYWORD) {
+            ret = ini_parse_keyword(ini, line_buffer);
+            if (ret < 0) {
+                FTS_TEST_ERROR("ini_parse_keyword fail");
+                return ret;
+            }
+        } else if (ret == LINE_SECTION) {
+            ret = ini_parse_section(ini, line_buffer);
+            if (ret < 0) {
+                FTS_TEST_ERROR("ini_parse_section fail");
+                return ret;
+            }
+        }
+
+        pos += line_len;
+    }
+
+    print_ini_data(ini);
+    return 0;
+}
+
+static int ini_get_key(char *section_name, char *key_name, char *value)
+{
+    int i = 0;
+    int j = 0;
+    struct ini_data *ini = &fts_ftest->ini;
+    struct ini_section *section;
+    struct ini_keyword *keyword;
+    int key_len = 0;
+    int log_level = fts_ftest->ts_data->log_level;
+
+    if (log_level >= 10) {
+        FTS_TEST_DBG("section name:%s, key name:%s", section_name, key_name);
+        FTS_TEST_DBG("section num:%d", ini->section_num);
+    }
+
+    for (i = 0; i < ini->section_num; i++) {
+        section = &ini->section[i];
+        key_len = strlen(section_name);
+        if (key_len != strlen(section->name))
+            continue;
+        if (fts_strncmp(section->name, section_name, key_len) != 0)
+            continue;
+
+        if (log_level >= 10) {
+            FTS_TEST_DBG("section name:%s keyword num:%d",
+                         section->name, section->keyword_num);
+        }
+        for (j = 0; j < section->keyword_num; j++) {
+            keyword = &section->keyword[j];
+            key_len = strlen(key_name);
+            if (key_len == strlen(keyword->name)) {
+                if (0 == fts_strncmp(keyword->name, key_name, key_len)) {
+                    key_len = strlen(keyword->value);
+                    memcpy(value, keyword->value, key_len);
+                    if (log_level >= 3) {
+                        FTS_TEST_DBG("section:%s,%s=%s",
+                                     section_name, key_name, value);
+                    }
+
+                    return key_len;
+                }
+            }
+        }
+    }
+
+    return -ENODATA;
+}
+
+/* return keyword's value length if success */
+static int ini_get_string_value(char *section_name, char *key_name, char *rval)
+{
+    if (!section_name || !key_name || !rval) {
+        FTS_TEST_ERROR("section_name/key_name/rval is null");
+        return -EINVAL;
+    }
+
+    return ini_get_key(section_name, key_name, rval);
+}
+
+int get_keyword_value(char *section, char *name, int *value)
+{
+    int ret = 0;
+    char str[MAX_KEYWORD_VALUE_LEN] = { 0 };
+
+    ret = ini_get_string_value(section, name, str);
+    if (ret > 0) {
+        /* search successfully, so change value, otherwise keep default */
+        *value = fts_atoi(str);
+    }
+
+    return ret;
+}
+
+static void fts_init_buffer(int *buffer, int value, int len, bool key_check, int key_value, int key_len)
+{
+    int i = 0;
+    int va_len = 0;
+
+    if (NULL == buffer) {
+        FTS_TEST_ERROR("buffer is null\n");
+        return;
+    }
+
+    va_len = len - key_len;
+    if (va_len < 0) {
+        FTS_TEST_ERROR("total len(0x%x) less key len(0x%x)\n", len, key_len);
+        return;
+    }
+
+    for (i = 0; i < len; i++) {
+        buffer[i] = value;
+    }
+
+    if (key_check) {
+        for (i = 0; i < key_len; i++) {
+            buffer[va_len + i] = key_value;
+        }
+    }
+
+}
+
+static int get_test_item(char name[][MAX_KEYWORD_NAME_LEN], int length, int *val)
+{
+    int i = 0;
+    int ret = 0;
+    int tmpval = 0;
+
+    if (length > TEST_ITEM_COUNT_MAX) {
+        FTS_TEST_SAVE_ERR("test item count(%d) > max(%d)\n",
+                          length, TEST_ITEM_COUNT_MAX);
+        return -EINVAL;
+    }
+
+    FTS_TEST_INFO("test items in total of driver:%d", length);
+    *val = 0;
+    for (i = 0; i < length; i++) {
+        tmpval = 0;
+        ret = get_value_testitem(name[i], &tmpval);
+        if (ret < 0) {
+            FTS_TEST_DBG("test item:%s not found", name[i]);
+        } else {
+            FTS_TEST_DBG("test item:%s=%d", name[i], tmpval);
+            *val |= (tmpval << i);
+        }
+    }
+
+    return 0;
+}
+
+static int get_basic_threshold(char name[][MAX_KEYWORD_NAME_LEN], int length, int *val)
+{
+    int i = 0;
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+    int log_level = tdata->ts_data->log_level;
+
+    FTS_TEST_INFO("basic_thr string length(%d), count(%d)\n", length, tdata->basic_thr_count);
+    if (length > fts_ftest->basic_thr_count) {
+        FTS_TEST_SAVE_ERR("basic_thr string length > count\n");
+        return -EINVAL;
+    }
+
+    for (i = 0; i < length; i++) {
+        ret = get_value_basic(name[i], &val[i]);
+        if (log_level >= 3) {
+            if (ret < 0) {
+                FTS_TEST_DBG("basic thr:%s not found", name[i]);
+            } else {
+                FTS_TEST_DBG("basic thr:%s=%d", name[i], val[i]);
+            }
+        }
+    }
+
+    return 0;
+}
+
+static void get_detail_threshold(char *key_name, bool is_prex, int *thr, int node_num)
+{
+    char str[MAX_KEYWORD_VALUE_LEN] = { 0 };
+    char str_temp[MAX_KEYWORD_NAME_LEN] = { 0 };
+    char str_tmp[MAX_KEYWORD_VALUE_ONE_LEN] = { 0 };
+    struct fts_test *tdata = fts_ftest;
+    int divider_pos = 0;
+    int index = 0;
+    int i = 0;
+    int j = 0;
+    int k = 0;
+    int tx_num = 0;
+    int rx_num = 0;
+    int thr_pos = 0;
+
+    if (!key_name || !thr) {
+        FTS_TEST_ERROR("key_name/thr is null");
+        return;
+    }
+
+    if (is_prex) {
+        tx_num = tdata->node.tx_num;
+        rx_num = tdata->node.rx_num;
+    }
+    for (i = 0; i < tx_num + 1; i++) {
+        if (is_prex) {
+            snprintf(str_temp, MAX_KEYWORD_NAME_LEN, "%s%d", key_name, (i + 1));
+        } else {
+            snprintf(str_temp, MAX_KEYWORD_NAME_LEN, "%s", key_name);
+        }
+        divider_pos = ini_get_string_value("SpecialSet", str_temp, str);
+        if (divider_pos <= 0)
+            continue;
+        index = 0;
+        k = 0;
+        memset(str_tmp, 0, sizeof(str_tmp));
+        for (j = 0; j < divider_pos; j++) {
+            if (',' == str[j]) {
+                thr_pos = i * rx_num + k;
+                if (thr_pos >= node_num) {
+                    FTS_TEST_ERROR("key:%s %d,dthr_num(%d>=%d) fail",
+                                   key_name, i, thr_pos, node_num);
+                    break;
+                }
+                thr[thr_pos] = (int)(fts_atoi(str_tmp));
+                index = 0;
+                memset(str_tmp, 0x00, sizeof(str_tmp));
+                k++;
+            } else {
+                if (' ' == str[j])
+                    continue;
+                str_tmp[index] = str[j];
+                index++;
+            }
+        }
+    }
+}
+
+static int init_node_valid(void)
+{
+    char str[MAX_KEYWORD_NAME_LEN] = {0};
+    int i = 0;
+    int j = 0;
+    int chy = 0;
+    int node_num = 0;
+    int cnt = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if (!tdata || !tdata->node_valid || !tdata->node_valid_sc) {
+        FTS_TEST_ERROR("tdata/node_valid/node_valid_sc is null");
+        return -EINVAL;
+    }
+
+    chy = tdata->node.rx_num;
+    node_num = tdata->node.node_num;
+    fts_init_buffer(tdata->node_valid, 1 , node_num, false, 0, 0);
+    if ((tdata->func->hwtype == IC_HW_INCELL) || (tdata->func->hwtype == IC_HW_MC_SC)) {
+        for (cnt = 0; cnt < node_num; cnt++) {
+            i = cnt / chy + 1;
+            j = cnt % chy + 1;
+            snprintf(str, MAX_KEYWORD_NAME_LEN, "InvalidNode[%d][%d]", i, j);
+            get_keyword_value("INVALID_NODE", str, &tdata->node_valid[cnt]);
+        }
+    }
+
+    if (tdata->func->hwtype == IC_HW_MC_SC) {
+        chy = tdata->sc_node.rx_num;
+        node_num = tdata->sc_node.node_num;
+        fts_init_buffer(tdata->node_valid_sc, 1, node_num, false, 0, 0);
+
+        for (cnt = 0; cnt < node_num; cnt++) {
+            i = (cnt >= chy) ? 2 : 1;
+            j = (cnt >= chy) ? (cnt - chy + 1) : (cnt + 1);
+            snprintf(str, MAX_KEYWORD_NAME_LEN, "InvalidNodeS[%d][%d]", i, j);
+            get_keyword_value("INVALID_NODES", str, &tdata->node_valid_sc[cnt]);
+        }
+    }
+
+    print_buffer(tdata->node_valid, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(tdata->node_valid_sc, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    return 0;
+}
+
+/* incell */
+static int get_test_item_incell(void)
+{
+    int ret = 0;
+    char item_name[][MAX_KEYWORD_NAME_LEN] = TEST_ITEM_INCELL;
+    int length = sizeof(item_name) / MAX_KEYWORD_NAME_LEN;
+    int item_val = 0;
+
+    ret = get_test_item(item_name, length, &item_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get test item fail\n");
+        return ret;
+    }
+
+    fts_ftest->ic.incell.u.tmp = item_val;
+    return 0;
+}
+
+static char bthr_name_incell[][MAX_KEYWORD_NAME_LEN] = BASIC_THRESHOLD_INCELL;
+static int get_test_threshold_incell(void)
+{
+    int ret = 0;
+    int length = sizeof(bthr_name_incell) / MAX_KEYWORD_NAME_LEN;
+    struct fts_test *tdata = fts_ftest;
+    struct incell_threshold *thr = &tdata->ic.incell.thr;
+    int node_num = tdata->node.node_num;
+    int key_num = tdata->node.key_num;
+    bool raw_key_check = thr->basic.rawdata_vkey_check;
+    bool cb_key_check = thr->basic.cb_vkey_check;
+
+    tdata->basic_thr_count = sizeof(struct incell_threshold_b) / sizeof(int);
+    /* get standard basic threshold */
+    ret = get_basic_threshold(bthr_name_incell, length, (int *)&thr->basic);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get basic thr fail\n");
+        return ret;
+    }
+
+    /* basic special set by ic */
+    if (tdata->func->param_init) {
+        ret = tdata->func->param_init();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("special basic thr init fail\n");
+            return ret;
+        }
+    }
+
+    /* init buffer */
+    fts_init_buffer(thr->rawdata_max, thr->basic.rawdata_max, node_num, raw_key_check, thr->basic.rawdata_max_vk, key_num);
+    fts_init_buffer(thr->rawdata_min, thr->basic.rawdata_min, node_num, raw_key_check, thr->basic.rawdata_min_vk, key_num);
+    if (tdata->func->rawdata2_support) {
+        fts_init_buffer(thr->rawdata2_max, thr->basic.rawdata2_max, node_num, false, 0, 0);
+        fts_init_buffer(thr->rawdata2_min, thr->basic.rawdata2_min, node_num, false, 0, 0);
+    }
+    fts_init_buffer(thr->cb_max, thr->basic.cb_max, node_num, cb_key_check, thr->basic.cb_max_vk, key_num);
+    fts_init_buffer(thr->cb_min, thr->basic.cb_min, node_num, cb_key_check, thr->basic.cb_min_vk, key_num);
+
+    /* detail threshold */
+    get_detail_threshold("RawData_Max_Tx", true, thr->rawdata_max, node_num);
+    get_detail_threshold("RawData_Min_Tx", true, thr->rawdata_min, node_num);
+    get_detail_threshold("CB_Max_Tx", true, thr->cb_max, node_num);
+    get_detail_threshold("CB_Min_Tx", true, thr->cb_min, node_num);
+
+    return 0;
+}
+
+static void print_thr_incell(void)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct incell_threshold *thr = &tdata->ic.incell.thr;
+
+    if (tdata->ts_data->log_level < 3) {
+        return;
+    }
+
+    FTS_TEST_DBG("short_res_min:%d", thr->basic.short_res_min);
+    FTS_TEST_DBG("short_res_vk_min:%d", thr->basic.short_res_vk_min);
+    FTS_TEST_DBG("open_cb_min:%d", thr->basic.open_cb_min);
+    FTS_TEST_DBG("open_k1_check:%d", thr->basic.open_k1_check);
+    FTS_TEST_DBG("open_k1_value:%d", thr->basic.open_k1_value);
+    FTS_TEST_DBG("open_k2_check:%d", thr->basic.open_k2_check);
+    FTS_TEST_DBG("open_k2_value:%d", thr->basic.open_k2_value);
+    FTS_TEST_DBG("cb_min:%d", thr->basic.cb_min);
+    FTS_TEST_DBG("cb_max:%d", thr->basic.cb_max);
+    FTS_TEST_DBG("cb_vkey_check:%d", thr->basic.cb_vkey_check);
+    FTS_TEST_DBG("cb_min_vk:%d", thr->basic.cb_min_vk);
+    FTS_TEST_DBG("cb_max_vk:%d", thr->basic.cb_max_vk);
+    FTS_TEST_DBG("rawdata_min:%d", thr->basic.rawdata_min);
+    FTS_TEST_DBG("rawdata_max:%d", thr->basic.rawdata_max);
+    FTS_TEST_DBG("rawdata_vkey_check:%d", thr->basic.rawdata_vkey_check);
+    FTS_TEST_DBG("rawdata_min_vk:%d", thr->basic.rawdata_min_vk);
+    FTS_TEST_DBG("rawdata_max_vk:%d", thr->basic.rawdata_max_vk);
+    FTS_TEST_DBG("lcdnoise_frame:%d", thr->basic.lcdnoise_frame);
+    FTS_TEST_DBG("lcdnoise_coefficient:%d", thr->basic.lcdnoise_coefficient);
+    FTS_TEST_DBG("lcdnoise_coefficient_vkey:%d", thr->basic.lcdnoise_coefficient_vkey);
+    FTS_TEST_DBG("open_diff_min:%d", thr->basic.open_diff_min);
+
+    FTS_TEST_DBG("open_nmos:%d", thr->basic.open_nmos);
+    FTS_TEST_DBG("keyshort_k1:%d", thr->basic.keyshort_k1);
+    FTS_TEST_DBG("keyshort_cb_max:%d", thr->basic.keyshort_cb_max);
+    FTS_TEST_DBG("rawdata2_min:%d", thr->basic.rawdata2_min);
+    FTS_TEST_DBG("rawdata2_max:%d", thr->basic.rawdata2_max);
+    FTS_TEST_DBG("mux_open_cb_min:%d", thr->basic.mux_open_cb_min);
+    FTS_TEST_DBG("open_delta_V:%d", thr->basic.open_delta_V);
+
+    print_buffer(thr->rawdata_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata2_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata2_max, tdata->node.node_num, tdata->node.rx_num);
+}
+
+static int ini_init_test_incell(void)
+{
+    int ret = 0;
+
+    ret = get_test_item_incell();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get incell test item fail\n");
+        return ret;
+    }
+
+
+    ret = get_test_threshold_incell();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get incell threshold fail\n");
+        return ret;
+    }
+
+    print_thr_incell();
+    return 0;
+}
+
+/* mc_sc */
+static int get_test_item_mc_sc(void)
+{
+    int ret = 0;
+    char item_name[][MAX_KEYWORD_NAME_LEN] = TEST_ITEM_MC_SC;
+    int length = sizeof(item_name) / MAX_KEYWORD_NAME_LEN;
+    int item_val = 0;
+
+    ret = get_test_item(item_name, length, &item_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get test item fail\n");
+        return ret;
+    }
+
+    fts_ftest->ic.mc_sc.u.tmp = item_val;
+    FTS_TEST_INFO("test item:0x%x in ini", fts_ftest->ic.mc_sc.u.tmp);
+    return 0;
+}
+
+static char bthr_name_mc_sc[][MAX_KEYWORD_NAME_LEN] = BASIC_THRESHOLD_MC_SC;
+static int get_test_threshold_mc_sc(void)
+{
+    int ret = 0;
+    int length = sizeof(bthr_name_mc_sc) / MAX_KEYWORD_NAME_LEN;
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+    int node_num = tdata->node.node_num;
+    int sc_num = tdata->sc_node.node_num;
+
+    tdata->basic_thr_count = sizeof(struct mc_sc_threshold_b) / sizeof(int);
+    /* get standard basic threshold */
+    ret = get_basic_threshold(bthr_name_mc_sc, length, (int *)&thr->basic);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get basic thr fail\n");
+        return ret;
+    }
+
+    /* basic special set by ic */
+    if (tdata->func->param_init) {
+        ret = tdata->func->param_init();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("special basic thr init fail\n");
+            return ret;
+        }
+    }
+
+    /* init buffer */
+    fts_init_buffer(thr->rawdata_h_min, thr->basic.rawdata_h_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->rawdata_h_max, thr->basic.rawdata_h_max, node_num, false, 0, 0);
+    if (tdata->func->rawdata2_support) {
+        fts_init_buffer(thr->rawdata_l_min, thr->basic.rawdata_l_min, node_num, false, 0, 0);
+        fts_init_buffer(thr->rawdata_l_max, thr->basic.rawdata_l_max, node_num, false, 0, 0);
+    }
+    fts_init_buffer(thr->tx_linearity_max, thr->basic.uniformity_tx_hole, node_num, false, 0, 0);
+    fts_init_buffer(thr->tx_linearity_min, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->rx_linearity_max, thr->basic.uniformity_rx_hole, node_num, false, 0, 0);
+    fts_init_buffer(thr->rx_linearity_min, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_off_min, thr->basic.scap_cb_off_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_off_max, thr->basic.scap_cb_off_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_on_min, thr->basic.scap_cb_on_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_on_max, thr->basic.scap_cb_on_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hi_min, thr->basic.scap_cb_hi_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hi_max, thr->basic.scap_cb_hi_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hov_min, thr->basic.scap_cb_hov_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hov_max, thr->basic.scap_cb_hov_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_off_min, thr->basic.scap_rawdata_off_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_off_max, thr->basic.scap_rawdata_off_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_on_min, thr->basic.scap_rawdata_on_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_on_max, thr->basic.scap_rawdata_on_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hi_min, thr->basic.scap_rawdata_hi_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hi_max, thr->basic.scap_rawdata_hi_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hov_min, thr->basic.scap_rawdata_hov_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hov_max, thr->basic.scap_rawdata_hov_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->panel_differ_min, thr->basic.panel_differ_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->panel_differ_max, thr->basic.panel_differ_max, node_num, false, 0, 0);
+
+    /* detail threshold */
+    get_detail_threshold("RawData_Min_High_Tx", true, thr->rawdata_h_min, node_num);
+    get_detail_threshold("RawData_Max_High_Tx", true, thr->rawdata_h_max, node_num);
+    if (tdata->func->rawdata2_support) {
+        get_detail_threshold("RawData_Min_Low_Tx", true, thr->rawdata_l_min, node_num);
+        get_detail_threshold("RawData_Max_Low_Tx", true, thr->rawdata_l_max, node_num);
+    }
+    get_detail_threshold("Tx_Linearity_Max_Tx", true, thr->tx_linearity_max, node_num);
+    get_detail_threshold("Rx_Linearity_Max_Tx", true, thr->rx_linearity_max, node_num);
+    get_detail_threshold("ScapCB_OFF_Min_", true, thr->scap_cb_off_min, sc_num);
+    get_detail_threshold("ScapCB_OFF_Max_", true, thr->scap_cb_off_max, sc_num);
+    get_detail_threshold("ScapCB_ON_Min_", true, thr->scap_cb_on_min, sc_num);
+    get_detail_threshold("ScapCB_ON_Max_", true, thr->scap_cb_on_max, sc_num);
+    get_detail_threshold("ScapCB_High_Min_", true, thr->scap_cb_hi_min, sc_num);
+    get_detail_threshold("ScapCB_High_Max_", true, thr->scap_cb_hi_max, sc_num);
+    get_detail_threshold("ScapCB_Hov_Min_", true, thr->scap_cb_hov_min, sc_num);
+    get_detail_threshold("ScapCB_Hov_Max_", true, thr->scap_cb_hov_max, sc_num);
+    get_detail_threshold("ScapRawData_OFF_Min_", true, thr->scap_rawdata_off_min, sc_num);
+    get_detail_threshold("ScapRawData_OFF_Max_", true, thr->scap_rawdata_off_max, sc_num);
+    get_detail_threshold("ScapRawData_ON_Min_", true, thr->scap_rawdata_on_min, sc_num);
+    get_detail_threshold("ScapRawData_ON_Max_", true, thr->scap_rawdata_on_max, sc_num);
+    get_detail_threshold("ScapRawData_High_Min_", true, thr->scap_rawdata_hi_min, sc_num);
+    get_detail_threshold("ScapRawData_High_Max_", true, thr->scap_rawdata_hi_max, sc_num);
+    get_detail_threshold("ScapRawData_Hov_Min_", true, thr->scap_rawdata_hov_min, sc_num);
+    get_detail_threshold("ScapRawData_Hov_Max_", true, thr->scap_rawdata_hov_max, sc_num);
+    get_detail_threshold("Panel_Differ_Min_Tx", true, thr->panel_differ_min, node_num);
+    get_detail_threshold("Panel_Differ_Max_Tx", true, thr->panel_differ_max, node_num);
+
+    return 0;
+}
+
+static void print_thr_mc_sc(void)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    if (tdata->ts_data->log_level < 3) {
+        return;
+    }
+
+    FTS_TEST_DBG("rawdata_h_min:%d", thr->basic.rawdata_h_min);
+    FTS_TEST_DBG("rawdata_h_max:%d", thr->basic.rawdata_h_max);
+    FTS_TEST_DBG("rawdata_set_hfreq:%d", thr->basic.rawdata_set_hfreq);
+    FTS_TEST_DBG("rawdata_l_min:%d", thr->basic.rawdata_l_min);
+    FTS_TEST_DBG("rawdata_l_max:%d", thr->basic.rawdata_l_max);
+    FTS_TEST_DBG("rawdata_set_lfreq:%d", thr->basic.rawdata_set_lfreq);
+    FTS_TEST_DBG("uniformity_check_tx:%d", thr->basic.uniformity_check_tx);
+    FTS_TEST_DBG("uniformity_check_rx:%d", thr->basic.uniformity_check_rx);
+    FTS_TEST_DBG("uniformity_check_min_max:%d", thr->basic.uniformity_check_min_max);
+    FTS_TEST_DBG("uniformity_tx_hole:%d", thr->basic.uniformity_tx_hole);
+    FTS_TEST_DBG("uniformity_rx_hole:%d", thr->basic.uniformity_rx_hole);
+    FTS_TEST_DBG("uniformity_min_max_hole:%d", thr->basic.uniformity_min_max_hole);
+    FTS_TEST_DBG("scap_cb_off_min:%d", thr->basic.scap_cb_off_min);
+    FTS_TEST_DBG("scap_cb_off_max:%d", thr->basic.scap_cb_off_max);
+    FTS_TEST_DBG("scap_cb_wp_off_check:%d", thr->basic.scap_cb_wp_off_check);
+    FTS_TEST_DBG("scap_cb_on_min:%d", thr->basic.scap_cb_on_min);
+    FTS_TEST_DBG("scap_cb_on_max:%d", thr->basic.scap_cb_on_max);
+    FTS_TEST_DBG("scap_cb_wp_on_check:%d", thr->basic.scap_cb_wp_on_check);
+    FTS_TEST_DBG("scap_rawdata_off_min:%d", thr->basic.scap_rawdata_off_min);
+    FTS_TEST_DBG("scap_rawdata_off_max:%d", thr->basic.scap_rawdata_off_max);
+    FTS_TEST_DBG("scap_rawdata_wp_off_check:%d", thr->basic.scap_rawdata_wp_off_check);
+    FTS_TEST_DBG("scap_rawdata_on_min:%d", thr->basic.scap_rawdata_on_min);
+    FTS_TEST_DBG("scap_rawdata_on_max:%d", thr->basic.scap_rawdata_on_max);
+    FTS_TEST_DBG("scap_rawdata_wp_on_check:%d", thr->basic.scap_rawdata_wp_on_check);
+    FTS_TEST_DBG("short_cg:%d", thr->basic.short_cg);
+    FTS_TEST_DBG("short_cc:%d", thr->basic.short_cc);
+    FTS_TEST_DBG("panel_differ_min:%d", thr->basic.panel_differ_min);
+    FTS_TEST_DBG("panel_differ_max:%d", thr->basic.panel_differ_max);
+
+    print_buffer(thr->rawdata_h_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_h_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_l_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_l_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->tx_linearity_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rx_linearity_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->scap_cb_off_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_off_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_on_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_on_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hi_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hi_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hov_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hov_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_off_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_off_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_on_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_on_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hi_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hi_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hov_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hov_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->panel_differ_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->panel_differ_max, tdata->node.node_num, tdata->node.rx_num);
+}
+
+static int ini_init_test_mc_sc(void)
+{
+    int ret = 0;
+
+    ret = get_test_item_mc_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get mc_sc test item fail\n");
+        return ret;
+    }
+
+    ret = get_test_threshold_mc_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get mc_sc threshold fail\n");
+        return ret;
+    }
+
+    print_thr_mc_sc();
+    return 0;
+}
+
+/* sc */
+static int get_test_item_sc(void)
+{
+    int ret = 0;
+    char item_name[][MAX_KEYWORD_NAME_LEN] = TEST_ITEM_SC;
+    int length = sizeof(item_name) / MAX_KEYWORD_NAME_LEN;
+    int item_val = 0;
+
+    ret = get_test_item(item_name, length, &item_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get test item fail\n");
+        return ret;
+    }
+
+    fts_ftest->ic.sc.u.tmp = item_val;
+    return 0;
+}
+
+static char bthr_name_sc[][MAX_KEYWORD_NAME_LEN] = BASIC_THRESHOLD_SC;
+static int get_test_threshold_sc(void)
+{
+    int ret = 0;
+    int length = sizeof(bthr_name_sc) / MAX_KEYWORD_NAME_LEN;
+    struct fts_test *tdata = fts_ftest;
+    struct sc_threshold *thr = &tdata->ic.sc.thr;
+    int node_num = tdata->node.node_num;
+
+    tdata->basic_thr_count = sizeof(struct sc_threshold_b) / sizeof(int);
+    /* get standard basic threshold */
+    ret = get_basic_threshold(bthr_name_sc, length, (int *)&thr->basic);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get basic thr fail\n");
+        return ret;
+    }
+
+    /* basic special set by ic */
+    if (tdata->func->param_init) {
+        ret = tdata->func->param_init();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("special basic thr init fail\n");
+            return ret;
+        }
+    }
+
+    /* init buffer */
+    fts_init_buffer(thr->rawdata_min, thr->basic.rawdata_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->rawdata_max, thr->basic.rawdata_max, node_num, false, 0, 0);
+    fts_init_buffer(thr->cb_min, thr->basic.cb_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->cb_max, thr->basic.cb_max, node_num, false, 0, 0);
+    fts_init_buffer(thr->dcb_sort, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->dcb_base, thr->basic.dcb_base, node_num, false, 0, 0);
+
+    /* detail threshold */
+    get_detail_threshold("RawDataTest_Min", false, thr->rawdata_min, node_num);
+    get_detail_threshold("RawDataTest_Max", false, thr->rawdata_max, node_num);
+    get_detail_threshold("CbTest_Min", false, thr->cb_min, node_num);
+    get_detail_threshold("CbTest_Max", false, thr->cb_max, node_num);
+    get_detail_threshold("DeltaCxTest_Sort", false, thr->dcb_sort, node_num);
+    get_detail_threshold("DeltaCbTest_Base", false, thr->dcb_base, node_num);
+
+    return 0;
+}
+
+static void print_thr_sc(void)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct sc_threshold *thr = &tdata->ic.sc.thr;
+
+    if (tdata->ts_data->log_level < 3) {
+        return;
+    }
+
+    FTS_TEST_DBG("rawdata_min:%d", thr->basic.rawdata_min);
+    FTS_TEST_DBG("rawdata_max:%d", thr->basic.rawdata_max);
+    FTS_TEST_DBG("cb_min:%d", thr->basic.cb_min);
+    FTS_TEST_DBG("cb_max:%d", thr->basic.cb_max);
+    FTS_TEST_DBG("dcb_differ_max:%d", thr->basic.dcb_differ_max);
+    FTS_TEST_DBG("dcb_key_check:%d", thr->basic.dcb_key_check);
+    FTS_TEST_DBG("dcb_key_differ_max:%d", thr->basic.dcb_key_differ_max);
+    FTS_TEST_DBG("dcb_ds1:%d", thr->basic.dcb_ds1);
+    FTS_TEST_DBG("dcb_ds2:%d", thr->basic.dcb_ds2);
+    FTS_TEST_DBG("dcb_ds3:%d", thr->basic.dcb_ds3);
+    FTS_TEST_DBG("dcb_ds4:%d", thr->basic.dcb_ds4);
+    FTS_TEST_DBG("dcb_ds5:%d", thr->basic.dcb_ds5);
+    FTS_TEST_DBG("dcb_ds6:%d", thr->basic.dcb_ds6);
+    FTS_TEST_DBG("dcb_critical_check:%d", thr->basic.dcb_critical_check);
+    FTS_TEST_DBG("dcb_cs1:%d", thr->basic.dcb_cs1);
+    FTS_TEST_DBG("dcb_cs2:%d", thr->basic.dcb_cs2);
+    FTS_TEST_DBG("dcb_cs3:%d", thr->basic.dcb_cs3);
+    FTS_TEST_DBG("dcb_cs4:%d", thr->basic.dcb_cs4);
+    FTS_TEST_DBG("dcb_cs5:%d", thr->basic.dcb_cs5);
+    FTS_TEST_DBG("dcb_cs6:%d", thr->basic.dcb_cs6);
+
+    print_buffer(thr->rawdata_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->dcb_sort, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->dcb_base, tdata->node.node_num, tdata->node.rx_num);
+}
+
+static int ini_init_test_sc(void)
+{
+    int ret = 0;
+
+    ret = get_test_item_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get sc test item fail\n");
+        return ret;
+    }
+
+    ret = get_test_threshold_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get sc threshold fail\n");
+        return ret;
+    }
+
+    print_thr_sc();
+    return 0;
+}
+
+static u32 ini_get_ic_code(char *ic_name)
+{
+    int i = 0;
+    int type_size = 0;
+    int ini_icname_len = 0;
+    int ic_types_len = 0;
+
+    ini_icname_len = strlen(ic_name);
+    type_size = sizeof(ic_types) / sizeof(ic_types[0]);
+    for (i = 0; i < type_size; i++) {
+        ic_types_len = strlen(ic_name);
+        if (ini_icname_len == ic_types_len) {
+            if (0 == strncmp(ic_name, ic_types[i].ic_name, ic_types_len))
+                return ic_types[i].ic_type;
+        }
+    }
+
+    FTS_TEST_ERROR("no IC type match");
+    return 0;
+}
+
+
+static void ini_init_interface(struct ini_data *ini)
+{
+    char str[MAX_KEYWORD_VALUE_LEN] = { 0 };
+    u32 value = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    /* IC type */
+    ini_get_string_value("Interface", "IC_Type", str);
+    snprintf(ini->ic_name, MAX_IC_NAME_LEN, "%s", str);
+
+    value = ini_get_ic_code(str);
+    ini->ic_code = value;
+    FTS_TEST_INFO("ic name:%s, ic code:%x", ini->ic_name, ini->ic_code);
+
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        get_value_interface("Normalize_Type", &value);
+        tdata->normalize = (u8)value;
+        FTS_TEST_DBG("normalize:%d", tdata->normalize);
+    }
+}
+
+static int ini_init_test(struct ini_data *ini)
+{
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    /* interface init */
+    ini_init_interface(ini);
+
+    /* node valid */
+    ret = init_node_valid();
+    if (ret < 0) {
+        FTS_TEST_ERROR("init node valid fail");
+        return ret;
+    }
+
+    switch (tdata->func->hwtype) {
+    case IC_HW_INCELL:
+        ret = ini_init_test_incell();
+        break;
+    case IC_HW_MC_SC:
+        ret = ini_init_test_mc_sc();
+        break;
+    case IC_HW_SC:
+        ret = ini_init_test_sc();
+        break;
+    default:
+        FTS_TEST_SAVE_ERR("test ic type(%d) fail\n", tdata->func->hwtype);
+        ret = -EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+/*
+ * fts_test_get_testparam_from_ini - get test parameters from ini
+ *
+ * read, parse the configuration file, initialize the test variable
+ *
+ * return 0 if succuss, else errro code
+ */
+int fts_test_get_testparam_from_ini(char *config_name)
+{
+    int ret = 0;
+    struct ini_data *ini = &fts_ftest->ini;
+
+    ret = fts_test_get_ini_via_request_firmware(ini, config_name);
+    if (ret != 0) {
+        FTS_TEST_INFO("read ini fail,ret=%d", ret);
+        return ret;
+    }
+
+    ini->keyword_num_total = 0;
+    ini->section_num = 0;
+
+    ini->tmp = vmalloc(sizeof(struct ini_keyword) * MAX_KEYWORD_NUM);
+    if (ini->tmp == NULL) {
+        FTS_TEST_ERROR("malloc memory for ini tmp fail");
+        return -ENOMEM;
+    }
+    memset(ini->tmp, 0, sizeof(struct ini_keyword) * MAX_KEYWORD_NUM);
+
+    /* parse ini data to get keyword name&value */
+    ret = ini_init_inidata(ini);
+    if (ret < 0) {
+        FTS_TEST_ERROR("ini_init_inidata fail");
+        goto get_ini_err;
+    }
+
+    /* parse threshold & test item */
+    ret = ini_init_test(ini);
+    if (ret < 0) {
+        FTS_TEST_ERROR("ini init fail");
+        goto get_ini_err;
+    }
+
+get_ini_err:
+    if (ini->tmp) {
+        vfree(ini->tmp);
+        ini->tmp = NULL;
+    }
+
+    if (ini->data) {
+        vfree(ini->data);
+        ini->data = NULL;
+    }
+
+    return ret;
+}
diff --git a/ft3658/focaltech_test/focaltech_test_ini.h b/ft3658/focaltech_test/focaltech_test_ini.h
new file mode 100644
index 0000000..051393e
--- /dev/null
+++ b/ft3658/focaltech_test/focaltech_test_ini.h
@@ -0,0 +1,146 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R)£¬All Rights Reserved.
+*
+* File Name: focaltech_test_ini.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Abstract: parsing function of INI file
+*
+************************************************************************/
+#ifndef _INI_H
+#define _INI_H
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define MAX_KEYWORD_NUM                         (1000)
+#define MAX_KEYWORD_NAME_LEN                    (50)
+#define MAX_KEYWORD_VALUE_LEN                   (512)
+#define MAX_KEYWORD_VALUE_ONE_LEN               (16)
+#define MAX_INI_LINE_LEN        (MAX_KEYWORD_NAME_LEN + MAX_KEYWORD_VALUE_LEN)
+#define MAX_INI_SECTION_NUM                     (20)
+#define MAX_IC_NAME_LEN                         (32)
+#define MAX_TEST_ITEM                           (20)
+#define IC_CODE_OFFSET                          (16)
+
+/*****************************************************************************
+* enumerations, structures and unions
+*****************************************************************************/
+struct ini_ic_type {
+    char ic_name[MAX_IC_NAME_LEN];
+    u32 ic_type;
+};
+
+enum line_type {
+    LINE_SECTION = 1,
+    LINE_KEYWORD = 2 ,
+    LINE_OTHER = 3,
+};
+
+struct ini_keyword {
+    char name[MAX_KEYWORD_NAME_LEN];
+    char value[MAX_KEYWORD_VALUE_LEN];
+};
+
+struct ini_section {
+    char name[MAX_KEYWORD_NAME_LEN];
+    int keyword_num;
+    /* point to ini.tmp, don't need free */
+    struct ini_keyword *keyword;
+};
+
+struct ini_data {
+    char *data;
+    int length;
+    int keyword_num_total;
+    int section_num;
+    struct ini_section section[MAX_INI_SECTION_NUM];
+    struct ini_keyword *tmp;
+    char ic_name[MAX_IC_NAME_LEN];
+    u32 ic_code;
+};
+
+#define TEST_ITEM_INCELL            { \
+    "SHORT_CIRCUIT_TEST", \
+    "OPEN_TEST", \
+    "CB_TEST", \
+    "RAWDATA_TEST", \
+    "LCD_NOISE_TEST", \
+    "KEY_SHORT_TEST", \
+    "MUX_OPEN_TEST", \
+}
+
+#define BASIC_THRESHOLD_INCELL      { \
+    "ShortCircuit_ResMin", "ShortCircuit_VkResMin", \
+    "OpenTest_CBMin", "OpenTest_Check_K1", "OpenTest_K1Threshold", "OpenTest_Check_K2", "OpenTest_K2Threshold", \
+    "CBTest_Min", "CBTest_Max", \
+    "CBTest_VKey_Check", "CBTest_Min_Vkey", "CBTest_Max_Vkey", \
+    "RawDataTest_Min", "RawDataTest_Max", \
+    "RawDataTest_VKey_Check", "RawDataTest_Min_VKey", "RawDataTest_Max_VKey", \
+    "LCD_NoiseTest_Frame", "LCD_NoiseTest_Coefficient", "LCD_NoiseTest_Coefficient_key", \
+    "OpenTest_DifferMin", \
+}
+
+
+#define TEST_ITEM_MC_SC             { \
+    "RAWDATA_TEST", \
+    "UNIFORMITY_TEST", \
+    "SCAP_CB_TEST", \
+    "SCAP_RAWDATA_TEST", \
+    "WEAK_SHORT_CIRCUIT_TEST", \
+    "PANEL_DIFFER_TEST", \
+}
+
+#define BASIC_THRESHOLD_MC_SC       { \
+    "RawDataTest_High_Min", "RawDataTest_High_Max", "RawDataTest_HighFreq", \
+    "RawDataTest_Low_Min", "RawDataTest_Low_Max", "RawDataTest_LowFreq", \
+    "UniformityTest_Check_Tx", "UniformityTest_Check_Rx","UniformityTest_Check_MinMax", \
+    "UniformityTest_Tx_Hole", "UniformityTest_Rx_Hole", "UniformityTest_MinMax_Hole", \
+    "SCapCbTest_OFF_Min", "SCapCbTest_OFF_Max", "ScapCBTest_SetWaterproof_OFF", \
+    "SCapCbTest_ON_Min", "SCapCbTest_ON_Max", "ScapCBTest_SetWaterproof_ON", \
+    "SCapRawDataTest_OFF_Min", "SCapRawDataTest_OFF_Max", "SCapRawDataTest_SetWaterproof_OFF", \
+    "SCapRawDataTest_ON_Min", "SCapRawDataTest_ON_Max", "SCapRawDataTest_SetWaterproof_ON", \
+    "WeakShortTest_CG", "WeakShortTest_CC", \
+    "PanelDifferTest_Min", "PanelDifferTest_Max", \
+    "SCapCbTest_High_Min", "SCapCbTest_High_Max", "ScapCBTest_SetHighSensitivity", \
+    "SCapRawDataTest_High_Min", "SCapRawDataTest_High_Max", "SCapRawDataTest_SetHighSensitivity", \
+    "SCapCbTest_Hov_Min", "SCapCbTest_Hov_Max", "ScapCBTest_SetHov", \
+    "SCapRawDataTest_Hov_Min", "SCapRawDataTest_Hov_Max", "SCapRawDataTest_SetHov", \
+}
+
+#define TEST_ITEM_SC                { \
+    "RAWDATA_TEST", \
+    "CB_TEST", \
+    "DELTA_CB_TEST", \
+    "WEAK_SHORT_TEST", \
+}
+
+#define BASIC_THRESHOLD_SC          { \
+    "RawDataTest_Min", "RawDataTest_Max", \
+    "CbTest_Min", "CbTest_Max", \
+    "DeltaCbTest_Base", "DeltaCbTest_Differ_Max", \
+    "DeltaCbTest_Include_Key_Test", "DeltaCbTest_Key_Differ_Max", \
+    "DeltaCbTest_Deviation_S1", "DeltaCbTest_Deviation_S2", "DeltaCbTest_Deviation_S3", \
+    "DeltaCbTest_Deviation_S4", "DeltaCbTest_Deviation_S5", "DeltaCbTest_Deviation_S6", \
+    "DeltaCbTest_Set_Critical", "DeltaCbTest_Critical_S1", "DeltaCbTest_Critical_S2", \
+    "DeltaCbTest_Critical_S3", "DeltaCbTest_Critical_S4", \
+    "DeltaCbTest_Critical_S5", "DeltaCbTest_Critical_S6", \
+}
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+int fts_test_get_testparam_from_ini(char *config_name);
+int get_keyword_value(char *section, char *name, int *value);
+
+#define get_value_interface(name, value) \
+    get_keyword_value("Interface", name, value)
+#define get_value_basic(name, value) \
+    get_keyword_value("Basic_Threshold", name, value)
+#define get_value_detail(name, value) \
+    get_keyword_value("SpecialSet", name, value)
+#define get_value_testitem(name, value) \
+    get_keyword_value("TestItem", name, value)
+#endif /* _INI_H */
diff --git a/ft3658/focaltech_test/supported_ic/Makefile b/ft3658/focaltech_test/supported_ic/Makefile
new file mode 100644
index 0000000..8ab065f
--- /dev/null
+++ b/ft3658/focaltech_test/supported_ic/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_test_ft3658u.o
\ No newline at end of file
diff --git a/ft3658/focaltech_test/supported_ic/focaltech_test_ft3658u.c b/ft3658/focaltech_test/supported_ic/focaltech_test_ft3658u.c
new file mode 100644
index 0000000..719dc29
--- /dev/null
+++ b/ft3658/focaltech_test/supported_ic/focaltech_test_ft3658u.c
@@ -0,0 +1,2965 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R), All Rights Reserved.
+*
+* File Name: Focaltech_test_ft5652.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2018-03-08
+*
+* Abstract:
+*
+************************************************************************/
+
+/*****************************************************************************
+* included header files
+*****************************************************************************/
+#include "../focaltech_test.h"
+
+/*****************************************************************************
+* private constant and macro definitions using #define
+*****************************************************************************/
+static int short_test_ch_to_all(
+    struct fts_test *tdata, int *adc, u8 *ab_ch, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    int short_res[SC_NUM_MAX + 1] = { 0 };
+    int min_cc = tdata->ic.mc_sc.thr.basic.short_cc;
+    int ch_num = tdata->sc_node.tx_num + tdata->sc_node.rx_num;
+    int byte_num = 0;
+    int code = 0;
+    int code1 = 0;
+    int offset = 0;
+    int denominator = 0;
+    int numerator = 0;
+    u8 ab_ch_num = 0;
+
+    FTS_TEST_DBG("short test:channel to all other\n");
+    /* choose resistor_level */
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_RES_LEVEL, 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short resistor level fail\n");
+        return ret;
+    }
+
+    /*get adc data*/
+    byte_num = (ch_num + 1) * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &adc[0], \
+                                FACTROY_REG_SHORT2_CA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+    //tdata->code1 = adc[ch_num];
+    tdata->code1 = 1407;
+
+    /*get resistor*/
+    code1 = tdata->code1;
+    offset = tdata->offset;
+    for (i = 0; i < ch_num; i++) {
+        code = adc[i];
+        denominator = code1 - code + offset;
+        if (denominator == 0) {
+            short_res[i] = min_cc;
+        } else {
+            numerator = (code - offset + 395) * 112;
+            short_res[i] = fts_abs(numerator / denominator - 3);
+        }
+
+        if (short_res[i] < min_cc) {
+            ab_ch_num++;
+            ab_ch[ab_ch_num] = i + 1;
+        }
+    }
+
+    if (ab_ch_num) {
+        FTS_TEST_SAVE_INFO("Offset:%d, Code1:%d\n", offset, code1);
+        print_buffer(adc, ch_num + 1, ch_num + 1);
+        print_buffer(short_res, ch_num, ch_num);
+        ab_ch[0] = ab_ch_num;
+        printk("[FTS_TS]ab_ch:");
+        for (i = 0; i < ab_ch_num + 1; i++) {
+            printk("%2d ", ab_ch[i]);
+        }
+        printk("\n");
+        *result = false;
+    } else {
+        *result = true;
+    }
+
+    return 0;
+}
+
+static int short_test_ch_to_gnd(
+    struct fts_test *tdata, int *adc, u8 *ab_ch, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    int short_res[SC_NUM_MAX + 1] = { 0 };
+    int min_cg = tdata->ic.mc_sc.thr.basic.short_cg;
+    int tx_num = tdata->sc_node.tx_num;
+    int byte_num = 0;
+    int code = 0;
+    int code1 = 0;
+    int offset = 0;
+    int denominator = 0;
+    int numerator = 0;
+    u8 ab_ch_num = 0;
+    bool is_cg_short = false;
+
+    FTS_TEST_DBG("short test:channel to gnd\n");
+    ab_ch_num = ab_ch[0];
+    ret = fts_test_write(FACTROY_REG_SHORT2_AB_CH, ab_ch, ab_ch_num + 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write abnormal channel fail\n");
+        return ret;
+    }
+
+    /* choose resistor_level */
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_RES_LEVEL, 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short resistor level fail\n");
+        return ret;
+    }
+
+    /*get adc data*/
+    byte_num = ab_ch_num * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &adc[0], \
+                                FACTROY_REG_SHORT2_CG);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+
+    /*get resistor*/
+    *result = true;
+    code1 = tdata->code1;
+    offset = tdata->offset;
+    for (i = 0; i < ab_ch_num; i++) {
+        code = adc[i];
+        denominator = code1 - code + offset;
+        if (denominator == 0) {
+            short_res[i] = min_cg;
+        } else {
+            numerator = (code - offset + 395) * 112;
+            short_res[i] = fts_abs(numerator / denominator - 3);
+        }
+
+        if (short_res[i] < min_cg) {
+            *result = false;
+            if (!is_cg_short) {
+                FTS_TEST_SAVE_INFO("GND Short:");
+                is_cg_short = true;
+            }
+
+            if (ab_ch[i + 1] <= tx_num) {
+                FTS_TEST_SAVE_INFO("Tx%d with GND:", ab_ch[i + 1]);
+            } else {
+                FTS_TEST_SAVE_INFO( "Rx%d with GND:", (ab_ch[i + 1] - tx_num));
+            }
+            FTS_TEST_SAVE_INFO("%d(K), ADC:%d\n", short_res[i], code);
+        }
+    }
+
+    return 0;
+}
+
+static int short_test_ch_to_ch(
+    struct fts_test *tdata, int *adc, u8 *ab_ch, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    int j = 0;
+    int adc_cnt = 0;
+    int short_res[SC_NUM_MAX + 1] = { 0 };
+    int min_cc = tdata->ic.mc_sc.thr.basic.short_cc;
+    int tx_num = tdata->sc_node.tx_num;
+    int ch_num = tdata->sc_node.tx_num + tdata->sc_node.rx_num;
+    int byte_num = 0;
+    int tmp_num = 0;
+    int code = 0;
+    int code1 = 0;
+    int offset = 0;
+    int denominator = 0;
+    int numerator = 0;
+    u8 ab_ch_num = 0;
+    bool is_cc_short = false;
+
+    FTS_TEST_DBG("short test:channel to channel\n");
+    ab_ch_num = ab_ch[0];
+    if (ab_ch_num < 2) {
+        FTS_TEST_DBG("abnormal channel number<2, not run ch_ch test");
+        return ret;
+    }
+
+    ret = fts_test_write(FACTROY_REG_SHORT2_AB_CH, ab_ch, ab_ch_num + 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write abnormal channel fail\n");
+        return ret;
+    }
+
+    /* choose resistor_level */
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_RES_LEVEL, 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short resistor level fail\n");
+        return ret;
+    }
+
+    /*get adc data*/
+    /*channel to channel: num * (num - 1) / 2, max. node_num*/
+    tmp_num = ab_ch_num * (ab_ch_num - 1) / 2;
+    tmp_num = (tmp_num > ch_num) ? ch_num : tmp_num;
+    byte_num = tmp_num * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &adc[0], \
+                                FACTROY_REG_SHORT2_CC);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+
+    /*get resistor*/
+    *result = true;
+    code1 = tdata->code1;
+    offset = tdata->offset;
+    for (i = 0; i < ab_ch_num; i++) {
+        for (j = i + 1; j < ab_ch_num; j++) {
+            if (adc_cnt >= tmp_num)
+                break;
+            code = adc[adc_cnt];
+            denominator = code1 - code + offset;
+            if (denominator == 0) {
+                short_res[adc_cnt] = min_cc;
+            } else {
+                numerator = (code - offset + 395) * 112;
+                short_res[adc_cnt] = fts_abs(numerator / denominator - 3);
+            }
+
+            if (short_res[adc_cnt] < min_cc) {
+                *result = false;
+                if (!is_cc_short) {
+                    FTS_TEST_SAVE_INFO("Mutual Short:");
+                    is_cc_short = true;
+                }
+
+                if (ab_ch[i + 1] <= tx_num) {
+                    FTS_TEST_SAVE_INFO("Tx%d with", (ab_ch[i + 1]));
+                } else {
+                    FTS_TEST_SAVE_INFO("Rx%d with", (ab_ch[i + 1] - tx_num));
+                }
+
+                if (ab_ch[j + 1] <= tx_num) {
+                    FTS_TEST_SAVE_INFO(" Tx%d", (ab_ch[j + 1] ) );
+                } else {
+                    FTS_TEST_SAVE_INFO(" Rx%d", (ab_ch[j + 1] - tx_num));
+                }
+                FTS_TEST_SAVE_INFO(":%d(K), ADC:%d\n",
+                                   short_res[adc_cnt], code);
+            }
+            adc_cnt++;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * start_scan - start to scan a frame
+ */
+int ft5652_start_scan(int frame_num)
+{
+    int ret = 0;
+    u8 addr = 0;
+    u8 val = 0;
+    u8 finish_val = 0;
+    int times = 0;
+    int max_retry_cnt = 100;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((tdata == NULL) || (tdata->func == NULL)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    addr = DIVIDE_MODE_ADDR;
+    val = 0xC0;
+    finish_val = 0x40;
+
+    /* write register to start scan */
+    ret = fts_test_write_reg(addr, val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write start scan mode fail\n");
+        return ret;
+    }
+
+    sys_delay(frame_num * FACTORY_TEST_DELAY / 2);
+    /* Wait for the scan to complete */
+    while (times++ < max_retry_cnt) {
+        sys_delay(FACTORY_TEST_DELAY);
+
+        ret = fts_test_read_reg(addr, &val);
+        if ((ret >= 0) && (val == finish_val)) {
+            break;
+        } else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", addr, val, times);
+    }
+
+    if (times >= max_retry_cnt) {
+        FTS_TEST_SAVE_ERR("scan timeout\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int ft5652_rawdata_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    int *rawdata = NULL;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    bool result = false;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("====== Test Item: rawdata test");
+    memset(tdata->buffer, 0, tdata->buffer_length);
+    rawdata = tdata->buffer;
+
+    if (!thr->rawdata_h_min || !thr->rawdata_h_max) {
+        FTS_TEST_SAVE_ERR("rawdata_h_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* rawdata test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x06 error,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /*********************GET RAWDATA*********************/
+    for (i = 0; i < 3; i++) {
+        /* lost 3 frames, in order to obtain stable data */
+        ret = get_rawdata(rawdata);
+    }
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* show test data */
+    show_data(rawdata, false);
+
+    /* compare */
+    result = compare_array(rawdata,
+                           thr->rawdata_h_min,
+                           thr->rawdata_h_max,
+                           false);
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+test_err:
+    if (result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("====== rawdata test PASS");
+        if (tdata->s) seq_printf(tdata->s, "------ rawdata test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_INFO("====== rawdata test NG");
+        if (tdata->s) seq_printf(tdata->s, "------ rawdata test NG\n");
+    }
+
+    /* save test data */
+    fts_test_save_data("Rawdata Test", CODE_M_RAWDATA_TEST,
+                       rawdata, 0, false, false, *test_result);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5652_uniformity_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int index = 0;
+    int row = 0;
+    int col = 1;
+    int i = 0;
+    int deviation = 0;
+    int max = 0;
+    int min = 0;
+    int uniform = 0;
+    int *rawdata = NULL;
+    int *rawdata_linearity = NULL;
+    int *rl_tmp = NULL;
+    int rl_cnt = 0;
+    int offset = 0;
+    int offset2 = 0;
+    int tx_num = 0;
+    int rx_num = 0;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    bool result = false;
+    bool result2 = false;
+    bool result3 = false;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("====== Test Item: rawdata unfiormity test");
+    memset(tdata->buffer, 0, tdata->buffer_length);
+    rawdata = tdata->buffer;
+    tx_num = tdata->node.tx_num;
+    rx_num = tdata->node.rx_num;
+
+    if (!thr->tx_linearity_max || !thr->rx_linearity_max
+        || !tdata->node_valid) {
+        FTS_TEST_SAVE_ERR("tx/rx_lmax/node_valid is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    rawdata_linearity = fts_malloc(tdata->node.node_num * 2 * sizeof(int));
+    if (!rawdata_linearity) {
+        FTS_TEST_SAVE_ERR("rawdata_linearity buffer malloc fail");
+        ret = -ENOMEM;
+        goto test_err;
+    }
+
+    /* rawdata unfiormity test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("failed to switch_to_mapping,ret=%d", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x06 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /* change register value before,need to lose 3 frame data */
+    for (index = 0; index < 3; ++index) {
+        ret = get_rawdata(rawdata);
+    }
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+    print_buffer(rawdata, tdata->node.node_num, tdata->node.rx_num);
+
+    result = true;
+    if (thr->basic.uniformity_check_tx) {
+        FTS_TEST_SAVE_INFO("Check Tx Linearity\n");
+        rl_tmp = rawdata_linearity + rl_cnt;
+        for (row = 0; row < tx_num; row++) {
+            for (col = 1; col <  rx_num; col++) {
+                offset = row * rx_num + col;
+                offset2 = row * rx_num + col - 1;
+                deviation = abs( rawdata[offset] - rawdata[offset2]);
+                max = max(rawdata[offset], rawdata[offset2]);
+                max = max ? max : 1;
+                rl_tmp[offset] = 100 * deviation / max;
+            }
+        }
+        /*show data in result.txt*/
+        FTS_TEST_SAVE_INFO(" Tx Linearity:\n");
+        show_data(rl_tmp, false);
+        FTS_TEST_SAVE_INFO("\n" );
+
+        /* compare */
+        result = compare_array(rl_tmp,
+                               thr->tx_linearity_min,
+                               thr->tx_linearity_max,
+                               false);
+
+        rl_cnt += tdata->node.node_num;
+    }
+
+    result2 = true;
+    if (thr->basic.uniformity_check_rx) {
+        FTS_TEST_SAVE_INFO("Check Rx Linearity\n");
+        rl_tmp = rawdata_linearity + rl_cnt;
+        for (row = 1; row < tx_num; row++) {
+            for (col = 0; col < rx_num; col++) {
+                offset = row * rx_num + col;
+                offset2 = (row - 1) * rx_num + col;
+                deviation = abs(rawdata[offset] - rawdata[offset2]);
+                max = max(rawdata[offset], rawdata[offset2]);
+                max = max ? max : 1;
+                rl_tmp[offset] = 100 * deviation / max;
+            }
+        }
+
+        FTS_TEST_SAVE_INFO("Rx Linearity:\n");
+        show_data(rl_tmp, false);
+        FTS_TEST_SAVE_INFO("\n");
+
+        /* compare */
+        result2 = compare_array(rl_tmp,
+                                thr->rx_linearity_min,
+                                thr->rx_linearity_max,
+                                false);
+        rl_cnt += tdata->node.node_num;
+    }
+
+    result3 = true;
+    if (thr->basic.uniformity_check_min_max) {
+        FTS_TEST_SAVE_INFO("Check Min/Max\n") ;
+        min = 100000;
+        max = -100000;
+        for (i = 0; i < tdata->node.node_num; i++) {
+            if (0 == tdata->node_valid[i])
+                continue;
+            min = min(min, rawdata[i]);
+            max = max(max, rawdata[i]);
+        }
+        max = !max ? 1 : max;
+        uniform = 100 * abs(min) / abs(max);
+
+        FTS_TEST_SAVE_INFO("min:%d, max:%d, get value of min/max:%d\n",
+                           min, max, uniform);
+        if (uniform < thr->basic.uniformity_min_max_hole) {
+            result3 = false;
+            FTS_TEST_SAVE_ERR("min_max out of range, set value: %d\n",
+                              thr->basic.uniformity_min_max_hole);
+        }
+    }
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+test_err:
+    if (result && result2 && result3) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("====== uniformity test PASS");
+        if (tdata->s) seq_printf(tdata->s, "------ uniformity test is Pass\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("====== uniformity test NG");
+        if (tdata->s) seq_printf(tdata->s, "------ uniformity test is NG\n");
+    }
+
+    fts_test_save_data("Rawdata Uniformity Test",
+                       CODE_M_RAWDATA_UNIFORMITY_TEST, rawdata_linearity,
+                       tdata->node.node_num * 2, false, false, *test_result);
+
+    fts_free(rawdata_linearity);
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5652_scap_cb_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    u8 wc_sel = 0;
+    u8 sc_mode = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    int byte_num = 0;
+    bool tmp_result = false;
+    bool tmp2_result = false;
+    bool tmp3_result = false;
+    bool tmp4_result = false;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scap_cb = NULL;
+    int *scb_tmp = NULL;
+    int scb_cnt = 0;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("====== Test Item: Scap CB Test");
+    memset(tdata->buffer, 0, tdata->buffer_length);
+    scap_cb = tdata->buffer;
+    byte_num = tdata->sc_node.node_num * 2;
+
+    if (tdata->sc_node.node_num * 4 > tdata->buffer_length) {
+        FTS_TEST_SAVE_ERR("scap cb num(%d) > buffer length(%d)",
+                          tdata->sc_node.node_num * 4,
+                          tdata->buffer_length);
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    if (!thr->scap_cb_on_min || !thr->scap_cb_on_max
+        || !thr->scap_cb_off_min || !thr->scap_cb_off_max
+        || !thr->scap_cb_hi_min || !thr->scap_cb_hi_max
+        || !thr->scap_cb_hov_min || !thr->scap_cb_hov_max) {
+        FTS_TEST_SAVE_ERR("scap_cb_on/off/hi/hov_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* SCAP CB is in no-mapping mode */
+    ret = mapping_switch(NO_MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch no-mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_MC_SC_MODE, &sc_mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read sc_mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (thr->basic.scap_cb_wp_on_check && fw_wp_check) {
+        scb_tmp = scap_cb + scb_cnt;
+        /* 1:waterproof 0:non-waterproof */
+        ret = get_cb_mc_sc(WATER_PROOF_ON, byte_num, scb_tmp, DATA_TWO_BYTE);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto restore_reg;
+        }
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in waterproof on mode:\n");
+        show_data_mc_sc(scb_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        tmp_result = compare_mc_sc(tx_check, rx_check, scb_tmp,
+                                   thr->scap_cb_on_min,
+                                   thr->scap_cb_on_max);
+
+        scb_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp_result = true;
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (thr->basic.scap_cb_wp_off_check && fw_wp_check) {
+        scb_tmp = scap_cb + scb_cnt;
+        /* 1:waterproof 0:non-waterproof */
+        ret = get_cb_mc_sc(WATER_PROOF_OFF, byte_num, scb_tmp, DATA_TWO_BYTE);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto restore_reg;
+        }
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in waterproof off mode:\n");
+        show_data_mc_sc(scb_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        tmp2_result = compare_mc_sc(tx_check, rx_check, scb_tmp,
+                                    thr->scap_cb_off_min,
+                                    thr->scap_cb_off_max);
+
+        scb_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp2_result = true;
+    }
+
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    if (thr->basic.scap_cb_hi_check && hov_high) {
+        scb_tmp = scap_cb + scb_cnt;
+        /* 1:waterproof 0:non-waterproof */
+        ret = get_cb_mc_sc(HIGH_SENSITIVITY, byte_num, scb_tmp, DATA_TWO_BYTE);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto restore_reg;
+        }
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in high mode:\n");
+        show_data_mc_sc(scb_tmp);
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        tmp3_result = compare_mc_sc(tx_check, rx_check, scb_tmp,
+                                    thr->scap_cb_hi_min,
+                                    thr->scap_cb_hi_max);
+
+        scb_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp3_result = true;
+    }
+
+    /*hov mode*/
+    hov_high = (hc_sel & 0x04);
+    if (thr->basic.scap_cb_hov_check && hov_high) {
+        scb_tmp = scap_cb + scb_cnt;
+        byte_num = 4 * 2;
+        /* 1:waterproof 0:non-waterproof */
+        ret = get_cb_mc_sc(HOV, byte_num, scb_tmp, DATA_TWO_BYTE);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto restore_reg;
+        }
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in hov mode:\n");
+        show_data_mc_sc(scb_tmp);
+
+        /* compare */
+        tmp4_result = true;
+        for (i = 0; i < 4; i++) {
+            if ((scb_tmp[i] < thr->scap_cb_hov_min[i])
+                || (scb_tmp[i] > thr->scap_cb_hov_max[i])) {
+                FTS_TEST_SAVE_ERR("test fail,hov%d=%5d,range=(%5d,%5d)\n",
+                                  i + 1, scb_tmp[i],
+                                  thr->scap_cb_hov_min[i],
+                                  thr->scap_cb_hov_max[i]);
+                tmp4_result = false;
+            }
+        }
+
+        scb_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp4_result = true;
+    }
+
+restore_reg:
+    ret = fts_test_write_reg(FACTORY_REG_WC_SEL, wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore water_channel_sel fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, sc_mode);/* set the origin value */
+    if (ret) {
+        FTS_TEST_SAVE_ERR("restore sc mode fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_HC_SEL, hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore high_channel_sel fail,ret=%d\n", ret);
+    }
+
+test_err:
+    if (tmp_result && tmp2_result && tmp3_result && tmp4_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("====== scap cb test PASS");
+        if (tdata->s) seq_printf(tdata->s, "------ scap cb test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("====== scap cb test NG");
+        if (tdata->s) seq_printf(tdata->s, "------ scap cb test NG\n");
+    }
+
+    /* save test data */
+    fts_test_save_data("SCAP CB Test", CODE_M_SCAP_CB_TEST,
+                       scap_cb, scb_cnt, true, false, *test_result);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5652_scap_rawdata_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    bool tmp_result = false;
+    bool tmp2_result = false;
+    bool tmp3_result = false;
+    bool tmp4_result = false;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scap_rawdata = NULL;
+    int *srawdata_tmp = NULL;
+    int srawdata_cnt = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    u8 data_type = 0;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("====== Test Item: Scap Rawdata Test");
+    memset(tdata->buffer, 0, tdata->buffer_length);
+    scap_rawdata = tdata->buffer;
+
+    if ((tdata->sc_node.node_num * 4) > tdata->buffer_length) {
+        FTS_TEST_SAVE_ERR("scap rawdata num(%d) > buffer length(%d)",
+                          tdata->sc_node.node_num * 4,
+                          tdata->buffer_length);
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    if (!thr->scap_rawdata_on_min || !thr->scap_rawdata_on_max
+        || !thr->scap_rawdata_off_min || !thr->scap_rawdata_off_max
+        || !thr->scap_rawdata_hi_min || !thr->scap_rawdata_hi_max
+        || !thr->scap_rawdata_hov_min || !thr->scap_rawdata_hov_max) {
+        FTS_TEST_SAVE_ERR("scap_rawdata_on/off/hi/hov_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* SCAP RAWDATA is in no-mapping mode */
+    ret = mapping_switch(NO_MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch no-mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* scan rawdata */
+    ret = start_scan();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scan scap rawdata fail\n");
+        goto restore_reg;
+    }
+
+    ret = start_scan();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scan scap rawdata(2) fail\n");
+        goto restore_reg;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (thr->basic.scap_rawdata_wp_on_check && fw_wp_check) {
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(WATER_PROOF_ON, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_ON) rawdata fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in waterproof on mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        tmp_result = compare_mc_sc(tx_check, rx_check, srawdata_tmp,
+                                   thr->scap_rawdata_on_min,
+                                   thr->scap_rawdata_on_max);
+
+        srawdata_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp_result = true;
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (thr->basic.scap_rawdata_wp_off_check && fw_wp_check) {
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(WATER_PROOF_OFF, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_OFF) rawdata fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in waterproof off mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        tmp2_result = compare_mc_sc(tx_check, rx_check, srawdata_tmp,
+                                    thr->scap_rawdata_off_min,
+                                    thr->scap_rawdata_off_max);
+
+        srawdata_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp2_result = true;
+    }
+
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    if (thr->basic.scap_rawdata_hi_check && hov_high) {
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(HIGH_SENSITIVITY, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(HS) rawdata fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in hs mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        tmp3_result = compare_mc_sc(tx_check, rx_check, srawdata_tmp,
+                                    thr->scap_rawdata_hi_min,
+                                    thr->scap_rawdata_hi_max);
+
+        srawdata_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp3_result = true;
+    }
+
+    /*hov mode*/
+    hov_high = (hc_sel & 0x04);
+    if (thr->basic.scap_rawdata_hov_check && hov_high) {
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(HOV, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(HOV) rawdata fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in hov mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tmp4_result = true;
+        for (i = 0; i < 4; i++) {
+            if ((srawdata_tmp[i] < thr->scap_rawdata_hov_min[i])
+                || (srawdata_tmp[i] > thr->scap_rawdata_hov_max[i])) {
+                FTS_TEST_SAVE_ERR("test fail,hov%d=%5d,range=(%5d,%5d)\n",
+                                  i + 1, srawdata_tmp[i],
+                                  thr->scap_rawdata_hov_min[i],
+                                  thr->scap_rawdata_hov_max[i]);
+                tmp4_result = false;
+            }
+        }
+
+        srawdata_cnt += tdata->sc_node.node_num;
+    } else {
+        tmp4_result = true;
+    }
+
+restore_reg:
+    ret = fts_test_write_reg(FACTORY_REG_WC_SEL, wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore water_channel_sel fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_HC_SEL, hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore high_channel_sel fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore raw type fail,ret=%d\n", ret);
+    }
+
+test_err:
+    if (tmp_result && tmp2_result && tmp3_result && tmp4_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("====== scap rawdata test PASS");
+        if (tdata->s) seq_printf(tdata->s, "------ scap rawdata test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_INFO("====== scap rawdata test NG");
+        if (tdata->s) seq_printf(tdata->s, "------ scap rawdata test NG\n");
+    }
+
+    /* save data */
+    fts_test_save_data("SCAP Rawdata Test", CODE_M_SCAP_RAWDATA_TEST,
+                       scap_rawdata, srawdata_cnt, true, false, *test_result);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5652_short_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int ch_num = 0;
+    int adc[SC_NUM_MAX + 1] = { 0 };
+    u8 ab_ch[SC_NUM_MAX + 1] = { 0 };
+    u8 res_level = 0;
+    bool ca_result = false;
+    bool cg_result = false;
+    bool cc_result = false;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("====== Test Item: Short Test");
+    ch_num = tdata->sc_node.tx_num + tdata->sc_node.rx_num;
+
+    if (ch_num >= SC_NUM_MAX) {
+        FTS_TEST_SAVE_ERR("sc_node ch_num(%d)>max(%d)", ch_num, SC_NUM_MAX);
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* short is in no-mapping mode */
+    ret = mapping_switch(NO_MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch no-mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTROY_REG_SHORT2_RES_LEVEL, &res_level);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read short level fails\n");
+        goto test_err;
+    }
+
+    /* get offset = readdata - 1024 */
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, 1 * 2, &tdata->offset, \
+                                FACTROY_REG_SHORT2_OFFSET);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        goto test_err;
+    }
+    tdata->offset -= 1024;
+    FTS_TEST_DBG("short offset:%d", tdata->offset);
+
+    /* get short resistance and exceptional channel */
+    ret = short_test_ch_to_all(tdata, adc, ab_ch, &ca_result);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("short test of channel to all fails\n");
+        goto restore_reg;
+    }
+
+    if (!ca_result) {
+        /*weak short fail, get short values*/
+        ret = short_test_ch_to_gnd(tdata, adc, ab_ch, &cg_result);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("short test of channel to gnd fails\n");
+            goto restore_reg;
+        }
+
+        ret = short_test_ch_to_ch(tdata, adc, ab_ch, &cc_result);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("short test of channel to channel fails\n");
+            goto restore_reg;
+        }
+
+    }
+
+restore_reg:
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_RES_LEVEL, res_level);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore short level fails");
+    }
+
+test_err:
+    if (ca_result) {
+        FTS_TEST_SAVE_INFO("====== short test PASS");
+        if (tdata->s) seq_printf(tdata->s, "------ short test PASS\n");
+        *test_result = true;
+    } else {
+        FTS_TEST_SAVE_ERR("====== short test NG");
+        if (tdata->s) seq_printf(tdata->s, "------ short test NG\n");
+        *test_result = false;
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5652_panel_differ_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    bool tmp_result = false;
+    int i = 0;
+    u8 fre = 0;
+    u8 fir = 0;
+    u8 normalize = 0;
+    u8 data_type = 0;
+    u8 data_sel = 0;
+    int *panel_differ = NULL;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("====== Test Item: Panel Differ Test");
+    memset(tdata->buffer, 0, tdata->buffer_length);
+    panel_differ = tdata->buffer;
+
+    if (!thr->panel_differ_min || !thr->panel_differ_max) {
+        FTS_TEST_SAVE_ERR("panel_differ_h_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* panel differ test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_NORMALIZE, &normalize);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read normalize fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FIR, &fir);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0xFB fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x06 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set to overall normalize */
+    ret = fts_test_write_reg(FACTORY_REG_NORMALIZE, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write normalize fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /* fir disable */
+    ret = fts_test_write_reg(FACTORY_REG_FIR, 0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data sel fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /* get rawdata */
+    for (i = 0; i < 3; i++) {
+        ret = get_rawdata(panel_differ);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get rawdata fail\n");
+            goto restore_reg;
+        }
+    }
+
+    for (i = 0; i < tdata->node.node_num; i++) {
+        panel_differ[i] = panel_differ[i] / 10;
+    }
+
+    /* show test data */
+    show_data(panel_differ, false);
+
+    /* compare */
+    tmp_result = compare_array(panel_differ,
+                               thr->panel_differ_min,
+                               thr->panel_differ_max,
+                               false);
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_NORMALIZE, normalize);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore normalize fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FIR, fir);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0xFB fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data sel fail,ret=%d\n", ret);
+    }
+
+    if (tdata->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+test_err:
+    /* result */
+    if (tmp_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("====== panel differ test PASS");
+        if (tdata->s) seq_printf(tdata->s, "------ panel differ test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("====== panel differ test NG");
+        if (tdata->s) seq_printf(tdata->s, "------ panel differ test NG\n");
+    }
+
+    /* save test data */
+    fts_test_save_data("Panel Differ Test", CODE_M_PANELDIFFER_TEST,
+                       panel_differ, 0, false, false, *test_result);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int start_test_ft5652(void)
+{
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_testitem *test_item = &tdata->ic.mc_sc.u.item;
+    bool temp_result = false;
+    bool test_result = true;
+    u8 state = 0xFF;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_INFO("test item:0x%x", tdata->ic.mc_sc.u.tmp);
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    tdata->func->param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", tdata->func->param_update_support);
+
+
+    /* rawdata test */
+    if (test_item->rawdata_test == true) {
+        ret = ft5652_rawdata_test(tdata, &temp_result);
+        if ((ret < 0) || (temp_result == false)) {
+            test_result = false;
+        }
+    }
+
+    if (test_item->rawdata_uniformity_test == true) {
+        ret = ft5652_uniformity_test(tdata, &temp_result);
+        if ((ret < 0) || (temp_result == false)) {
+            test_result = false;
+        }
+    }
+
+    /* scap_cb test */
+    if (test_item->scap_cb_test == true) {
+        ret = ft5652_scap_cb_test(tdata, &temp_result);
+        if ((ret < 0) || (temp_result == false)) {
+            test_result = false;
+        }
+    }
+
+    /* scap_rawdata test */
+    if (test_item->scap_rawdata_test == true) {
+        ret = ft5652_scap_rawdata_test(tdata, &temp_result);
+        if ((ret < 0) || (temp_result == false)) {
+            test_result = false;
+        }
+    }
+
+    /* short test */
+    if (test_item->short_test == true) {
+        ret = ft5652_short_test(tdata, &temp_result);
+        if ((ret < 0) || (temp_result == false)) {
+            test_result = false;
+        }
+    }
+    /* panel differ test */
+    if (test_item->panel_differ_test == true) {
+        ret = ft5652_panel_differ_test(tdata, &temp_result);
+        if ((ret < 0) || (temp_result == false)) {
+            test_result = false;
+        }
+    }
+
+    /* restore mapping state */
+    fts_test_write_reg(FACTORY_REG_NOMAPPING, tdata->mapping);
+
+    FTS_TEST_FUNC_EXIT();
+    return test_result;
+}
+
+struct test_funcs test_func_ft5652 = {
+    .ctype = {0x88},
+    .hwtype = IC_HW_MC_SC,
+    .key_num_total = 0,
+    .mc_sc_short_v2 = true,
+    .raw_u16 = true,
+    .cb_high_support = true,
+    .param_update_support = false,
+    .start_test = start_test_ft5652,
+};
+
+static int get_cb(int *cb_buf, int byte_num)
+{
+    int ret = 0;
+    int i = 0;
+    int read_num = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    u8 *cb = NULL;
+
+    cb = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (cb == NULL) {
+        FTS_TEST_SAVE_ERR("malloc memory for cb buffer fail\n");
+        return -ENOMEM;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+    read_num = BYTES_PER_TIME;
+    offset = 0;
+
+    FTS_TEST_INFO("cb packet:%d,remainder:%d", packet_num, packet_remainder);
+    for (i = 0; i < packet_num; i++) {
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            read_num = packet_remainder;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_CB_ADDR_OFF, offset);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("write cb addr offset fail\n");
+            fts_free(cb);
+            return ret;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_CB_H_ADDR_OFF, offset >> 8);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("write cb_h addr offset fail\n");
+            fts_free(cb);
+            return ret;
+        }
+
+        ret = fts_test_read(FACTORY_REG_MC_SC_CB_ADDR, cb + offset, read_num);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read cb fail\n");
+            fts_free(cb);
+            return ret;
+        }
+
+        offset += read_num;
+    }
+
+    for (i = 0; i < byte_num; i = i + 2) {
+        cb_buf[i >> 1] = (int)(short)((cb[i] << 8) + cb[i + 1]);
+    }
+
+    fts_free(cb);
+    return ret;
+}
+
+static int get_short_adc(int *adc_buf, int byte_num, u8 mode)
+{
+    int ret = 0;
+    int i = 0;
+    u8 short_state = 0;
+
+    FTS_TEST_FUNC_ENTER();
+    /* select short test mode & start test */
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_TEST_EN, mode);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write short test mode fail\n");
+        return ret;
+    }
+
+    for (i = 0; i < FACTORY_TEST_RETRY; i++) {
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+
+        ret = fts_test_read_reg(FACTROY_REG_SHORT2_TEST_STATE, &short_state);
+        if ((ret >= 0) && (TEST_RETVAL_AA == short_state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", FACTROY_REG_SHORT2_TEST_STATE,
+                short_state, i);
+    }
+    if (i >= FACTORY_TEST_RETRY) {
+        FTS_TEST_ERROR("short test timeout, ADC data not OK\n");
+        ret = -EIO;
+        return ret;
+    }
+
+    ret = read_mass_data(FACTORY_REG_SHORT2_ADDR_MC, byte_num, adc_buf);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get short(adc) data fail\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_test_get_raw_restore_reg(u8 fre, u8 data_sel, u8 data_type) {
+    int ret = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_FUNC_ENTER();
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x5B fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+int fts_test_get_raw(int *raw, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int times = 0;
+    int node_num = tx * rx;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    u8 val = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: rawdata test start\n");
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x0A fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x5B fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x06 fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set frequency fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+            return ret;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set fir fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+            return ret;
+        }
+    }
+
+    /*********************GET RAWDATA*********************/
+    for (i = 0; i < 3; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            continue;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2),
+              raw);
+    }
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+    FTS_TEST_INFO("====== Test Item: rawdata test end\n");
+    return ret;
+}
+
+int fts_test_get_baseline(int *raw,int *base_raw, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int times = 0;
+    int node_num = tx * rx;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    u8 val = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: baseline test start\n");
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_ERROR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (0xAA == state);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_ERROR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x06 error,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set frequency fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set fir fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /*********************GET RAWDATA*********************/
+    FTS_TEST_INFO("get rawdata,i=%d", i);
+    ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write start scan mode fail\n");
+    }
+
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+        ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+        if ((ret >= 0) && (val == 0x40)) {
+            break;
+        } else {
+            FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+        }
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_ERROR("scan timeout\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write line/start addr fail\n");
+    }
+
+    ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2),raw);
+
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x01);
+    if (ret < 0) {
+        goto restore_reg;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            goto restore_reg;
+        }
+    }
+
+    /*********************GET DATA*********************/
+    ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+
+        ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+        if ((ret >= 0) && (val == 0x40)) {
+            break;
+        } else {
+            FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+        }
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_ERROR("scan timeout\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write line/start addr fail\n");
+    }
+    ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), base_raw);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+test_err:
+    FTS_TEST_INFO("====== Test Item: baseline test end\n");
+    return ret;
+}
+
+int fts_test_get_strength(u8 *base_raw, u16 base_raw_size)
+{
+    int ret = 0;
+    u8 id_cmd[1] = {0};
+
+    FTS_TEST_INFO("====== Test Item: strength test start\n");
+    id_cmd[0] = FTS_CMD_READ_TOUCH_DATA;
+    sys_delay(500);
+    ret = fts_read(id_cmd, 1, base_raw, base_raw_size);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get strength fail,ret=%d\n", ret);
+    }
+
+    FTS_TEST_INFO("====== Test Item: strength test end\n");
+    return ret;
+}
+
+int fts_test_get_uniformity_data(int *rawdata_linearity, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int row = 0;
+    int col = 1;
+    int i = 0;
+    int deviation = 0;
+    int max = 0;
+    int *raw = NULL;
+    int *rl_tmp = NULL;
+    int offset = 0;
+    int offset2 = 0;
+    int node_num = tx * rx;
+    int times = 0;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    u8 val = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: rawdata unfiormity test start\n");
+
+    raw = fts_malloc(node_num * sizeof(int));
+    if (!raw) {
+        FTS_TEST_ERROR("raw buffer malloc fail");
+        return -ENOMEM;
+    }
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_ERROR("read FACTORY_REG_FRE_LIST fail,ret=%d\n", ret);
+        fts_free(raw);
+        return -ENOMEM;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_ERROR("read FACTORY_REG_DATA_TYPE fail,ret=%d\n", ret);
+        fts_free(raw);
+        return -ENOMEM;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_ERROR("read FACTORY_REG_DATA_SELECT fail,ret=%d\n", ret);
+        fts_free(raw);
+        return -ENOMEM;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set frequency fail,ret=%d\n", ret);
+        fts_free(raw);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            fts_free(raw);
+            goto exit;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set data select fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    for (i = 0; i < 3; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            continue;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), raw);
+    }
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    FTS_TEST_INFO("Check Tx Linearity\n");
+    rl_tmp = rawdata_linearity;
+    for (row = 0; row < tx; row++) {
+        for (col = 1; col <  rx; col++) {
+            offset = row * rx + col;
+            offset2 = row * rx + col - 1;
+            deviation = abs( raw[offset] - raw[offset2]);
+            max = max(raw[offset], raw[offset2]);
+            max = max ? max : 1;
+            rl_tmp[offset] = 100 * deviation / max;
+        }
+    }
+
+    FTS_TEST_INFO("Check Rx Linearity\n");
+    rl_tmp = rawdata_linearity + node_num;
+    for (row = 1; row < tx; row++) {
+        for (col = 0; col < rx; col++) {
+            offset = row * rx + col;
+            offset2 = (row - 1) * rx + col;
+            deviation = abs(raw[offset] - raw[offset2]);
+            max = max(raw[offset], raw[offset2]);
+            max = max ? max : 1;
+            rl_tmp[offset] = 100 * deviation / max;
+        }
+    }
+
+exit:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    fts_free(raw);
+    FTS_TEST_INFO("====== Test Item: rawdata unfiormity test end\n");
+    return ret;
+}
+
+int fts_test_get_scap_cb(int *scap_cb, u8 tx, u8 rx, int *fwcheck)
+{
+    int ret = 0;
+    u8 wc_sel = 0;
+    u8 sc_mode = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    int node_num = (tx + rx);
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scb_tmp = NULL;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: Scap CB Test start\n");
+    *fwcheck = 0;
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read water_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_MC_SC_MODE, &sc_mode);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read sc_mode fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read high_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (fw_wp_check) {
+        scb_tmp = scap_cb;
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, WATER_PROOF_ON);
+        if (ret < 0) {
+            FTS_TEST_ERROR("set mc_sc mode fail\n");
+            goto exit;
+        }
+
+        if (param_update_support) {
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto exit;
+            }
+        }
+
+        ret = get_cb(scb_tmp, node_num * 2);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get sc cb fail\n");
+            goto exit;
+        }
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        *fwcheck |= (rx_check ? 0x01 : 0x00);
+        *fwcheck |= (tx_check ? 0x02 : 0x00);
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (fw_wp_check) {
+        scb_tmp = scap_cb + node_num;
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, WATER_PROOF_OFF);
+        if (ret < 0) {
+            FTS_TEST_ERROR("set mc_sc mode fail\n");
+            goto exit;
+        }
+
+        if (param_update_support) {
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto exit;
+            }
+        }
+
+        ret = get_cb(scb_tmp, node_num * 2);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get sc cb fail\n");
+            goto exit;
+        }
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        *fwcheck |= (rx_check ? 0x04 : 0x00);
+        *fwcheck |= (tx_check ? 0x08 : 0x00);
+    }
+
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    if (hov_high) {
+        scb_tmp = scap_cb + node_num * 2;
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, HIGH_SENSITIVITY);
+        if (ret < 0) {
+            FTS_TEST_ERROR("set mc_sc mode fail\n");
+            goto exit;
+        }
+
+        if (param_update_support) {
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto exit;
+            }
+        }
+
+        ret = get_cb(scb_tmp, node_num * 2);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get sc cb fail\n");
+            goto exit;
+        }
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        *fwcheck |= (rx_check ? 0x10 : 0x00);
+        *fwcheck |= (tx_check ? 0x20 : 0x00);
+    }
+
+exit:
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, sc_mode);/* set the origin value */
+    if (ret) {
+        FTS_TEST_ERROR("restore sc mode fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    FTS_TEST_INFO("====== Test Item: Scap CB Test end\n");
+    return ret;
+}
+
+int fts_test_get_scap_raw(int *scap_raw, u8 tx, u8 rx, int *fwcheck)
+{
+    int ret = 0;
+    int i = 0;
+    int times = 0;
+    int node_num = tx + rx;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *srawdata_tmp = NULL;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    u8 data_type = 0;
+    u8 val = 0;
+
+    FTS_TEST_INFO("====== Test Item: Scap Rawdata Test start\n");
+    *fwcheck = 0;
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read water_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read high_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x5B fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    /* scan rawdata 2 times*/
+    for (i = 0; i < 2; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+    }
+    if (ret < 0) {
+        FTS_TEST_ERROR("scan scap rawdata fail\n");
+        goto exit;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (fw_wp_check) {
+        srawdata_tmp = scap_raw;
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAC);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            goto exit;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get scap(WP_ON) rawdata fail\n");
+            goto exit;
+        }
+
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        *fwcheck |= (rx_check ? 0x01 : 0x00);
+        *fwcheck |= (tx_check ? 0x02 : 0x00);
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (fw_wp_check) {
+        srawdata_tmp = scap_raw + node_num;
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAB);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            goto exit;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get scap(WP_OFF) rawdata fail\n");
+            goto exit;
+        }
+
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        *fwcheck |= (rx_check ? 0x04 : 0x00);
+        *fwcheck |= (tx_check ? 0x08 : 0x00);
+    }
+
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    if (hov_high) {
+        srawdata_tmp = scap_raw + node_num * 2;
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xA0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            goto exit;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get scap(HIGH) rawdata fail\n");
+            goto exit;
+        }
+
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        *fwcheck |= (rx_check ? 0x10 : 0x00);
+        *fwcheck |= (tx_check ? 0x20 : 0x00);
+    }
+
+exit:
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+    }
+
+    FTS_TEST_INFO("====== Test Item: Scap Rawdata Test end\n");
+    return ret;
+}
+
+static int fts_test_get_short_restore_reg(u8 res_level) {
+    int ret = 0;
+
+    FTS_TEST_FUNC_ENTER();
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_RES_LEVEL, res_level);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore FACTROY_REG_SHORT2_RES_LEVEL level fails");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+int fts_test_get_short(int *short_data, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int ch_num = (tx + rx);
+    int offset = 0;
+    int code = 0;
+    int denominator = 0;
+    int numerator = 0;
+    u8 res_level = 0;
+
+    FTS_TEST_INFO("====== Test Item: Short Test start");
+
+    ret = fts_test_read_reg(FACTROY_REG_SHORT2_RES_LEVEL, &res_level);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read short level fails\n");
+        return ret;
+    }
+
+    /* get offset = readdata - 1024 */
+    ret = get_short_adc(&offset, 1 * 2, FACTROY_REG_SHORT2_OFFSET);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get weak short data fail,ret:%d\n", ret);
+        fts_test_get_short_restore_reg(res_level);
+        return ret;
+    }
+    offset -= 1024;
+    FTS_TEST_INFO("short offset:%d", offset);
+
+    /* get short resistance and exceptional channel */
+    /* choose resistor_level */
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_RES_LEVEL, 1);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write short resistor level fail\n");
+        fts_test_get_short_restore_reg(res_level);
+        return ret;
+    }
+
+    /* get adc data */
+    ret = get_short_adc(short_data, ch_num * 2, FACTROY_REG_SHORT2_CA);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get weak short data fail,ret:%d\n", ret);
+        fts_test_get_short_restore_reg(res_level);
+        return ret;
+    }
+
+    for (i = 0; i < ch_num; i++) {
+        code = short_data[i];
+        denominator = 1407 - code + offset;
+        if (denominator == 0) {
+            short_data[i] = 2000;
+        } else {
+            numerator = (code - offset + 395) * 112;
+            short_data[i] = fts_abs(numerator / denominator - 3);
+        }
+    }
+
+    ret = fts_test_get_short_restore_reg(res_level);
+
+    FTS_TEST_INFO("====== Test Item: Short Test end");
+    return ret;
+}
+
+int fts_test_get_noise(int *noise, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int node_num = (tx * rx);
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u16 noise_frame = 20;
+    u8 noise_mode = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: Noise test start");
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read FACTORY_REG_DATA_SELECT error,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read FACTORY_REG_FRE_LIST fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_MAXDIFF_EN, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1A fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRAME_NUM_H, (noise_frame >> 8));
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1C fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRAME_NUM_L, noise_frame);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1D fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    noise_mode = 1;
+    FTS_TEST_INFO("noise_mode = %x\n", noise_mode);
+    ret = fts_test_write_reg(FACTORY_REG_MAXDIFF_FLAG, noise_mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1B fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = ft5652_start_scan(noise_frame);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("ft5652_start_scan fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x01 fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = read_mass_data(FACTORY_REG_NOISE_ADDR, (node_num * 2), noise);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read 0xCE fail\n");
+        return ret;
+    }
+
+exit:
+    ret = fts_test_write_reg(FACTORY_REG_MAXDIFF_FLAG, 0x0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x1B fail,ret=%d\n", ret);
+    }
+
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    FTS_TEST_INFO("====== Test Item: Noise test end");
+    return ret;
+}
+
+int fts_test_get_panel_differ(int *panel_differ, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = tx * rx;
+    int times = 0;
+    u8 val = 0;
+    u8 fre = 0;
+    u8 fir = 0;
+    u8 normalize = 0;
+    u8 data_type = 0;
+    u8 data_sel = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: Panel Differ Test start");
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_NORMALIZE, &normalize);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read normalize fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5B fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FIR, &fir);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0xFB fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x06 fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* set to overall normalize */
+    ret = fts_test_write_reg(FACTORY_REG_NORMALIZE, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write normalize fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, 0x81);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    /* fir disable */
+    ret = fts_test_write_reg(FACTORY_REG_FIR, 0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data sel fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    for (i = 0; i < 3; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            continue;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), panel_differ);
+    }
+
+    if (ret < 0) {
+        FTS_TEST_ERROR("get panel_differ fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    for (i = 0; i < node_num; i++) {
+        panel_differ[i] = panel_differ[i] / 10;
+    }
+
+exit:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_NORMALIZE, normalize);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore normalize fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FIR, fir);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0xFB fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data sel fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    FTS_TEST_INFO("====== Test Item: Panel Differ Test end");
+    return ret;
+}
diff --git a/ft3658/include/firmware/fw_sample.i b/ft3658/include/firmware/fw_sample.i
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ft3658/include/firmware/fw_sample.i
diff --git a/ft3683u/BUILD.bazel b/ft3683u/BUILD.bazel
new file mode 100644
index 0000000..060f57f
--- /dev/null
+++ b/ft3683u/BUILD.bazel
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+load("//build/kernel/kleaf:kernel.bzl", "kernel_module")
+
+kernel_module(
+    name = "ft3683u",
+    srcs = glob([
+        "**/*.c",
+        "**/*.h",
+    ]) + [
+        "Kbuild",
+        "//private/google-modules/soc/gs:gs_soc_headers",
+        "//private/google-modules/touch/common:headers",
+    ],
+    outs = [
+        "focal_touch.ko",
+    ],
+    kernel_build = "//private/devices/google/common:kernel",
+    visibility = [
+        "//private/devices/google:__subpackages__",
+        "//private/google-modules/soc/gs:__pkg__",
+    ],
+    deps = [
+        "//private/google-modules/soc/gs:gs_soc_module",
+        "//private/google-modules/touch/common:touch.common",
+    ],
+)
diff --git a/ft3683u/Kbuild b/ft3683u/Kbuild
new file mode 100644
index 0000000..856acab
--- /dev/null
+++ b/ft3683u/Kbuild
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+ccflags-y += -I$(srctree)/../private/google-modules/touch/common
+ccflags-y += -I$(srctree)/../private/google-modules/touch/common/include
+
+obj-$(CONFIG_TOUCHSCREEN_FTS) = focal_touch.o
+focal_touch-objs += focaltech_core.o focaltech_ex_fun.o focaltech_ex_mode.o \
+	focaltech_gesture.o focaltech_esdcheck.o focaltech_point_report_check.o \
+	focaltech_test/focaltech_test.o focaltech_test/focaltech_test_ini.o \
+	focaltech_test/supported_ic/focaltech_test_ft3683u.o \
+	focaltech_flash.o \
+	focaltech_flash/focaltech_upgrade_ft3683u.o \
+	focaltech_spi.o
+
+ifneq ($(CONFIG_GOOG_TOUCH_INTERFACE),)
+focal_touch-objs += focaltech_goog.o
+endif
diff --git a/ft3683u/Kconfig b/ft3683u/Kconfig
new file mode 100644
index 0000000..27ea952
--- /dev/null
+++ b/ft3683u/Kconfig
@@ -0,0 +1,16 @@
+#
+# Focaltech Touchscreen driver configuration
+#
+
+config TOUCHSCREEN_FTS
+    bool "Focaltech Touchscreen"
+    default n
+    help
+      Say Y here if you have Focaltech touch panel.
+      If unsure, say N.
+      
+config TOUCHSCREEN_FTS_DIRECTORY
+    string "Focaltech ts directory name"
+    default "focaltech_touch"
+    depends on TOUCHSCREEN_FTS
+    
\ No newline at end of file
diff --git a/ft3683u/Makefile b/ft3683u/Makefile
new file mode 100644
index 0000000..d4f5307
--- /dev/null
+++ b/ft3683u/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Makefile for the focaltech touchscreen drivers.
+
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+KBUILD_OPTIONS	+= CONFIG_TOUCHSCREEN_FTS=m
+EXTRA_CFLAGS	+= -DDYNAMIC_DEBUG_MODULE
+EXTRA_SYMBOLS	+= $(OUT_DIR)/../private/google-modules/touch/common/Module.symvers
+
+include $(KERNEL_SRC)/../private/google-modules/soc/gs/Makefile.include
+
+modules modules_install clean:
+	$(MAKE) -C $(KERNEL_SRC) M=$(M) \
+	$(KBUILD_OPTIONS) \
+	EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
+	KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)" \
+	$(@)
+
diff --git a/ft3683u/focaltech_common.h b/ft3683u/focaltech_common.h
new file mode 100644
index 0000000..b16061d
--- /dev/null
+++ b/ft3683u/focaltech_common.h
@@ -0,0 +1,252 @@
+/*
+ *
+ * FocalTech fts TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/*****************************************************************************
+*
+* File Name: focaltech_common.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-16
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+#ifndef __LINUX_FOCALTECH_COMMON_H__
+#define __LINUX_FOCALTECH_COMMON_H__
+
+#include "focaltech_config.h"
+
+/*****************************************************************************
+* Macro definitions using #define
+*****************************************************************************/
+#define FTS_DRIVER_VERSION                  "Focaltech V3.3 20201229"
+
+#define BYTE_OFF_0(x)           (u8)((x) & 0xFF)
+#define BYTE_OFF_8(x)           (u8)(((x) >> 8) & 0xFF)
+#define BYTE_OFF_16(x)          (u8)(((x) >> 16) & 0xFF)
+#define BYTE_OFF_24(x)          (u8)(((x) >> 24) & 0xFF)
+#define FLAGBIT(x)              (0x00000001 << (x))
+#define FLAGBITS(x, y)          ((0xFFFFFFFF >> (32 - (y) - 1)) & (0xFFFFFFFF << (x)))
+
+#define FLAG_ICSERIALS_LEN      8
+#define FLAG_HID_BIT            10
+#define FLAG_IDC_BIT            11
+
+#define IC_SERIALS              (FTS_CHIP_TYPE & FLAGBITS(0, FLAG_ICSERIALS_LEN-1))
+#define IC_TO_SERIALS(x)        ((x) & FLAGBITS(0, FLAG_ICSERIALS_LEN-1))
+#define FTS_CHIP_IDC            ((FTS_CHIP_TYPE & FLAGBIT(FLAG_IDC_BIT)) == FLAGBIT(FLAG_IDC_BIT))
+#define FTS_HID_SUPPORTTED      ((FTS_CHIP_TYPE & FLAGBIT(FLAG_HID_BIT)) == FLAGBIT(FLAG_HID_BIT))
+
+#define FTS_MAX_CHIP_IDS        8
+#define FTS_RESET_INTERVAL      200
+
+#define FTS_CHIP_TYPE_MAPPING   {{0x90, 0x56, 0x72, 0x00, 0x00, 0x00, 0x00, 0x36, 0xB3}}
+
+#define FTS_STTW_E5_BUF_LEN                 14
+#define FTS_LPTW_E3_BUF_LEN                 12
+#define FTS_LPTW_E4_BUF_LEN                 20
+#define FTS_LPTW_BUF_LEN                    (max(FTS_LPTW_E3_BUF_LEN, FTS_LPTW_E4_BUF_LEN))
+
+#define FILE_NAME_LENGTH                    128
+#define FTS_MESSAGE_LENGTH                  128
+#define ENABLE                              1
+#define DISABLE                             0
+#define VALID                               1
+#define INVALID                             0
+#define MAX_RETRY_CNT                       3
+#define FTS_CMD_START1                      0x55
+#define FTS_CMD_START2                      0xAA
+#define FTS_CMD_START_DELAY                 12
+#define FTS_CMD_READ_ID                     0x90
+#define FTS_CMD_READ_ID_LEN                 4
+#define FTS_CMD_READ_ID_LEN_INCELL          1
+#define FTS_CMD_READ_FW_CONF                0xA8
+#define FTS_CMD_READ_TOUCH_DATA             0x01
+/*register address*/
+#define FTS_REG_INT_CNT                     0x8F
+#define FTS_REG_FLOW_WORK_CNT               0x91
+#define FTS_REG_WORKMODE                    0x00
+#define FTS_REG_WORKMODE_FACTORY_VALUE      0x40
+#define FTS_REG_WORKMODE_WORK_VALUE         0x00
+#define FTS_REG_ESDCHECK_DISABLE            0x8D
+#define FTS_REG_CHIP_ID                     0xA3
+#define FTS_REG_CHIP_ID2                    0x9F
+#define FTS_REG_POWER_MODE                  0xA5
+#define FTS_REG_POWER_MODE_SLEEP            0x03
+#define FTS_REG_FW_MAJOR_VER                0xA6
+#define FTS_REG_FW_MINOR_VER                0xAD
+#define FTS_REG_VENDOR_ID                   0xA8
+#define FTS_REG_LCD_BUSY_NUM                0xAB
+#define FTS_REG_FACE_DEC_MODE_EN            0xB0
+#define FTS_REG_FACTORY_MODE_DETACH_FLAG    0xB4
+#define FTS_REG_FACE_DEC_MODE_STATUS        0x01
+#define FTS_REG_IDE_PARA_VER_ID             0xB5
+#define FTS_REG_IDE_PARA_STATUS             0xB6
+#define FTS_REG_GLOVE_MODE_EN               0xC0
+#define FTS_REG_COVER_MODE_EN               0xC1
+#define FTS_REG_PALM_EN                     0xC5
+#define FTS_REG_CHARGER_MODE_EN             0x8B
+#define FTS_REG_EDGE_MODE_EN                0x8C
+#define FTS_REG_GESTURE_EN                  0xD0
+#define FTS_REG_GESTURE_OUTPUT_ADDRESS      0xD3
+#define FTS_REG_MODULE_ID                   0xE3
+#define FTS_REG_LIC_VER                     0xE4
+#define FTS_REG_ESD_SATURATE                0xED
+#define FTS_REG_GESTURE_SWITCH              0xCF
+#define FTS_REG_MONITOR_CTRL                0x86
+#define FTS_REG_SENSE_ONOFF                 0xEA
+#define FTS_REG_IRQ_ONOFF                   0xEB
+#define FTS_REG_INT2                        0xBF
+#define FTS_REG_CLR_RESET                   0xEC
+
+#define FTS_REG_WAKEUP                      0x95
+#define FTS_WAKEUP_VALUE                    0x55
+
+#define FTS_REG_HEATMAP_98                  0x98
+
+#define FTS_LPTW_REG_SET_E3                 0xE3
+#define FTS_LPTW_REG_SET_E4                 0xE4
+#define FTS_STTW_REG_SET_E5                 0xE5
+#define FTS_GESTURE_MAJOR_MINOR             0xE1
+#define FTS_REG_COORDINATE_FILTER          0xE6
+#define FTS_REG_CONTINUOUS_EN               0xE7
+
+#define FTS_REG_CUSTOMER_STATUS             0xB2    // follow FTS_CUSTOMER_STATUS.
+                                                    // bit 0~1 : HOPPING
+                                                    // bit 2   : PALM
+                                                    // bit 3   : WATER
+                                                    // bit 4   : GRIP
+                                                    // bit 5   : GLOVE
+                                                    // bit 6   : STTW
+                                                    // bit 7   : LPWG
+#define FTS_CAP_DATA_LEN                    (10 * 8 + 4)//84
+#define FTS_CAP_DUMMY_DATA_SIZE             29
+#define FTS_MUTUAL_DATA_SIZE                1152
+#define FTS_SELF_DATA_LEN                   61
+#define FTS_SELF_DATA_SIZE                  (FTS_SELF_DATA_LEN * 2) // 122
+#define FTS_FULL_TOUCH_DATA_SIZE            (FTS_CAP_DATA_LEN + \
+       FTS_CAP_DUMMY_DATA_SIZE + FTS_MUTUAL_DATA_SIZE + FTS_SELF_DATA_SIZE*2)
+#define FTS_FULL_TOUCH_RAW_SIZE(tx_num, rx_num) \
+    (FTS_CAP_DATA_LEN + FTS_CAP_DUMMY_DATA_SIZE + \
+     ((tx_num) * (rx_num) + FTS_SELF_DATA_LEN * 2) * sizeof(u16))
+
+#define FTS_PRESSURE_SCALE                  85      // 255 / 3
+#define FTS_ORIENTATION_SCALE               45
+#define FTS_GESTURE_ID_STTW                 0x25
+#define FTS_GESTURE_ID_LPTW_DOWN            0x26
+#define FTS_GESTURE_ID_LPTW_UP              0x27
+
+#define FTS_SYSFS_ECHO_ON(buf)      (buf[0] == '1')
+#define FTS_SYSFS_ECHO_OFF(buf)     (buf[0] == '0')
+
+#define kfree_safe(pbuf) do {\
+    if (pbuf) {\
+        kfree(pbuf);\
+        pbuf = NULL;\
+    }\
+} while(0)
+
+/*****************************************************************************
+*  Alternative mode (When something goes wrong, the modules may be able to solve the problem.)
+*****************************************************************************/
+/*
+ * point report check
+ * default: disable
+ */
+#define FTS_POINT_REPORT_CHECK_EN               0
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+struct ft_chip_t {
+    u16 type;
+    u8 chip_idh;
+    u8 chip_idl;
+    u8 rom_idh;
+    u8 rom_idl;
+    u8 pb_idh;
+    u8 pb_idl;
+    u8 bl_idh;
+    u8 bl_idl;
+};
+
+struct ft_chip_id_t {
+    u16 type;
+    u16 chip_ids[FTS_MAX_CHIP_IDS];
+};
+
+struct ts_ic_info {
+    bool is_incell;
+    bool hid_supported;
+    struct ft_chip_t ids;
+    struct ft_chip_id_t cid;
+};
+
+/* Firmware Grip suppression mode.
+ * 0 - Disable fw grip suppression.
+ * 1 - Enable fw grip suppression.
+ */
+enum FW_GRIP_MODE {
+    FW_GRIP_DISABLE,
+    FW_GRIP_ENABLE,
+};
+
+/* Firmware Heatmap mode.
+ * 0 - Disable fw heatmap.
+ * 1 - Enable fw Diff heatmap.
+ * 2 - Enable fw Baseline heatmap.
+ * 3 - Enable fw Rawdata heatmap.
+ */
+enum FW_HEATMAP_MODE {
+    FW_HEATMAP_MODE_DISABLE,
+    FW_HEATMAP_MODE_DIFF,
+    FW_HEATMAP_MODE_BASELINE,
+    FW_HEATMAP_MODE_RAWDATA,
+};
+
+/* Firmware Palm rejection mode.
+ * 0 - Disable fw palm rejection.
+ * 1 - Enable fw palm rejection.
+ */
+enum FW_PALM_MODE {
+    FW_PALM_DISABLE,
+    FW_PALM_ENABLE,
+};
+
+/*****************************************************************************
+* DEBUG function define here
+*****************************************************************************/
+#undef pr_fmt
+#define pr_fmt(fmt) "gtd: FTS_TS: " fmt
+#if FTS_DEBUG_EN
+#define FTS_DEBUG(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
+#define FTS_FUNC_ENTER() pr_debug("%s: Enter\n", __func__)
+#define FTS_FUNC_EXIT() pr_debug("%s: Exit(%d)\n", __func__, __LINE__)
+#else /* #if FTS_DEBUG_EN*/
+#define FTS_DEBUG(fmt, ...)
+#define FTS_FUNC_ENTER()
+#define FTS_FUNC_EXIT()
+#endif
+
+#define FTS_INFO(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
+#define FTS_ERROR(fmt, ...) pr_err(fmt, ##__VA_ARGS__)
+#define PR_LOGD(fmt, ...) pr_debug(fmt, ##__VA_ARGS__)
+
+#endif /* __LINUX_FOCALTECH_COMMON_H__ */
diff --git a/ft3683u/focaltech_config.h b/ft3683u/focaltech_config.h
new file mode 100644
index 0000000..915342e
--- /dev/null
+++ b/ft3683u/focaltech_config.h
@@ -0,0 +1,335 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/************************************************************************
+*
+* File Name: focaltech_config.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract: global configurations
+*
+* Version: v1.0
+*
+************************************************************************/
+#ifndef _LINUX_FOCLATECH_CONFIG_H_
+#define _LINUX_FOCLATECH_CONFIG_H_
+
+/*
+ * TODO:
+ * 1. b/196923176: WHI panel bridge porting for suspend/resume.
+ *
+ */
+#ifdef CONFIG_SOC_GOOGLE
+#undef FTS_DRM_BRIDGE
+#undef FTS_VFS_EN
+#undef CONFIG_FB
+#undef CONFIG_DRM_PANEL
+#undef CONFIG_HAS_EARLYSUSPEND
+#undef CONFIG_ARCH_QCOM
+#undef CONFIG_ARCH_MSM
+#endif
+
+/**************************************************/
+/****** G: A, I: B, S: C, U: D  ******************/
+/****** chip type defines, do not modify *********/
+#define _FT8716             0x87160805
+#define _FT8736             0x87360806
+#define _FT8607             0x86070809
+#define _FT8006U            0x8006D80B
+#define _FT8006S            0x8006A80B
+#define _FT8613             0x8613080C
+#define _FT8719             0x8719080D
+#define _FT8739             0x8739080E
+#define _FT8615             0x8615080F
+#define _FT8201             0x82010810
+#define _FT8201AA           0x8201A810
+#define _FT8006P            0x86220811
+#define _FT7251             0x72510812
+#define _FT7252             0x72520813
+#define _FT8613S            0x8613C814
+#define _FT8756             0x87560815
+#define _FT8302             0x83020816
+#define _FT8009             0x80090817
+#define _FT8656             0x86560818
+#define _FT8006S_AA         0x86320819
+#define _FT7250             0x7250081A
+#define _FT7120             0x7120081B
+#define _FT8720             0x8720081C
+#define _FT8726             0x8726081C
+#define _FT8720H            0x8720E81C
+#define _FT8720M            0x8720F81C
+#define _FT8016             0x8016081D
+#define _FT2388             0x2388081E
+#define _FT8006S_AB         0x8642081F
+#define _FT8722             0x87220820
+#define _FT8201AB           0x8201B821
+#define _FT8203             0x82030821
+
+
+#define _FT5416             0x54160402
+#define _FT5426             0x54260402
+#define _FT5435             0x54350402
+#define _FT5436             0x54360402
+#define _FT5526             0x55260402
+#define _FT5526I            0x5526B402
+#define _FT5446             0x54460402
+#define _FT5346             0x53460402
+#define _FT5446I            0x5446B402
+#define _FT5346I            0x5346B402
+#define _FT7661             0x76610402
+#define _FT7511             0x75110402
+#define _FT7421             0x74210402
+#define _FT7681             0x76810402
+#define _FT3C47U            0x3C47D402
+#define _FT3417             0x34170402
+#define _FT3517             0x35170402
+#define _FT3327             0x33270402
+#define _FT3427             0x34270402
+#define _FT7311             0x73110402
+#define _FT5526_V00         0x5526C402
+
+#define _FT5626             0x56260401
+#define _FT5726             0x57260401
+#define _FT5826B            0x5826B401
+#define _FT5826S            0x5826C401
+#define _FT7811             0x78110401
+#define _FT3D47             0x3D470401
+#define _FT3617             0x36170401
+#define _FT3717             0x37170401
+#define _FT3817B            0x3817B401
+#define _FT3517U            0x3517D401
+
+#define _FT6236U            0x6236D003
+#define _FT6336G            0x6336A003
+#define _FT6336U            0x6336D003
+#define _FT6436U            0x6436D003
+#define _FT6436T            0x6436E003
+
+#define _FT3267             0x32670004
+#define _FT3367             0x33670004
+
+#define _FT3327G_003        0x3327A482
+#define _FT3427_003         0x3427D482
+#define _FT3427G_003        0x3427A482
+#define _FT5446_003         0x5446D482
+#define _FT5446_Q03         0x5446C482
+#define _FT5446_P03         0x5446A481
+#define _FT5446_N03         0x5446A489
+#define _FT5426_003         0x5426D482
+#define _FT5526_003         0x5526D482
+
+#define _FT3518             0x35180481
+#define _FT3518U            0x3518D481
+#define _FT3558             0x35580481
+#define _FT3528             0x35280481
+#define _FT5536             0x55360481
+#define _FT5536L            0x5536E481
+#define _FT3418             0x34180481
+
+#define _FT3519             0x35190489
+
+#define _FT5446U            0x5446D083
+#define _FT5456U            0x5456D083
+#define _FT3417U            0x3417D083
+#define _FT5426U            0x5426D083
+#define _FT3428             0x34280083
+#define _FT3437U            0x3437D083
+
+#define _FT7302             0x73020084
+#define _FT7202             0x72020084
+#define _FT3308             0x33080084
+#define _FT6446             0x64460084
+
+#define _FT6346U            0x6346D085
+#define _FT6346G            0x6346A085
+#define _FT3067             0x30670085
+#define _FT3068             0x30680085
+#define _FT3168             0x31680085
+#define _FT3268             0x32680085
+#define _FT6146             0x61460085
+
+#define _FT5726_003         0x5726D486
+#define _FT5726_V03         0x5726C486
+
+#define _FT3618             0x36180487
+#define _FT5646             0x56460487
+#define _FT3A58             0x3A580487
+#define _FT3B58             0x3B580487
+#define _FT3D58             0x3D580487
+#define _FT5936             0x59360487
+#define _FT5A36             0x5A360487
+#define _FT5B36             0x5B360487
+#define _FT5D36             0x5D360487
+#define _FT5946             0x59460487
+#define _FT5A46             0x5A460487
+#define _FT5B46             0x5B460487
+#define _FT5D46             0x5D460487
+
+#define _FT3658U            0x3658D488
+#define _FT3658G            0x3658A488
+#define _FT3683G            0x56720090
+#define _FT3683U            0x3683D090
+
+/*************************************************/
+
+/*
+ * choose your ic chip type of focaltech
+ */
+#define FTS_CHIP_TYPE   _FT3683U
+
+/******************* Enables *********************/
+/*********** 1 to enable, 0 to disable ***********/
+
+/*
+ * show function flag info for GOOGLE debug
+ */
+#define GOOGLE_REPORT_MODE                      1
+
+/*
+ * show debug log info for heatmap
+ */
+#define FTS_DEBUG_EN                            1
+
+/*
+ * Log level of touch key info
+ * 0: Do not show key info
+ * 1: Show single key event
+ * 2: Show continuous key event
+ * 3: Show continuous key event and buffer info
+ */
+#define FTS_KEY_LOG_LEVEL                       0
+
+/*
+ * Linux MultiTouch Protocol
+ * 1: Protocol B(default), 0: Protocol A
+ */
+#define FTS_MT_PROTOCOL_B_EN                    1
+
+/*
+ * Report Pressure in multitouch
+ * 1:enable(default),0:disable
+*/
+#define FTS_REPORT_PRESSURE_EN                  0
+
+/*
+ * Stylus PEN enable
+ * 1:enable(default),0:disable
+*/
+#define FTS_PEN_EN                              0
+
+/*
+ * Gesture function enable
+ * default: disable
+ */
+#define FTS_GESTURE_EN                          0
+
+/*
+ * AOC Gesture function enable
+ * 1:enable(default),0:disable
+ */
+#define FTS_AOC_GESTURE_EN                      1
+
+/*
+ * ESD check & protection
+ * default: disable
+ */
+#define FTS_ESDCHECK_EN                         0
+
+/*
+ * Production test enable
+ * 1: enable, 0:disable(default)
+ */
+#define FTS_TEST_EN                             1
+
+/*
+ * Pinctrl enable
+ * default: disable
+ */
+#define FTS_PINCTRL_EN                          1
+
+/*
+ * Customer power enable
+ * enable it when customer need control TP power
+ * default: disable
+ */
+#define FTS_POWER_SOURCE_CUST_EN                1
+
+/****************************************************/
+
+/********************** Upgrade ****************************/
+/*
+ * auto upgrade
+ */
+#define FTS_AUTO_UPGRADE_EN                     1
+
+/*
+ * auto upgrade for lcd cfg
+ */
+#define FTS_AUTO_LIC_UPGRADE_EN                 0
+
+/*
+ * Numbers of modules support
+ */
+#define FTS_GET_MODULE_NUM                      0
+
+/*
+ * module_id: mean vendor_id generally, also maybe gpio or lcm_id...
+ * If means vendor_id, the FTS_MODULE_ID = PANEL_ID << 8 + VENDOR_ID
+ * FTS_GET_MODULE_NUM == 0/1, no check module id, you may ignore them
+ * FTS_GET_MODULE_NUM >= 2, compatible with FTS_MODULE2_ID
+ * FTS_GET_MODULE_NUM >= 3, compatible with FTS_MODULE3_ID
+ */
+#define FTS_MODULE_ID                          0x0000
+#define FTS_MODULE2_ID                         0x0000
+#define FTS_MODULE3_ID                         0x0000
+
+/*
+ * Need set the following when get firmware via firmware_request()
+ * For example: if module'vendor is tianma,
+ * #define FTS_MODULE_NAME                        "tianma"
+ * then file_name will be "focaltech_ts_fw_tianma"
+ * You should rename fw to "focaltech_ts_fw_tianma", and push it into
+ * etc/firmware or by customers
+ */
+#define FTS_MODULE_NAME                        ""
+#define FTS_MODULE2_NAME                       ""
+#define FTS_MODULE3_NAME                       ""
+
+/*
+ * FW.i file for auto upgrade, you must replace it with your own
+ * define your own fw_file, the sample one to be replaced is invalid
+ * NOTE: if FTS_GET_MODULE_NUM > 1, it's the fw corresponding with FTS_VENDOR_ID
+ */
+#define FTS_UPGRADE_FW_FILE                      "include/firmware/fw_sample.i"
+
+/*
+ * if FTS_GET_MODULE_NUM >= 2, fw corrsponding with FTS_VENDOR_ID2
+ * define your own fw_file, the sample one is invalid
+ */
+#define FTS_UPGRADE_FW2_FILE                     "include/firmware/fw_sample.i"
+
+/*
+ * if FTS_GET_MODULE_NUM >= 3, fw corrsponding with FTS_VENDOR_ID3
+ * define your own fw_file, the sample one is invalid
+ */
+#define FTS_UPGRADE_FW3_FILE                     "include/firmware/fw_sample.i"
+
+/*********************************************************/
+
+#endif /* _LINUX_FOCLATECH_CONFIG_H_ */
diff --git a/ft3683u/focaltech_core.c b/ft3683u/focaltech_core.c
new file mode 100644
index 0000000..14ca230
--- /dev/null
+++ b/ft3683u/focaltech_core.c
@@ -0,0 +1,2759 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/*****************************************************************************
+*
+* File Name: focaltech_core.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract: entrance for focaltech ts driver
+*
+* Version: V1.0
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/consumer.h>
+#if defined(CONFIG_FB)
+#include <linux/notifier.h>
+#include <linux/fb.h>
+#elif defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+#include <drm/drm_panel.h>
+#elif defined(CONFIG_ARCH_MSM)
+#include <linux/msm_drm_notify.h>
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+#include <linux/earlysuspend.h>
+#define FTS_SUSPEND_LEVEL 1     /* Early-suspend level */
+#endif
+#include <linux/types.h>
+#include "focaltech_core.h"
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+#include <goog_touch_interface.h>
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_DRIVER_NAME                     "focal_ts"
+#define FTS_DRIVER_PEN_NAME                 "fts_ts,pen"
+#define INTERVAL_READ_REG                   200  /* unit:ms */
+#define TIMEOUT_READ_REG                    1000 /* unit:ms */
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+struct fts_ts_data *fts_data;
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+static int fts_ts_suspend(struct device *dev);
+static int fts_ts_resume(struct device *dev);
+
+static char *frequency_table0[8] = {
+  "175K",
+  "375K",
+  "232K",
+  "161K",
+  "274K",
+  "119K",
+  "undef 6",
+  "undef 7",
+};
+
+static char *frequency_table1[8] = {
+  "205K",
+  "323K",
+  "131K",
+  "166K",
+  "238K",
+  "110K",
+  "undef 6",
+  "undef 7",
+};
+
+
+int fts_check_cid(struct fts_ts_data *ts_data, u8 id_h)
+{
+    int i = 0;
+    struct ft_chip_id_t *cid = &ts_data->ic_info.cid;
+    u8 cid_h = 0x0;
+
+    if (cid->type == 0)
+        return -ENODATA;
+
+    for (i = 0; i < FTS_MAX_CHIP_IDS; i++) {
+        cid_h = ((cid->chip_ids[i] >> 8) & 0x00FF);
+        if (cid_h && (id_h == cid_h)) {
+            return 0;
+        }
+    }
+
+    return -ENODATA;
+}
+
+/*****************************************************************************
+*  Name: fts_wait_tp_to_valid
+*  Brief: Read chip id until TP FW become valid(Timeout: TIMEOUT_READ_REG),
+*         need call when reset/power on/resume...
+*  Input:
+*  Output:
+*  Return: return 0 if tp valid, otherwise return error code
+*****************************************************************************/
+int fts_wait_tp_to_valid(void)
+{
+    int ret = 0;
+    int cnt = 0;
+    u8 idh = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 chip_idh = ts_data->ic_info.ids.chip_idh;
+    u16 retry_duration = 0;
+
+    do {
+        ret = fts_read_reg(FTS_REG_CHIP_ID, &idh);
+
+        if (ret == 0 && ((idh == chip_idh) || (fts_check_cid(ts_data, idh) == 0))) {
+            FTS_INFO("TP Ready,Device ID:0x%02x, retry:%d", idh, cnt);
+            return 0;
+        }
+
+        cnt++;
+        if (ret == -EIO) {
+            fts_reset_proc(FTS_RESET_INTERVAL);
+            retry_duration += FTS_RESET_INTERVAL;
+        } else {
+            msleep(INTERVAL_READ_REG);
+            retry_duration += INTERVAL_READ_REG;
+        }
+
+    } while (retry_duration < TIMEOUT_READ_REG);
+
+    FTS_ERROR("Wait tp timeout");
+    return -ETIMEDOUT;
+}
+
+/*****************************************************************************
+*  Name: fts_tp_state_recovery
+*  Brief: Need execute this function when reset
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+void fts_tp_state_recovery(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    /* wait tp stable */
+    fts_wait_tp_to_valid();
+    /* recover all firmware modes based on the settings of driver side. */
+    fts_ex_mode_recovery(ts_data);
+    FTS_FUNC_EXIT();
+}
+
+int fts_reset_proc(int hdelayms)
+{
+    FTS_DEBUG("tp reset");
+    
+    fts_write_reg(0xB6, 1);
+    msleep(20);
+    
+    gpio_direction_output(fts_data->pdata->reset_gpio, 0);
+    /* The minimum reset duration is 1 ms. */
+    msleep(1);
+    gpio_direction_output(fts_data->pdata->reset_gpio, 1);
+    if (hdelayms) {
+        msleep(hdelayms);
+    }
+
+    return 0;
+}
+
+void fts_irq_disable(void)
+{
+    unsigned long irqflags;
+
+    FTS_FUNC_ENTER();
+    spin_lock_irqsave(&fts_data->irq_lock, irqflags);
+
+    if (!fts_data->irq_disabled) {
+        disable_irq_nosync(fts_data->irq);
+        fts_data->irq_disabled = true;
+    }
+
+    spin_unlock_irqrestore(&fts_data->irq_lock, irqflags);
+    FTS_FUNC_EXIT();
+}
+
+void fts_irq_enable(void)
+{
+    unsigned long irqflags = 0;
+
+    FTS_FUNC_ENTER();
+    spin_lock_irqsave(&fts_data->irq_lock, irqflags);
+
+    if (fts_data->irq_disabled) {
+        enable_irq(fts_data->irq);
+        fts_data->irq_disabled = false;
+    }
+
+    spin_unlock_irqrestore(&fts_data->irq_lock, irqflags);
+    FTS_FUNC_EXIT();
+}
+
+void fts_hid2std(void)
+{
+    int ret = 0;
+    u8 buf[3] = {0xEB, 0xAA, 0x09};
+
+    if (fts_data->bus_type != FTS_BUS_TYPE_I2C)
+        return;
+
+    ret = fts_write(buf, 3);
+    if (ret < 0) {
+        FTS_ERROR("hid2std cmd write fail");
+    } else {
+        msleep(10);
+        buf[0] = buf[1] = buf[2] = 0;
+        ret = fts_read(NULL, 0, buf, 3);
+        if (ret < 0) {
+            FTS_ERROR("hid2std cmd read fail");
+        } else if ((0xEB == buf[0]) && (0xAA == buf[1]) && (0x08 == buf[2])) {
+            FTS_DEBUG("hidi2c change to stdi2c successful");
+        } else {
+            FTS_DEBUG("hidi2c change to stdi2c not support or fail");
+        }
+    }
+}
+
+static int fts_match_cid(struct fts_ts_data *ts_data,
+                         u16 type, u8 id_h, u8 id_l, bool force)
+{
+#ifdef FTS_CHIP_ID_MAPPING
+    u32 i = 0;
+    u32 j = 0;
+    struct ft_chip_id_t chip_id_list[] = FTS_CHIP_ID_MAPPING;
+    u32 cid_entries = sizeof(chip_id_list) / sizeof(struct ft_chip_id_t);
+    u16 id = (id_h << 8) + id_l;
+
+    memset(&ts_data->ic_info.cid, 0, sizeof(struct ft_chip_id_t));
+    for (i = 0; i < cid_entries; i++) {
+        if (!force && (type == chip_id_list[i].type)) {
+            break;
+        } else if (force && (type == chip_id_list[i].type)) {
+            FTS_INFO("match cid,type:0x%x", (int)chip_id_list[i].type);
+            ts_data->ic_info.cid = chip_id_list[i];
+            return 0;
+        }
+    }
+
+    if (i >= cid_entries) {
+        return -ENODATA;
+    }
+
+    for (j = 0; j < FTS_MAX_CHIP_IDS; j++) {
+        if (id == chip_id_list[i].chip_ids[j]) {
+            FTS_DEBUG("cid:%x==%x", id, chip_id_list[i].chip_ids[j]);
+            FTS_INFO("match cid,type:0x%x", (int)chip_id_list[i].type);
+            ts_data->ic_info.cid = chip_id_list[i];
+            return 0;
+        }
+    }
+
+    return -ENODATA;
+#else
+    return -EINVAL;
+#endif
+}
+
+static int fts_get_chip_types(
+    struct fts_ts_data *ts_data,
+    u8 id_h, u8 id_l, bool fw_valid)
+{
+    u32 i = 0;
+    struct ft_chip_t ctype[] = FTS_CHIP_TYPE_MAPPING;
+    u32 ctype_entries = sizeof(ctype) / sizeof(struct ft_chip_t);
+
+    if ((0x0 == id_h) || (0x0 == id_l)) {
+        FTS_ERROR("id_h/id_l is 0");
+        return -EINVAL;
+    }
+
+    FTS_DEBUG("verify id:0x%02x%02x", id_h, id_l);
+    for (i = 0; i < ctype_entries; i++) {
+        if (VALID == fw_valid) {
+            if (((id_h == ctype[i].chip_idh) && (id_l == ctype[i].chip_idl))
+                || (!fts_match_cid(ts_data, ctype[i].type, id_h, id_l, 0)))
+                break;
+        } else {
+            if (((id_h == ctype[i].rom_idh) && (id_l == ctype[i].rom_idl))
+                || ((id_h == ctype[i].pb_idh) && (id_l == ctype[i].pb_idl))
+                || ((id_h == ctype[i].bl_idh) && (id_l == ctype[i].bl_idl))) {
+                break;
+            }
+        }
+    }
+
+    if (i >= ctype_entries) {
+        return -ENODATA;
+    }
+
+    fts_match_cid(ts_data, ctype[i].type, id_h, id_l, 1);
+    ts_data->ic_info.ids = ctype[i];
+    return 0;
+}
+
+static int fts_read_bootid(struct fts_ts_data *ts_data, u8 *id)
+{
+    int ret = 0;
+    u8 chip_id[2] = { 0 };
+    u8 id_cmd[4] = { 0 };
+    u32 id_cmd_len = 0;
+
+    id_cmd[0] = FTS_CMD_START1;
+    id_cmd[1] = FTS_CMD_START2;
+    ret = fts_write(id_cmd, 2);
+    if (ret < 0) {
+        FTS_ERROR("start cmd write fail");
+        return ret;
+    }
+
+    msleep(FTS_CMD_START_DELAY);
+    id_cmd[0] = FTS_CMD_READ_ID;
+    id_cmd[1] = id_cmd[2] = id_cmd[3] = 0x00;
+    if (ts_data->ic_info.is_incell)
+        id_cmd_len = FTS_CMD_READ_ID_LEN_INCELL;
+    else
+        id_cmd_len = FTS_CMD_READ_ID_LEN;
+    ret = fts_read(id_cmd, id_cmd_len, chip_id, 2);
+    if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) {
+        FTS_ERROR("read boot id fail,read:0x%02x%02x", chip_id[0], chip_id[1]);
+        return -EIO;
+    }
+
+    id[0] = chip_id[0];
+    id[1] = chip_id[1];
+    return 0;
+}
+
+/*****************************************************************************
+* Name: fts_get_ic_information
+* Brief: read chip id to get ic information, after run the function, driver w-
+*        ill know which IC is it.
+*        If cant get the ic information, maybe not focaltech's touch IC, need
+*        unregister the driver
+* Input:
+* Output:
+* Return: return 0 if get correct ic information, otherwise return error code
+*****************************************************************************/
+static int fts_get_ic_information(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int cnt = 0;
+    u8 chip_id[2] = { 0 };
+
+    ts_data->ic_info.is_incell = FTS_CHIP_IDC;
+    ts_data->ic_info.hid_supported = FTS_HID_SUPPORTTED;
+
+    do {
+        ret = fts_read_reg(FTS_REG_CHIP_ID, &chip_id[0]);
+        ret = fts_read_reg(FTS_REG_CHIP_ID2, &chip_id[1]);
+        if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) {
+            FTS_DEBUG("chip id read invalid, read:0x%02x%02x",
+                      chip_id[0], chip_id[1]);
+        } else {
+            ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], VALID);
+            if (!ret)
+                break;
+            else
+                FTS_DEBUG("TP not ready, read:0x%02x%02x",
+                          chip_id[0], chip_id[1]);
+        }
+
+        cnt++;
+        msleep(INTERVAL_READ_REG);
+    } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG);
+
+    if ((cnt * INTERVAL_READ_REG) >= TIMEOUT_READ_REG) {
+        FTS_INFO("fw is invalid, need read boot id");
+        if (ts_data->ic_info.hid_supported) {
+            fts_hid2std();
+        }
+
+        ret = fts_read_bootid(ts_data, &chip_id[0]);
+        if (ret <  0) {
+            FTS_ERROR("read boot id fail");
+            return ret;
+        }
+
+        ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], INVALID);
+        if (ret < 0) {
+            FTS_ERROR("can't get ic informaton");
+            return ret;
+        }
+    }
+
+    FTS_INFO("get ic information, chip id = 0x%02x%02x(cid type=0x%x)",
+             ts_data->ic_info.ids.chip_idh, ts_data->ic_info.ids.chip_idl,
+             ts_data->ic_info.cid.type);
+
+    return 0;
+}
+
+/*****************************************************************************
+*  Reprot related
+*****************************************************************************/
+static void fts_show_touch_buffer(u8 *data, int datalen)
+{
+    int i = 0;
+    int count = 0;
+    char *tmpbuf = NULL;
+
+    tmpbuf = kzalloc(1024, GFP_KERNEL);
+    if (!tmpbuf) {
+        FTS_ERROR("tmpbuf zalloc fail");
+        return;
+    }
+
+    FTS_DEBUG("-------------------------------------");
+    for (i = 0; i < datalen; i++) {
+        count += scnprintf(tmpbuf + count, 1024 - count, "%02X,", data[i]);
+        if ((i + 1) % 256 == 0) {
+          FTS_DEBUG("%s", tmpbuf);
+          count = 0;
+        }
+    }
+
+    if (tmpbuf) {
+        kfree(tmpbuf);
+        tmpbuf = NULL;
+    }
+}
+
+void fts_release_all_finger(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+#if FTS_MT_PROTOCOL_B_EN
+    u32 finger_count = 0;
+    u32 max_touches = ts_data->pdata->max_touch_number;
+#endif
+
+    mutex_lock(&ts_data->report_mutex);
+
+#if FTS_MT_PROTOCOL_B_EN
+    for (finger_count = 0; finger_count < max_touches; finger_count++) {
+        input_mt_slot(input_dev, finger_count);
+        input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false);
+    }
+#else
+    input_mt_sync(input_dev);
+#endif
+    input_report_key(input_dev, BTN_TOUCH, 0);
+    input_sync(input_dev);
+
+#if FTS_PEN_EN
+    input_report_key(ts_data->pen_dev, BTN_TOOL_PEN, 0);
+    input_report_key(ts_data->pen_dev, BTN_TOUCH, 0);
+    input_sync(ts_data->pen_dev);
+#endif
+
+    ts_data->touchs = 0;
+    ts_data->key_state = 0;
+    mutex_unlock(&ts_data->report_mutex);
+}
+
+/*****************************************************************************
+* Name: fts_input_report_key
+* Brief: process key events,need report key-event if key enable.
+*        if point's coordinate is in (x_dim-50,y_dim-50) ~ (x_dim+50,y_dim+50),
+*        need report it to key event.
+*        x_dim: parse from dts, means key x_coordinate, dimension:+-50
+*        y_dim: parse from dts, means key y_coordinate, dimension:+-50
+* Input:
+* Output:
+* Return: return 0 if it's key event, otherwise return error code
+*****************************************************************************/
+#if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+static int fts_input_report_key(struct fts_ts_data *data, int index)
+{
+    int i = 0;
+    int x = data->events[index].x;
+    int y = data->events[index].y;
+    int *x_dim = &data->pdata->key_x_coords[0];
+    int *y_dim = &data->pdata->key_y_coords[0];
+
+    if (!data->pdata->have_key) {
+        return -EINVAL;
+    }
+    for (i = 0; i < data->pdata->key_number; i++) {
+        if ((x >= x_dim[i] - FTS_KEY_DIM) && (x <= x_dim[i] + FTS_KEY_DIM) &&
+            (y >= y_dim[i] - FTS_KEY_DIM) && (y <= y_dim[i] + FTS_KEY_DIM)) {
+            if (EVENT_DOWN(data->events[index].flag)
+                && !(data->key_state & (1 << i))) {
+                input_report_key(data->input_dev, data->pdata->keys[i], 1);
+                data->key_state |= (1 << i);
+                FTS_DEBUG("Key%d(%d,%d) DOWN!", i, x, y);
+            } else if (EVENT_UP(data->events[index].flag)
+                       && (data->key_state & (1 << i))) {
+                input_report_key(data->input_dev, data->pdata->keys[i], 0);
+                data->key_state &= ~(1 << i);
+                FTS_DEBUG("Key%d(%d,%d) Up!", i, x, y);
+            }
+            return 0;
+        }
+    }
+    return -EINVAL;
+}
+#endif // !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+#if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+#if FTS_MT_PROTOCOL_B_EN
+static int fts_input_report_b(struct fts_ts_data *data)
+{
+    int i = 0;
+    int touchs = 0;
+    bool va_reported = false;
+    u32 max_touch_num = data->pdata->max_touch_number;
+    struct ts_event *events = data->events;
+
+    for (i = 0; i < data->touch_point; i++) {
+        if (fts_input_report_key(data, i) == 0) {
+            continue;
+        }
+
+        va_reported = true;
+
+        if (EVENT_DOWN(events[i].flag)) {
+            input_mt_slot(data->input_dev, events[i].id);
+            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true);
+
+#if FTS_REPORT_PRESSURE_EN
+            if (events[i].p <= 0) {
+                events[i].p = 0x00;
+            }
+			events[i].p = 0x3F
+            input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p);
+#endif
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y);
+
+            touchs |= BIT(events[i].id);
+            data->touchs |= BIT(events[i].id);
+            if ((data->log_level >= 2) ||
+                ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
+                FTS_DEBUG("[B]P%d(%d, %d)[ma:%d,mi:%d,p:%d,o:%d] DOWN!",
+                          events[i].id,
+                          events[i].x,
+                          events[i].y,
+                          events[i].major,
+                          events[i].minor,
+                          events[i].p,
+                          events[i].orientation);
+            }
+        } else {  //EVENT_UP
+            input_mt_slot(data->input_dev, events[i].id);
+            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
+            data->touchs &= ~BIT(events[i].id);
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[B1]P%d UP!", events[i].id);
+            }
+        }
+    }
+
+    if (unlikely(data->touchs ^ touchs)) {
+        for (i = 0; i < max_touch_num; i++)  {
+            if (BIT(i) & (data->touchs ^ touchs)) {
+                if (data->log_level >= 1) {
+                    FTS_DEBUG("[B2]P%d UP!", i);
+                }
+                va_reported = true;
+                input_mt_slot(data->input_dev, i);
+                input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
+            }
+        }
+    }
+    data->touchs = touchs;
+
+    if (va_reported) {
+        /* touchs==0, there's no point but key */
+        if (EVENT_NO_DOWN(data) || (!touchs)) {
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[B]Points All Up!");
+            }
+            input_report_key(data->input_dev, BTN_TOUCH, 0);
+        } else {
+            input_report_key(data->input_dev, BTN_TOUCH, 1);
+        }
+    }
+    input_set_timestamp(data->input_dev, data->coords_timestamp);
+    input_sync(data->input_dev);
+    return 0;
+}
+
+#else
+static int fts_input_report_a(struct fts_ts_data *data)
+{
+    int i = 0;
+    int touchs = 0;
+    bool va_reported = false;
+    struct ts_event *events = data->events;
+
+    for (i = 0; i < data->touch_point; i++) {
+        if (fts_input_report_key(data, i) == 0) {
+            continue;
+        }
+
+        va_reported = true;
+        if (EVENT_DOWN(events[i].flag)) {
+            input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, events[i].id);
+#if FTS_REPORT_PRESSURE_EN
+            if (events[i].p <= 0) {
+                events[i].p = 0x00;
+            }
+            input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p);
+#endif
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
+            input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x);
+            input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y);
+
+            input_mt_sync(data->input_dev);
+
+            if ((data->log_level >= 2) ||
+                ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
+                FTS_DEBUG("[A]P%d(%d, %d)[ma:%d,mi:%d,p:%d] DOWN!",
+                          events[i].id,
+                          events[i].x,
+                          events[i].y,
+                          events[i].major,
+                          events[i].minor,
+                          events[i].p);
+            }
+            touchs++;
+        }
+    }
+
+    /* last point down, current no point but key */
+    if (data->touchs && !touchs) {
+        va_reported = true;
+    }
+    data->touchs = touchs;
+
+    if (va_reported) {
+        if (EVENT_NO_DOWN(data)) {
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[A]Points All Up!");
+            }
+            input_report_key(data->input_dev, BTN_TOUCH, 0);
+            input_mt_sync(data->input_dev);
+        } else {
+            input_report_key(data->input_dev, BTN_TOUCH, 1);
+        }
+    }
+    input_set_timestamp(data->input_dev, data->timestamp);
+    input_sync(data->input_dev);
+    return 0;
+}
+#endif
+#endif // !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+#if FTS_PEN_EN
+static int fts_input_pen_report(struct fts_ts_data *data)
+{
+    struct input_dev *pen_dev = data->pen_dev;
+    struct pen_event *pevt = &data->pevent;
+    u8 *buf = data->point_buf;
+
+
+    if (buf[3] & 0x08)
+        input_report_key(pen_dev, BTN_STYLUS, 1);
+    else
+        input_report_key(pen_dev, BTN_STYLUS, 0);
+
+    if (buf[3] & 0x02)
+        input_report_key(pen_dev, BTN_STYLUS2, 1);
+    else
+        input_report_key(pen_dev, BTN_STYLUS2, 0);
+
+    pevt->inrange = (buf[3] & 0x20) ? 1 : 0;
+    pevt->tip = (buf[3] & 0x01) ? 1 : 0;
+    pevt->x = ((buf[4] & 0x0F) << 8) + buf[5];
+    pevt->y = ((buf[6] & 0x0F) << 8) + buf[7];
+    pevt->p = ((buf[8] & 0x0F) << 8) + buf[9];
+    pevt->id = buf[6] >> 4;
+    pevt->flag = buf[4] >> 6;
+    pevt->tilt_x = (buf[10] << 8) + buf[11];
+    pevt->tilt_y = (buf[12] << 8) + buf[13];
+    pevt->tool_type = BTN_TOOL_PEN;
+
+    if (data->log_level >= 2  ||
+        ((1 == data->log_level) && (FTS_TOUCH_DOWN == pevt->flag))) {
+        FTS_DEBUG("[PEN]x:%d,y:%d,p:%d,inrange:%d,tip:%d,flag:%d DOWN!",
+                  pevt->x, pevt->y, pevt->p, pevt->inrange,
+                  pevt->tip, pevt->flag);
+    }
+
+    if ( (data->log_level >= 1) && (!pevt->inrange)) {
+        FTS_DEBUG("[PEN]UP!");
+    }
+
+    input_report_abs(pen_dev, ABS_X, pevt->x);
+    input_report_abs(pen_dev, ABS_Y, pevt->y);
+    input_report_abs(pen_dev, ABS_PRESSURE, pevt->p);
+
+    /* check if the pen support tilt event */
+    if ((pevt->tilt_x != 0) || (pevt->tilt_y != 0)) {
+        input_report_abs(pen_dev, ABS_TILT_X, pevt->tilt_x);
+        input_report_abs(pen_dev, ABS_TILT_Y, pevt->tilt_y);
+    }
+
+    input_report_key(pen_dev, BTN_TOUCH, pevt->tip);
+    input_report_key(pen_dev, BTN_TOOL_PEN, pevt->inrange);
+    input_sync(pen_dev);
+
+    return 0;
+}
+#endif
+
+struct fts_heatmap_st {
+// TODO: be care TX, RX number between difference project
+// if heatmap struct is very similar with next project, please make it protable
+    union {
+        struct {
+            u8 count;
+            u16 mc[576];
+            u16 sc_water_rx[36];
+            u16 sc_water_tx[16];
+            u16 dummy1[9];
+            u16 sc_normal_rx[36];
+            u16 sc_normal_tx[16];
+            u16 dummy2[9];
+        };
+        u8 data[1397];
+    };
+};
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+static void fts_show_heatmap_buffer(struct fts_ts_data *ts_data, u8 *data, int datalen)
+{
+  struct fts_heatmap_st* heatmap;
+  int i, j;
+  char *tmpbuf = NULL;
+  int count = 0;
+
+  if (data == NULL || datalen < FTS_FULL_TOUCH_DATA_SIZE)
+    return ;
+
+  tmpbuf = kzalloc(1024, GFP_KERNEL);
+  if (!tmpbuf) {
+    FTS_ERROR("tmpbuf zalloc fail");
+    return ;
+  }
+
+  heatmap = (struct fts_heatmap_st*)(data + FTS_CAP_DATA_LEN + FTS_CAP_DUMMY_DATA_SIZE);
+
+  u8 tx = ts_data->pdata->tx_ch_num;
+  u8 rx = ts_data->pdata->rx_ch_num;
+  for (i = 0 ; i < tx; i++ ) {
+    for (j = 0; j < rx; j++) {
+      count += scnprintf(tmpbuf + count, 1024 - count, "%d,",
+        (int16_t)heatmap->mc[i*rx + j]);
+    }
+
+    FTS_DEBUG("%s", tmpbuf);
+    count = 0;
+  }
+
+  if (tmpbuf) {
+    kfree(tmpbuf);
+    tmpbuf = NULL;
+  }
+}
+
+static void goog_handle_heatmap_format(struct fts_ts_data *ts_data, u8 *data, int datalen)
+{
+  int i, j;
+  if (data == NULL || datalen < FTS_FULL_TOUCH_DATA_SIZE)
+    return ;
+
+  u8 tx = ts_data->pdata->tx_ch_num;
+  u8 rx = ts_data->pdata->rx_ch_num;
+
+  int mc_index = FTS_CAP_DATA_LEN + FTS_CAP_DUMMY_DATA_SIZE;
+  int sc_water_index = FTS_CAP_DATA_LEN + FTS_CAP_DUMMY_DATA_SIZE + FTS_MUTUAL_DATA_SIZE;
+  int sc_normal_index = FTS_CAP_DATA_LEN + FTS_CAP_DUMMY_DATA_SIZE + FTS_MUTUAL_DATA_SIZE +FTS_SELF_DATA_SIZE;
+  int heatmap_range[3][2] = {
+    {mc_index, mc_index + FTS_MUTUAL_DATA_SIZE},
+    {sc_water_index, sc_water_index + (tx + rx) * 2},
+    {sc_normal_index, sc_normal_index + (tx + rx) * 2},
+  };
+
+  for (i = 0; i < 3; i++) {
+    for (j = heatmap_range[i][0]; j < heatmap_range[i][1]; j = j+2) {
+        be16_to_cpus((u16*)(data + j));
+    }
+  }
+
+  return ;
+}
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+#if GOOGLE_REPORT_MODE
+static void fts_update_abnormal_reset(struct fts_ts_data *data,
+                                      struct fw_status_ts* new_status)
+{
+    switch (new_status->B0_b0_abnormal_reset) {
+        case 0: // Normal status
+          return;
+        case 1:
+          FTS_ERROR("Touch ic reset: MCU watchdog");
+          fts_update_feature_setting(data);
+          break;
+        case 2:
+          FTS_ERROR("Touch ic reset: Software reset");
+          break;
+        case 3:
+          FTS_ERROR("Touch ic reset: AFE watchdog");
+          fts_update_feature_setting(data);
+          break;
+        case 4:
+          FTS_ERROR("Touch ic reset: Hardware reset");
+          break;
+        case 5:
+          FTS_ERROR("Touch ic reset: Power on");
+          break;
+        case 6:
+          FTS_ERROR("Touch ic reset: 6");
+          fts_update_feature_setting(data);
+          break;
+        case 7:
+          FTS_ERROR("Touch ic reset: 7");
+          fts_update_feature_setting(data);
+          break;
+        default:
+          return;
+    }
+
+    // Clear reset flag
+    fts_write_reg(FTS_REG_CLR_RESET, 0x01);
+}
+static void fts_update_setting_status(struct fts_ts_data *data,
+                                      struct fw_status_ts* new_status)
+{
+    bool changed = false;
+    struct fw_status_ts *current_status = &data->current_host_status;
+
+    if (current_status->B0_b3_water_state != new_status->B0_b3_water_state) {
+      current_status->B0_b3_water_state = new_status->B0_b3_water_state;
+      changed = true;
+    }
+
+    if (current_status->B0_b4_grip_status != new_status->B0_b4_grip_status) {
+      current_status->B0_b4_grip_status = new_status->B0_b4_grip_status;
+      changed = true;
+    }
+
+    if (current_status->B0_b5_palm_status != new_status->B0_b5_palm_status) {
+      current_status->B0_b5_palm_status = new_status->B0_b5_palm_status;
+      changed = true;
+    }
+
+    if (current_status->B2_b3_v_sync_status != new_status->B2_b3_v_sync_status) {
+      current_status->B2_b3_v_sync_status = new_status->B2_b3_v_sync_status;
+      changed = true;
+    }
+
+    if (current_status->B1_b0_baseline != new_status->B1_b0_baseline) {
+      current_status->B1_b0_baseline = new_status->B1_b0_baseline;
+      changed = true;
+    }
+
+    if (current_status->B1_b3_noise_status != new_status->B1_b3_noise_status) {
+      current_status->B1_b3_noise_status = new_status->B1_b3_noise_status;
+      changed = true;
+    }
+
+    if (current_status->B2_b0_frequency_hopping != new_status->B2_b0_frequency_hopping) {
+      current_status->B2_b0_frequency_hopping = new_status->B2_b0_frequency_hopping;
+      changed = true;
+    }
+
+    if (changed) {
+       FTS_INFO("Status: water:%d grip:%d palm:%d, v-sync:%d, baseline:%d, "
+            "noise:%d, frequency:%s\n",
+            current_status->B0_b3_water_state, current_status->B0_b4_grip_status,
+            current_status->B0_b5_palm_status, current_status->B2_b3_v_sync_status,
+            current_status->B1_b0_baseline, current_status->B1_b3_noise_status,
+            data->pdata->panel_id == 0 ?
+              frequency_table0[current_status->B2_b0_frequency_hopping] :
+              frequency_table1[current_status->B2_b0_frequency_hopping]);
+    }
+}
+
+static int fts_read_and_update_fw_status(struct fts_ts_data *data)
+{
+    int ret;
+    u8 cmd[1] = { FTS_REG_CUSTOMER_STATUS };
+    struct fw_status_ts new_status = { 0 };
+
+    ret = fts_read(cmd, 1,  new_status.data, sizeof(struct fw_status_ts));
+    if (ret < 0)
+        return ret;
+
+    if (data->log_level >= 3) {
+        FTS_DEBUG("0xB2: %02x, %02x, %02x, %02x",
+                new_status.data[0],
+                new_status.data[1],
+                new_status.data[2],
+                new_status.data[3]);
+    }
+
+    fts_update_abnormal_reset(data, &new_status);
+    fts_update_setting_status(data, &new_status);
+
+    return 0;
+}
+#endif
+
+static int fts_read_touchdata(struct fts_ts_data *data)
+{
+    int ret = 0;
+    u8 *buf = data->point_buf;
+    u8 cmd[1] = { 0 };
+
+    if (data->gesture_mode) {
+        ret = fts_gesture_readdata(data);
+        if (ret == 0) {
+            FTS_INFO("succuss to get gesture data in irq handler");
+            return 1;
+        }
+        return 0;
+    }
+
+#if GOOGLE_REPORT_MODE
+    ret = fts_read_and_update_fw_status(data);
+    if (ret < 0) {
+        FTS_ERROR("read customer status failed %d", ret);
+    }
+#endif
+
+    cmd[0] = FTS_CMD_READ_TOUCH_DATA;
+    ret = fts_read(cmd, 1, buf, data->pnt_buf_size);
+    if (ret < 0) {
+        FTS_ERROR("touch data(%x) abnormal,ret:%d", buf[1], ret);
+        return -EIO;
+    }
+
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    goog_handle_heatmap_format(data, buf, data->pnt_buf_size);
+    if (data->log_level == 4)
+      fts_show_heatmap_buffer(data, buf, data->pnt_buf_size);
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+    if (data->log_level >= 5)
+        fts_show_touch_buffer(buf, data->pnt_buf_size);
+
+    return ret;
+}
+
+static int fts_read_parse_touchdata(struct fts_ts_data *data)
+{
+    int ret = 0;
+    int i = 0;
+    u8 pointid = 0;
+    int base = 0;
+    struct ts_event *events = data->events;
+    int max_touch_num = data->pdata->max_touch_number;
+    u8 *buf = data->point_buf;
+	int touch_etype = 0;
+	u8 event_num = 0;
+
+    ret = fts_read_touchdata(data);
+    if (ret) {
+        return ret;
+    }
+
+#if FTS_PEN_EN
+    if ((buf[2] & 0xF0) == 0xB0) {
+        fts_input_pen_report(data);
+        return 2;
+    }
+#endif
+
+	data->point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F;
+    data->touch_point = 0;
+
+    if (data->ic_info.is_incell) {
+        if ((data->point_num == 0x0F) && (buf[1] == 0xFF) && (buf[2] == 0xFF)
+            && (buf[3] == 0xFF) && (buf[4] == 0xFF) && (buf[5] == 0xFF)) {
+            FTS_DEBUG("touch buff is 0xff, need recovery state");
+            fts_release_all_finger();
+            fts_tp_state_recovery(data);
+            data->point_num = 0;
+            return -EIO;
+        }
+    }
+
+    if (data->point_num > max_touch_num) {
+        FTS_DEBUG("invalid point_num(%d)", data->point_num);
+        data->point_num = 0;
+        return -EIO;
+    }
+
+	touch_etype = ((buf[FTS_TOUCH_E_NUM] >> 4) & 0x0F);
+	switch (touch_etype) {
+	case TOUCH_PROTOCOL_v2:
+		event_num = buf[FTS_TOUCH_E_NUM] & 0x0F;
+		if (!event_num || (event_num > max_touch_num)) {
+			FTS_ERROR("invalid touch event num(%d)", event_num);
+			return -EIO;
+		}
+
+		data->touch_point = event_num;
+
+		for (i = 0; i < event_num; i++) {
+			base = FTS_ONE_TCH_LEN_V2 * i + 4;
+			pointid = (buf[FTS_TOUCH_OFF_ID_YH + base]) >> 4;
+			if (pointid >= max_touch_num) {
+				FTS_ERROR("touch point ID(%d) beyond max_touch_number(%d)",
+						  pointid, max_touch_num);
+				return -EINVAL;
+			}
+
+			events[i].id = pointid;
+			events[i].flag = buf[FTS_TOUCH_OFF_E_XH + base] >> 6;
+
+			events[i].x = ((buf[FTS_TOUCH_OFF_E_XH + base] & 0x0F) << 12) \
+						  + ((buf[FTS_TOUCH_OFF_XL + base] & 0xFF) << 4) \
+						  + ((buf[FTS_TOUCH_OFF_PRE + base] >> 4) & 0x0F);
+
+			events[i].y = ((buf[FTS_TOUCH_OFF_ID_YH + base] & 0x0F) << 12) \
+						  + ((buf[FTS_TOUCH_OFF_YL + base] & 0xFF) << 4) \
+						  + (buf[FTS_TOUCH_OFF_PRE + base] & 0x0F);
+
+			events[i].x = FTS_TOUCH_HIRES(events[i].x);
+			events[i].y = FTS_TOUCH_HIRES(events[i].y);
+
+			events[i].major = ((buf[FTS_TOUCH_OFF_MAJOR + base] >> 1) & 0x7F)
+                            * data->pdata->mm2px;
+			events[i].minor = ((buf[FTS_TOUCH_OFF_MINOR + base] >> 1) & 0x7F)
+                            * data->pdata->mm2px;
+			events[i].p = ((buf[FTS_TOUCH_OFF_MAJOR + base] & 0x01) << 1)
+                            + (buf[FTS_TOUCH_OFF_MINOR + base] & 0x01);
+
+			events[i].orientation = (s8)buf[FTS_TOUCH_OFF_ORIENTATION + base];
+
+			if (events[i].major <= 0) events[i].major = 0x09;
+			if (events[i].minor <= 0) events[i].minor = 0x09;
+
+		}
+		break;
+		
+		case TOUCH_DEFAULT:	
+		for (i = 0; i < max_touch_num; i++) {
+				base = FTS_ONE_TCH_LEN * i;
+				pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4;
+				if (pointid >= FTS_MAX_ID)
+					break;
+				else if (pointid >= max_touch_num) {
+					FTS_ERROR("ID(%d) beyond max_touch_number", pointid);
+					return -EINVAL;
+				}
+		
+				data->touch_point++;
+				events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) +
+							  (buf[FTS_TOUCH_X_L_POS + base] & 0xFF);
+				events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) +
+							  (buf[FTS_TOUCH_Y_L_POS + base] & 0xFF);
+				events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6;
+				events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4;
+				events[i].p = (((buf[FTS_TOUCH_AREA_POS + base] << 1) & 0x02) +
+							   (buf[FTS_TOUCH_PRE_POS + base] & 0x01)) *
+							   FTS_PRESSURE_SCALE;
+				events[i].p = 0x3F;
+				events[i].minor =
+					((buf[FTS_TOUCH_PRE_POS + base] >> 1) & 0x7F) * data->pdata->mm2px;
+				events[i].major =
+					((buf[FTS_TOUCH_AREA_POS + base] >> 1) & 0x7F) * data->pdata->mm2px;
+		
+				if (EVENT_DOWN(events[i].flag) && (data->point_num == 0)) {
+					FTS_INFO("abnormal touch data from fw");
+					return -EIO;
+				}
+			}
+			break;
+	}
+
+    if (data->touch_point == 0) {
+        FTS_INFO("no touch point information(%02x)", buf[1]);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+void fts_irq_read_report(void)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_set_intr(1);
+#endif
+
+#if FTS_POINT_REPORT_CHECK_EN
+    fts_prc_queue_work(ts_data);
+#endif
+
+    ret = fts_read_parse_touchdata(ts_data);
+    if (ret == 0) {
+        mutex_lock(&ts_data->report_mutex);
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+        goog_fts_input_report_b(ts_data);
+#else
+#if FTS_MT_PROTOCOL_B_EN
+        fts_input_report_b(ts_data);
+#else
+        fts_input_report_a(ts_data);
+#endif
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+        mutex_unlock(&ts_data->report_mutex);
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_set_intr(0);
+#endif
+}
+
+static irqreturn_t fts_irq_ts(int irq, void *data)
+{
+    struct fts_ts_data *ts_data = data;
+
+    ts_data->isr_timestamp = ktime_get();
+    return IRQ_WAKE_THREAD;
+}
+
+extern int int_test_has_interrupt;
+static irqreturn_t fts_irq_handler(int irq, void *data)
+{
+	struct fts_ts_data *ts_data = fts_data;
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+    int ret = 0;
+    
+
+    if ((ts_data->suspended) && (ts_data->pm_suspend)) {
+        ret = wait_for_completion_timeout(
+                  &ts_data->pm_completion,
+                  msecs_to_jiffies(FTS_TIMEOUT_COMERR_PM));
+        if (!ret) {
+            FTS_ERROR("Bus don't resume from pm(deep),timeout,skip irq");
+            return IRQ_HANDLED;
+        }
+    }
+#endif
+    int_test_has_interrupt++;
+    fts_data->coords_timestamp = fts_data->isr_timestamp;
+    cpu_latency_qos_update_request(&ts_data->pm_qos_req, 100 /* usec */);
+    fts_irq_read_report();
+    cpu_latency_qos_update_request(&ts_data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
+    return IRQ_HANDLED;
+}
+
+static int fts_irq_registration(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+    int irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+
+    ts_data->irq = gpio_to_irq(pdata->irq_gpio);
+    FTS_INFO("irq:%d, flag:%x", ts_data->irq, irq_flags);
+    ret = request_threaded_irq(ts_data->irq, fts_irq_ts, fts_irq_handler,
+                               irq_flags, FTS_DRIVER_NAME, ts_data);
+
+    return ret;
+}
+
+#if FTS_PEN_EN
+static int fts_input_pen_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    struct input_dev *pen_dev;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+
+    FTS_FUNC_ENTER();
+    pen_dev = input_allocate_device();
+    if (!pen_dev) {
+        FTS_ERROR("Failed to allocate memory for input_pen device");
+        return -ENOMEM;
+    }
+
+    pen_dev->dev.parent = ts_data->dev;
+    pen_dev->name = FTS_DRIVER_PEN_NAME;
+    pen_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+    __set_bit(ABS_X, pen_dev->absbit);
+    __set_bit(ABS_Y, pen_dev->absbit);
+    __set_bit(BTN_STYLUS, pen_dev->keybit);
+    __set_bit(BTN_STYLUS2, pen_dev->keybit);
+    __set_bit(BTN_TOUCH, pen_dev->keybit);
+    __set_bit(BTN_TOOL_PEN, pen_dev->keybit);
+    __set_bit(INPUT_PROP_DIRECT, pen_dev->propbit);
+    input_set_abs_params(pen_dev, ABS_X, pdata->x_min, pdata->x_max, 0, 0);
+    input_set_abs_params(pen_dev, ABS_Y, pdata->y_min, pdata->y_max, 0, 0);
+    input_set_abs_params(pen_dev, ABS_PRESSURE, 0, 4096, 0, 0);
+
+    ret = input_register_device(pen_dev);
+    if (ret) {
+        FTS_ERROR("Input device registration failed");
+        input_free_device(pen_dev);
+        pen_dev = NULL;
+        return ret;
+    }
+
+    ts_data->pen_dev = pen_dev;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+#endif
+
+static int fts_input_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int key_num = 0;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+    struct input_dev *input_dev;
+
+    FTS_FUNC_ENTER();
+    input_dev = input_allocate_device();
+    if (!input_dev) {
+        FTS_ERROR("Failed to allocate memory for input device");
+        return -ENOMEM;
+    }
+
+    /* Init and register Input device */
+    input_dev->name = FTS_DRIVER_NAME;
+    if (ts_data->bus_type == FTS_BUS_TYPE_I2C)
+        input_dev->id.bustype = BUS_I2C;
+    else
+        input_dev->id.bustype = BUS_SPI;
+    input_dev->dev.parent = ts_data->dev;
+
+    input_dev->uniq = "google_touchscreen";
+
+    input_set_drvdata(input_dev, ts_data);
+
+    __set_bit(EV_SYN, input_dev->evbit);
+    __set_bit(EV_ABS, input_dev->evbit);
+    __set_bit(EV_KEY, input_dev->evbit);
+    __set_bit(BTN_TOUCH, input_dev->keybit);
+    __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+
+    if (pdata->have_key) {
+        FTS_INFO("set key capabilities");
+        for (key_num = 0; key_num < pdata->key_number; key_num++)
+            input_set_capability(input_dev, EV_KEY, pdata->keys[key_num]);
+    }
+
+#if FTS_MT_PROTOCOL_B_EN
+    input_mt_init_slots(input_dev, pdata->max_touch_number, INPUT_MT_DIRECT);
+#else
+    input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 0x0F, 0, 0);
+#endif
+    input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, pdata->x_max, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, pdata->y_max, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 0x3F, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 0x3F, 0, 0);
+#if FTS_REPORT_PRESSURE_EN
+    input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 0xFF, 0, 0);
+#endif
+    /* Units are (-4096, 4096), representing the range between rotation
+     * 90 degrees to left and 90 degrees to the right.
+     */
+    input_set_abs_params(input_dev, ABS_MT_ORIENTATION, -4096, 4096, 0, 0);
+    input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER, MT_TOOL_PALM, 0, 0);
+    ret = input_register_device(input_dev);
+    if (ret) {
+        FTS_ERROR("Input device registration failed");
+        input_set_drvdata(input_dev, NULL);
+        input_free_device(input_dev);
+        input_dev = NULL;
+        return ret;
+    }
+
+#if FTS_PEN_EN
+    ret = fts_input_pen_init(ts_data);
+    if (ret) {
+        FTS_ERROR("Input-pen device registration failed");
+        input_set_drvdata(input_dev, NULL);
+        input_free_device(input_dev);
+        input_dev = NULL;
+        return ret;
+    }
+#endif
+
+    ts_data->input_dev = input_dev;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static int fts_report_buffer_init(struct fts_ts_data *ts_data)
+{
+    int point_num = 0;
+    int events_num = 0;
+
+    point_num = FTS_MAX_POINTS_SUPPORT;
+    ts_data->pnt_buf_size = FTS_FULL_TOUCH_DATA_SIZE;
+    ts_data->point_buf = (u8 *)kzalloc(ts_data->pnt_buf_size + 1, GFP_KERNEL);
+    if (!ts_data->point_buf) {
+        FTS_ERROR("failed to alloc memory for point buf");
+        return -ENOMEM;
+    }
+
+    events_num = point_num * sizeof(struct ts_event);
+    ts_data->events = (struct ts_event *)kzalloc(events_num, GFP_KERNEL);
+    if (!ts_data->events) {
+        FTS_ERROR("failed to alloc memory for point events");
+        kfree_safe(ts_data->point_buf);
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+#if FTS_POWER_SOURCE_CUST_EN
+/*****************************************************************************
+* Power Control
+*****************************************************************************/
+#if FTS_PINCTRL_EN
+static int fts_pinctrl_init(struct fts_ts_data *ts)
+{
+    int ret = 0;
+
+    ts->pinctrl = devm_pinctrl_get(ts->dev);
+    if (IS_ERR_OR_NULL(ts->pinctrl)) {
+        FTS_ERROR("Failed to get pinctrl, please check dts");
+        ret = PTR_ERR(ts->pinctrl);
+        goto err_pinctrl_get;
+    }
+
+    ts->pins_active = pinctrl_lookup_state(ts->pinctrl, "ts_active");
+    if (IS_ERR_OR_NULL(ts->pins_active)) {
+        FTS_ERROR("Pin state[active] not found");
+        ret = PTR_ERR(ts->pins_active);
+        goto err_pinctrl_lookup;
+    }
+
+    ts->pins_suspend = pinctrl_lookup_state(ts->pinctrl, "ts_suspend");
+    if (IS_ERR_OR_NULL(ts->pins_suspend)) {
+        FTS_ERROR("Pin state[suspend] not found");
+        ret = PTR_ERR(ts->pins_suspend);
+        goto err_pinctrl_lookup;
+    }
+
+    return 0;
+err_pinctrl_lookup:
+    if (ts->pinctrl) {
+        devm_pinctrl_put(ts->pinctrl);
+    }
+err_pinctrl_get:
+    ts->pinctrl = NULL;
+    ts->pins_suspend = NULL;
+    ts->pins_active = NULL;
+    return ret;
+}
+
+int fts_pinctrl_select_normal(struct fts_ts_data *ts)
+{
+    int ret = 0;
+    FTS_DEBUG("Pins control select normal");
+    if (ts->pinctrl && ts->pins_active) {
+        ret = pinctrl_select_state(ts->pinctrl, ts->pins_active);
+        if (ret < 0) {
+            FTS_ERROR("Set normal pin state error:%d", ret);
+        }
+    }
+
+    return ret;
+}
+
+int fts_pinctrl_select_suspend(struct fts_ts_data *ts)
+{
+    int ret = 0;
+    FTS_DEBUG("Pins control select suspend");
+    if (ts->pinctrl && ts->pins_suspend) {
+        ret = pinctrl_select_state(ts->pinctrl, ts->pins_suspend);
+        if (ret < 0) {
+            FTS_ERROR("Set suspend pin state error:%d", ret);
+        }
+    }
+
+    return ret;
+}
+#endif /* FTS_PINCTRL_EN */
+
+static int fts_power_source_ctrl(struct fts_ts_data *ts_data, int enable)
+{
+    int ret = 0;
+
+    if (IS_ERR_OR_NULL(ts_data->avdd)) {
+        FTS_ERROR("avdd is invalid");
+        return -EINVAL;
+    }
+
+    FTS_FUNC_ENTER();
+    if (enable) {
+        if (ts_data->power_disabled) {
+            gpio_direction_output(ts_data->pdata->reset_gpio, 0);
+            msleep(2);
+            FTS_DEBUG("regulator enable !");
+            ret = regulator_enable(ts_data->avdd);
+            if (ret) {
+                FTS_ERROR("enable avdd regulator failed,ret=%d", ret);
+            }
+
+            if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
+                ret = regulator_enable(ts_data->dvdd);
+                if (ret) {
+                    FTS_ERROR("enable dvdd regulator failed,ret=%d", ret);
+                }
+            }
+            /* sleep 1 ms to power on avdd/dvdd to match spec. */
+            msleep(1);
+            gpio_direction_output(ts_data->pdata->reset_gpio, 1);
+            ts_data->power_disabled = false;
+        }
+    } else {
+        if (!ts_data->power_disabled) {
+            FTS_DEBUG("regulator disable !");
+            gpio_direction_output(ts_data->pdata->reset_gpio, 0);
+            /* sleep 1 ms to power off avdd/dvdd to match spec. */
+            msleep(1);
+            ret = regulator_disable(ts_data->avdd);
+            if (ret) {
+                FTS_ERROR("disable avdd regulator failed,ret=%d", ret);
+            }
+            if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
+                ret = regulator_disable(ts_data->dvdd);
+                if (ret) {
+                    FTS_ERROR("disable dvdd regulator failed,ret=%d", ret);
+                }
+            }
+            ts_data->power_disabled = true;
+        }
+    }
+
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+/*****************************************************************************
+* Name: fts_power_source_init
+* Brief: Init regulator power:avdd/dvdd(if have), generally, no dvdd
+*        avdd---->avdd-supply in dts, kernel will auto add "-supply" to parse
+*        Must be call after fts_gpio_configure() execute,because this function
+*        will operate reset-gpio which request gpio in fts_gpio_configure()
+* Input:
+* Output:
+* Return: return 0 if init power successfully, otherwise return error code
+*****************************************************************************/
+static int fts_power_source_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+
+#if FTS_PINCTRL_EN
+    fts_pinctrl_init(ts_data);
+    fts_pinctrl_select_normal(ts_data);
+#endif
+
+    if (of_property_read_bool(ts_data->dev->of_node, "avdd-supply")) {
+        ts_data->avdd = regulator_get(ts_data->dev, "avdd");
+        if (IS_ERR_OR_NULL(ts_data->avdd)) {
+            ret = PTR_ERR(ts_data->avdd);
+            ts_data->avdd = NULL;
+            FTS_ERROR("get avdd regulator failed,ret=%d", ret);
+            return ret;
+        }
+    } else {
+        FTS_ERROR("avdd-supply not found!");
+    }
+
+    if (of_property_read_bool(ts_data->dev->of_node, "vdd-supply")) {
+        ts_data->dvdd = regulator_get(ts_data->dev, "vdd");
+
+        if (IS_ERR_OR_NULL(ts_data->dvdd)) {
+            ret = PTR_ERR(ts_data->dvdd);
+            ts_data->dvdd = NULL;
+            FTS_ERROR("get dvdd regulator failed,ret=%d", ret);
+        }
+    } else {
+        FTS_ERROR("vdd-supply not found!");
+    }
+
+    ts_data->power_disabled = true;
+    ret = fts_power_source_ctrl(ts_data, ENABLE);
+    if (ret) {
+        FTS_ERROR("fail to enable power(regulator)");
+    }
+
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_power_source_exit(struct fts_ts_data *ts_data)
+{
+    fts_power_source_ctrl(ts_data, DISABLE);
+#if FTS_PINCTRL_EN
+    fts_pinctrl_select_suspend(ts_data);
+#endif
+    if (!IS_ERR_OR_NULL(ts_data->avdd)) {
+        regulator_put(ts_data->avdd);
+        ts_data->avdd = NULL;
+    }
+
+    if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
+        regulator_put(ts_data->dvdd);
+        ts_data->dvdd = NULL;
+    }
+
+    return 0;
+}
+
+static int fts_power_source_suspend(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+#if !defined(FTS_AOC_GESTURE_EN)
+    ret = fts_power_source_ctrl(ts_data, DISABLE);
+    if (ret < 0) {
+        FTS_ERROR("power off fail, ret=%d", ret);
+    }
+#endif
+#if FTS_PINCTRL_EN
+    fts_pinctrl_select_suspend(ts_data);
+#endif
+
+    return ret;
+}
+
+static int fts_power_source_resume(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+#if FTS_PINCTRL_EN
+    fts_pinctrl_select_normal(ts_data);
+#endif
+#if !defined(FTS_AOC_GESTURE_EN)
+    ret = fts_power_source_ctrl(ts_data, ENABLE);
+    if (ret < 0) {
+        FTS_ERROR("power on fail, ret=%d", ret);
+    }
+#endif
+    return ret;
+}
+#endif /* FTS_POWER_SOURCE_CUST_EN */
+
+static int fts_gpio_configure(struct fts_ts_data *data)
+{
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    /* request irq gpio */
+    if (gpio_is_valid(data->pdata->irq_gpio)) {
+        ret = gpio_request(data->pdata->irq_gpio, "fts_irq_gpio");
+        if (ret) {
+            FTS_ERROR("[GPIO]irq gpio request failed");
+            goto err_irq_gpio_req;
+        }
+
+        ret = gpio_direction_input(data->pdata->irq_gpio);
+        if (ret) {
+            FTS_ERROR("[GPIO]set_direction for irq gpio failed");
+            goto err_irq_gpio_dir;
+        }
+    }
+
+    /* request reset gpio */
+    if (gpio_is_valid(data->pdata->reset_gpio)) {
+        ret = gpio_request(data->pdata->reset_gpio, "fts_reset_gpio");
+        if (ret) {
+            FTS_ERROR("[GPIO]reset gpio request failed");
+            goto err_irq_gpio_dir;
+        }
+
+        ret = gpio_direction_output(data->pdata->reset_gpio, 0);
+        if (ret) {
+            FTS_ERROR("[GPIO]set_direction for reset gpio failed");
+            goto err_reset_gpio_dir;
+        }
+    }
+
+    FTS_FUNC_EXIT();
+    return 0;
+
+err_reset_gpio_dir:
+    if (gpio_is_valid(data->pdata->reset_gpio))
+        gpio_free(data->pdata->reset_gpio);
+err_irq_gpio_dir:
+    if (gpio_is_valid(data->pdata->irq_gpio))
+        gpio_free(data->pdata->irq_gpio);
+err_irq_gpio_req:
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_get_dt_coords(struct device *dev, char *name,
+                             struct fts_ts_platform_data *pdata)
+{
+    int ret = 0;
+    u32 coords[FTS_COORDS_ARR_SIZE] = { 0 };
+    struct property *prop;
+    struct device_node *np = dev->of_node;
+    int coords_size;
+
+    prop = of_find_property(np, name, NULL);
+    if (!prop)
+        return -EINVAL;
+    if (!prop->value)
+        return -ENODATA;
+
+    coords_size = prop->length / sizeof(u32);
+    if (coords_size != FTS_COORDS_ARR_SIZE) {
+        FTS_ERROR("invalid:%s, size:%d", name, coords_size);
+        return -EINVAL;
+    }
+
+    ret = of_property_read_u32_array(np, name, coords, coords_size);
+    if (ret < 0) {
+        FTS_ERROR("Unable to read %s, please check dts", name);
+        pdata->x_min = FTS_X_MIN_DISPLAY_DEFAULT;
+        pdata->y_min = FTS_Y_MIN_DISPLAY_DEFAULT;
+        pdata->x_max = FTS_X_MAX_DISPLAY_DEFAULT;
+        pdata->y_max = FTS_Y_MAX_DISPLAY_DEFAULT;
+        return -ENODATA;
+    } else {
+        pdata->x_min = coords[0];
+        pdata->y_min = coords[1];
+        pdata->x_max = coords[2];
+        pdata->y_max = coords[3];
+    }
+
+    FTS_INFO("display x(%d %d) y(%d %d)", pdata->x_min, pdata->x_max,
+             pdata->y_min, pdata->y_max);
+    return 0;
+}
+
+static int fts_parse_dt(struct device *dev, struct fts_ts_platform_data *pdata)
+{
+    int ret = 0;
+    struct device_node *np = dev->of_node;
+    u32 temp_val = 0;
+
+    FTS_FUNC_ENTER();
+
+#define DEFAULT_FW_FILE              "focaltech_ts_fw.bin"
+#define DEFAULT_TEST_INI_FILE        "focaltech_testconf.ini"
+    scnprintf(pdata->fw_name, sizeof(pdata->fw_name), "%s", DEFAULT_FW_FILE);
+    scnprintf(pdata->test_limits_name, sizeof(pdata->test_limits_name),
+              "%s", DEFAULT_TEST_INI_FILE);
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    ret = goog_parse_dt(np, pdata);
+    if (ret < 0) {
+        return ret;
+    }
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+    ret = fts_get_dt_coords(dev, "focaltech,display-coords", pdata);
+    if (ret < 0)
+        FTS_ERROR("Unable to get display-coords");
+
+    /* key */
+    pdata->have_key = of_property_read_bool(np, "focaltech,have-key");
+    if (pdata->have_key) {
+        ret = of_property_read_u32(np, "focaltech,key-number", &pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Key number undefined!");
+
+        ret = of_property_read_u32_array(np, "focaltech,keys",
+                                         pdata->keys, pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Keys undefined!");
+        else if (pdata->key_number > FTS_MAX_KEYS)
+            pdata->key_number = FTS_MAX_KEYS;
+
+        ret = of_property_read_u32_array(np, "focaltech,key-x-coords",
+                                         pdata->key_x_coords,
+                                         pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Key Y Coords undefined!");
+
+        ret = of_property_read_u32_array(np, "focaltech,key-y-coords",
+                                         pdata->key_y_coords,
+                                         pdata->key_number);
+        if (ret < 0)
+            FTS_ERROR("Key X Coords undefined!");
+
+        FTS_INFO("VK Number:%d, key:(%d,%d,%d), "
+                 "coords:(%d,%d),(%d,%d),(%d,%d)",
+                 pdata->key_number,
+                 pdata->keys[0], pdata->keys[1], pdata->keys[2],
+                 pdata->key_x_coords[0], pdata->key_y_coords[0],
+                 pdata->key_x_coords[1], pdata->key_y_coords[1],
+                 pdata->key_x_coords[2], pdata->key_y_coords[2]);
+    }
+
+    /* reset, irq gpio info */
+    pdata->reset_gpio = of_get_named_gpio(np, "focaltech,reset-gpio", 0);
+    if (pdata->reset_gpio < 0)
+        FTS_ERROR("Unable to get reset_gpio");
+
+    ret = of_property_read_u32(np, "focaltech,tx_ch_num", &temp_val);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get tx_ch_num, please check dts");
+    } else {
+        pdata->tx_ch_num = temp_val;
+        FTS_DEBUG("tx_ch_num = %d", pdata->tx_ch_num);
+    }
+
+    ret = of_property_read_u32(np, "focaltech,rx_ch_num", &temp_val);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get rx_ch_num, please check dts");
+    } else {
+        pdata->rx_ch_num = temp_val;
+        FTS_DEBUG("rx_ch_num = %d", pdata->rx_ch_num);
+    }
+
+    ret = of_property_read_u8(np, "focaltech,mm2px", &pdata->mm2px);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get mm2px, please check dts");
+        pdata->mm2px = 1;
+    } else {
+        FTS_DEBUG("mm2px = %d", pdata->mm2px);
+    }
+
+    pdata->irq_gpio = of_get_named_gpio(np, "focaltech,irq-gpio", 0);
+    if (pdata->irq_gpio < 0)
+        FTS_ERROR("Unable to get irq_gpio");
+
+    ret = of_property_read_u32(np, "focaltech,max-touch-number", &temp_val);
+    if (ret < 0) {
+        FTS_ERROR("Unable to get max-touch-number, please check dts");
+        pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT;
+    } else {
+        if (temp_val < 2)
+            pdata->max_touch_number = 2; /* max_touch_number must >= 2 */
+        else if (temp_val > FTS_MAX_POINTS_SUPPORT)
+            pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT;
+        else
+            pdata->max_touch_number = temp_val;
+    }
+
+    FTS_INFO("max touch number:%d, irq gpio:%d, reset gpio:%d",
+             pdata->max_touch_number, pdata->irq_gpio, pdata->reset_gpio);
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static void fts_suspend_work(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data,
+        suspend_work);
+
+    FTS_DEBUG("Entry");
+
+    mutex_lock(&ts_data->device_mutex);
+
+    reinit_completion(&ts_data->bus_resumed);
+    fts_ts_suspend(ts_data->dev);
+
+    mutex_unlock(&ts_data->device_mutex);
+}
+
+static void fts_resume_work(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data,
+                                  resume_work);
+
+    FTS_DEBUG("Entry");
+    mutex_lock(&ts_data->device_mutex);
+
+    fts_ts_resume(ts_data->dev);
+    complete_all(&ts_data->bus_resumed);
+
+    mutex_unlock(&ts_data->device_mutex);
+}
+
+#if defined(CONFIG_FB)
+static int fb_notifier_callback(struct notifier_block *self,
+                                unsigned long event, void *data)
+{
+    struct fb_event *evdata = data;
+    int *blank = NULL;
+    struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
+                                  fb_notif);
+
+    if (!evdata) {
+        FTS_ERROR("evdata is null");
+        return 0;
+    }
+
+    if (!(event == FB_EARLY_EVENT_BLANK || event == FB_EVENT_BLANK)) {
+        FTS_INFO("event(%lu) do not need process\n", event);
+        return 0;
+    }
+
+    blank = evdata->data;
+    FTS_INFO("FB event:%lu,blank:%d", event, *blank);
+    switch (*blank) {
+    case FB_BLANK_UNBLANK:
+        if (FB_EARLY_EVENT_BLANK == event) {
+            FTS_INFO("resume: event = %lu, not care\n", event);
+        } else if (FB_EVENT_BLANK == event) {
+            queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+        }
+        break;
+    case FB_BLANK_POWERDOWN:
+        if (FB_EARLY_EVENT_BLANK == event) {
+            cancel_work_sync(&fts_data->resume_work);
+            fts_ts_suspend(ts_data->dev);
+        } else if (FB_EVENT_BLANK == event) {
+            FTS_INFO("suspend: event = %lu, not care\n", event);
+        }
+        break;
+    default:
+        FTS_INFO("FB BLANK(%d) do not need process\n", *blank);
+        break;
+    }
+
+    return 0;
+}
+#elif defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+static struct drm_panel *active_panel;
+
+static int drm_check_dt(struct device_node *np)
+{
+    int i = 0;
+    int count = 0;
+    struct device_node *node = NULL;
+    struct drm_panel *panel = NULL;
+
+    count = of_count_phandle_with_args(np, "panel", NULL);
+    if (count <= 0) {
+        FTS_ERROR("find drm_panel count(%d) fail", count);
+        return -ENODEV;
+    }
+
+    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)) {
+            FTS_INFO("find drm_panel successfully");
+            active_panel = panel;
+            return 0;
+        }
+    }
+
+    FTS_ERROR("no find drm_panel");
+    return -ENODEV;
+}
+
+static int drm_notifier_callback(struct notifier_block *self,
+                                 unsigned long event, void *data)
+{
+    struct msm_drm_notifier *evdata = data;
+    int *blank = NULL;
+    struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
+                                  fb_notif);
+
+    if (!evdata) {
+        FTS_ERROR("evdata is null");
+        return 0;
+    }
+
+    if (!((event == DRM_PANEL_EARLY_EVENT_BLANK )
+          || (event == DRM_PANEL_EVENT_BLANK))) {
+        FTS_INFO("event(%lu) do not need process\n", event);
+        return 0;
+    }
+
+    blank = evdata->data;
+    FTS_INFO("DRM event:%lu,blank:%d", event, *blank);
+    switch (*blank) {
+    case DRM_PANEL_BLANK_UNBLANK:
+        if (DRM_PANEL_EARLY_EVENT_BLANK == event) {
+            FTS_INFO("resume: event = %lu, not care\n", event);
+        } else if (DRM_PANEL_EVENT_BLANK == event) {
+            queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+        }
+        break;
+    case DRM_PANEL_BLANK_POWERDOWN:
+        if (DRM_PANEL_EARLY_EVENT_BLANK == event) {
+            cancel_work_sync(&fts_data->resume_work);
+            fts_ts_suspend(ts_data->dev);
+        } else if (DRM_PANEL_EVENT_BLANK == event) {
+            FTS_INFO("suspend: event = %lu, not care\n", event);
+        }
+        break;
+    default:
+        FTS_INFO("DRM BLANK(%d) do not need process\n", *blank);
+        break;
+    }
+
+    return 0;
+}
+#elif defined(CONFIG_ARCH_MSM)
+static int drm_notifier_callback(struct notifier_block *self,
+                                 unsigned long event, void *data)
+{
+    struct msm_drm_notifier *evdata = data;
+    int *blank = NULL;
+    struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
+                                  fb_notif);
+
+    if (!evdata) {
+        FTS_ERROR("evdata is null");
+        return 0;
+    }
+
+    if (!((event == MSM_DRM_EARLY_EVENT_BLANK )
+          || (event == MSM_DRM_EVENT_BLANK))) {
+        FTS_INFO("event(%lu) do not need process\n", event);
+        return 0;
+    }
+
+    blank = evdata->data;
+    FTS_INFO("DRM event:%lu,blank:%d", event, *blank);
+    switch (*blank) {
+    case MSM_DRM_BLANK_UNBLANK:
+        if (MSM_DRM_EARLY_EVENT_BLANK == event) {
+            FTS_INFO("resume: event = %lu, not care\n", event);
+        } else if (MSM_DRM_EVENT_BLANK == event) {
+            queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+        }
+        break;
+    case MSM_DRM_BLANK_POWERDOWN:
+        if (MSM_DRM_EARLY_EVENT_BLANK == event) {
+            cancel_work_sync(&fts_data->resume_work);
+            fts_ts_suspend(ts_data->dev);
+        } else if (MSM_DRM_EVENT_BLANK == event) {
+            FTS_INFO("suspend: event = %lu, not care\n", event);
+        }
+        break;
+    default:
+        FTS_INFO("DRM BLANK(%d) do not need process\n", *blank);
+        break;
+    }
+
+    return 0;
+}
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+static void fts_ts_early_suspend(struct early_suspend *handler)
+{
+    struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data,
+                                  early_suspend);
+
+    cancel_work_sync(&fts_data->resume_work);
+    fts_ts_suspend(ts_data->dev);
+}
+
+static void fts_ts_late_resume(struct early_suspend *handler)
+{
+    struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data,
+                                  early_suspend);
+
+    queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
+}
+#endif
+
+static int fts_ts_probe_entry(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int pdata_size = sizeof(struct fts_ts_platform_data);
+
+    FTS_FUNC_ENTER();
+    ts_data->driver_probed = false;
+    FTS_INFO("%s", FTS_DRIVER_VERSION);
+    ts_data->pdata = kzalloc(pdata_size, GFP_KERNEL);
+    if (!ts_data->pdata) {
+        FTS_ERROR("allocate memory for platform_data fail");
+        return -ENOMEM;
+    }
+
+    if (ts_data->dev->of_node) {
+        ret = fts_parse_dt(ts_data->dev, ts_data->pdata);
+        if (ret) {
+            FTS_ERROR("device-tree parse fail");
+            return ret;
+        }
+
+#if defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+        ret = drm_check_dt(ts_data->dev->of_node);
+        if (ret) {
+            FTS_ERROR("parse drm-panel fail");
+        }
+#endif
+#endif
+    } else {
+        if (ts_data->dev->platform_data) {
+            memcpy(ts_data->pdata, ts_data->dev->platform_data, pdata_size);
+        } else {
+            FTS_ERROR("platform_data is null");
+            return -ENODEV;
+        }
+    }
+
+    ts_data->ts_workqueue = create_singlethread_workqueue("fts_wq");
+    if (!ts_data->ts_workqueue) {
+        FTS_ERROR("create fts workqueue fail");
+    }
+
+    spin_lock_init(&ts_data->irq_lock);
+    mutex_init(&ts_data->report_mutex);
+    mutex_init(&ts_data->bus_lock);
+    mutex_init(&ts_data->reg_lock);
+    ts_data->is_deepsleep = false;
+
+    mutex_init(&ts_data->device_mutex);
+    init_completion(&ts_data->bus_resumed);
+    complete_all(&ts_data->bus_resumed);
+
+    /* Init communication interface */
+    ret = fts_bus_init(ts_data);
+    if (ret) {
+        FTS_ERROR("bus initialize fail");
+        goto err_bus_init;
+    }
+
+    ret = fts_input_init(ts_data);
+    if (ret) {
+        FTS_ERROR("input initialize fail");
+        goto err_input_init;
+    }
+
+    ret = fts_report_buffer_init(ts_data);
+    if (ret) {
+        FTS_ERROR("report buffer init fail");
+        goto err_report_buffer;
+    }
+
+    ret = fts_gpio_configure(ts_data);
+    if (ret) {
+        FTS_ERROR("configure the gpios fail");
+        goto err_gpio_config;
+    }
+
+#if FTS_POWER_SOURCE_CUST_EN
+    ret = fts_power_source_init(ts_data);
+    if (ret) {
+        FTS_ERROR("fail to get power(regulator)");
+        goto err_power_init;
+    }
+#endif
+
+#if (!FTS_CHIP_IDC)
+    fts_reset_proc(FTS_RESET_INTERVAL);
+#endif
+
+    ret = fts_get_ic_information(ts_data);
+    if (ret) {
+        FTS_ERROR("not focal IC, unregister driver");
+        goto err_power_init;
+    }
+
+    ret = fts_create_apk_debug_channel(ts_data);
+    if (ret) {
+        FTS_ERROR("create apk debug node fail");
+    }
+
+#if GOOGLE_REPORT_MODE
+    memset(ts_data->current_host_status.data, 0, sizeof(struct fw_status_ts));
+#endif
+
+    ts_data->enable_fw_grip = FW_GRIP_ENABLE;
+    ts_data->enable_fw_palm = FW_GRIP_ENABLE;
+    ts_data->glove_mode = DISABLE;
+    fts_update_feature_setting(ts_data);
+
+    ret = fts_create_sysfs(ts_data);
+    if (ret) {
+        FTS_ERROR("create sysfs node fail");
+    }
+
+#if FTS_POINT_REPORT_CHECK_EN
+    ret = fts_point_report_check_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init point report check fail");
+    }
+#endif
+
+    ret = fts_ex_mode_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init glove/cover/charger fail");
+    }
+
+    ret = fts_gesture_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init gesture fail");
+    }
+
+#if FTS_TEST_EN
+    ret = fts_test_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init production test fail");
+    }
+#endif
+
+#if FTS_ESDCHECK_EN
+    ret = fts_esdcheck_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init esd check fail");
+    }
+#endif
+    /* init pm_qos before interrupt registered. */
+    cpu_latency_qos_add_request(&ts_data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
+
+    ret = fts_irq_registration(ts_data);
+    if (ret) {
+        FTS_ERROR("request irq failed");
+        goto err_irq_req;
+    }
+
+    if (ts_data->ts_workqueue) {
+        INIT_WORK(&ts_data->resume_work, fts_resume_work);
+        INIT_WORK(&ts_data->suspend_work, fts_suspend_work);
+    }
+
+    ret = fts_fwupg_init(ts_data);
+    if (ret) {
+        FTS_ERROR("init fw upgrade fail");
+    }
+
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+    init_completion(&ts_data->pm_completion);
+    ts_data->pm_suspend = false;
+#endif
+
+#if defined(CONFIG_FB)
+    ts_data->fb_notif.notifier_call = fb_notifier_callback;
+    ret = fb_register_client(&ts_data->fb_notif);
+    if (ret) {
+        FTS_ERROR("[FB]Unable to register fb_notifier: %d", ret);
+    }
+#elif defined(CONFIG_DRM_PANEL) || defined(CONFIG_ARCH_MSM)
+    ts_data->fb_notif.notifier_call = drm_notifier_callback;
+#if defined(CONFIG_DRM_PANEL)
+    if (active_panel) {
+        ret = drm_panel_notifier_register(active_panel, &ts_data->fb_notif);
+        if (ret)
+            FTS_ERROR("[DRM]drm_panel_notifier_register fail: %d\n", ret);
+    }
+#elif defined(CONFIG_ARCH_MSM)
+    ret = msm_drm_register_client(&ts_data->fb_notif);
+    if (ret) {
+        FTS_ERROR("[DRM]Unable to register fb_notifier: %d\n", ret);
+    }
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+    ts_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + FTS_SUSPEND_LEVEL;
+    ts_data->early_suspend.suspend = fts_ts_early_suspend;
+    ts_data->early_suspend.resume = fts_ts_late_resume;
+    register_early_suspend(&ts_data->early_suspend);
+#endif
+
+    ts_data->work_mode = FTS_REG_WORKMODE_WORK_VALUE;
+
+    ts_data->driver_probed = true;
+    FTS_FUNC_EXIT();
+    return 0;
+
+err_irq_req:
+    cpu_latency_qos_remove_request(&ts_data->pm_qos_req);
+
+#if FTS_POWER_SOURCE_CUST_EN
+err_power_init:
+    fts_power_source_exit(ts_data);
+#endif
+    if (gpio_is_valid(ts_data->pdata->reset_gpio))
+        gpio_free(ts_data->pdata->reset_gpio);
+    if (gpio_is_valid(ts_data->pdata->irq_gpio))
+        gpio_free(ts_data->pdata->irq_gpio);
+err_gpio_config:
+    kfree_safe(ts_data->point_buf);
+    kfree_safe(ts_data->events);
+err_report_buffer:
+    input_unregister_device(ts_data->input_dev);
+#if FTS_PEN_EN
+    input_unregister_device(ts_data->pen_dev);
+#endif
+err_input_init:
+    if (ts_data->ts_workqueue)
+        destroy_workqueue(ts_data->ts_workqueue);
+err_bus_init:
+    kfree_safe(ts_data->bus_tx_buf);
+    kfree_safe(ts_data->bus_rx_buf);
+    kfree_safe(ts_data->pdata);
+
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_ts_remove_entry(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    if (ts_data->gti)
+        goog_gti_remove(ts_data);
+    else
+        free_irq(ts_data->irq, ts_data);
+#else
+    free_irq(ts_data->irq, ts_data);
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+#if FTS_POINT_REPORT_CHECK_EN
+    fts_point_report_check_exit(ts_data);
+#endif
+    fts_release_apk_debug_channel(ts_data);
+
+#if FTS_TEST_EN
+    /* remove the test nodes and sub-dir in /proc/focaltech_touch/selftest/ */
+    fts_test_exit(ts_data);
+#endif
+    /* remove all nodes and sub-dir in /proc/focaltech_touch/ */
+    fts_remove_sysfs(ts_data);
+
+    fts_ex_mode_exit(ts_data);
+
+    fts_fwupg_exit(ts_data);
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_exit(ts_data);
+#endif
+
+    fts_gesture_exit(ts_data);
+    fts_bus_exit(ts_data);
+
+    input_unregister_device(ts_data->input_dev);
+#if FTS_PEN_EN
+    input_unregister_device(ts_data->pen_dev);
+#endif
+
+    cancel_work_sync(&ts_data->suspend_work);
+    cancel_work_sync(&ts_data->resume_work);
+
+    if (ts_data->ts_workqueue)
+        destroy_workqueue(ts_data->ts_workqueue);
+
+    cpu_latency_qos_remove_request(&ts_data->pm_qos_req);
+
+#if defined(CONFIG_FB)
+    if (fb_unregister_client(&ts_data->fb_notif))
+        FTS_ERROR("[FB]Error occurred while unregistering fb_notifier.");
+#elif defined(CONFIG_DRM)
+#if defined(CONFIG_DRM_PANEL)
+    if (active_panel)
+        drm_panel_notifier_unregister(active_panel, &ts_data->fb_notif);
+#elif defined(CONFIG_ARCH_MSM)
+    if (msm_drm_unregister_client(&ts_data->fb_notif))
+        FTS_ERROR("[DRM]Error occurred while unregistering fb_notifier.\n");
+#endif
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+    unregister_early_suspend(&ts_data->early_suspend);
+#endif
+
+    if (gpio_is_valid(ts_data->pdata->reset_gpio))
+        gpio_free(ts_data->pdata->reset_gpio);
+
+    if (gpio_is_valid(ts_data->pdata->irq_gpio))
+        gpio_free(ts_data->pdata->irq_gpio);
+
+#if FTS_POWER_SOURCE_CUST_EN
+    fts_power_source_exit(ts_data);
+#endif
+
+    kfree_safe(ts_data->point_buf);
+    kfree_safe(ts_data->events);
+
+    kfree_safe(ts_data->pdata);
+    kfree_safe(ts_data);
+
+    FTS_FUNC_EXIT();
+
+    return 0;
+}
+
+static int fts_write_reg_safe(u8 reg, u8 write_val) {
+    int ret = 0;
+    int i;
+    int j;
+    u8 reg_val;
+
+    for (i = 0; i < MAX_RETRY_CNT; i++) {
+        ret = fts_write_reg(reg, write_val);
+        if (ret < 0) {
+            FTS_DEBUG("write 0x%X failed", reg);
+            return ret;
+        }
+        for (j = 0; j < MAX_RETRY_CNT; j++) {
+            reg_val = 0xFF;
+            ret = fts_read_reg(reg, &reg_val);
+            if (ret < 0) {
+                FTS_DEBUG("read 0x%X failed", reg);
+                return ret;
+            }
+            if (write_val == reg_val) {
+                return ret;
+            }
+            msleep(1);
+        }
+
+        FTS_ERROR("%s failed, reg(0x%X), write_val(0x%x), reg_val(0x%x), " \
+            "retry(%d)", __func__, reg, write_val, reg_val, i);
+    }
+    if (i == MAX_RETRY_CNT)
+        ret = -EIO;
+    return ret;
+}
+
+int fts_set_heatmap_mode(struct fts_ts_data *ts_data, u8 heatmap_mode)
+{
+    int ret = 0;
+    int count = 0;
+    char tmpbuf[FTS_MESSAGE_LENGTH];
+
+    switch (heatmap_mode) {
+      case FW_HEATMAP_MODE_DISABLE:
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Disable fw_heatmap");
+          break;
+      case FW_HEATMAP_MODE_DIFF:
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Enable Diff fw_heatmap");
+          break;
+      case FW_HEATMAP_MODE_BASELINE:
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Enable Baseline fw_heatmap");
+          break;
+      case FW_HEATMAP_MODE_RAWDATA:
+          count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
+              "Enable Rawdata fw_heatmap");
+          break;
+      default:
+          FTS_ERROR("The input heatmap mode(%d) is invalid.", heatmap_mode);
+          return -EINVAL;
+    }
+
+    ret = fts_write_reg_safe(FTS_REG_HEATMAP_98, heatmap_mode);
+
+    FTS_DEBUG("%s %s.\n", tmpbuf,
+        (ret == 0) ? "successfully" : "unsuccessfully");
+
+    return ret;
+}
+
+int fts_set_grip_mode(struct fts_ts_data *ts_data, u8 grip_mode)
+{
+    int ret = 0;
+    bool en = grip_mode % 2;
+    u8 value = en ? 0x00 : 0xAA;
+    u8 reg = FTS_REG_EDGE_MODE_EN;
+
+    ret = fts_write_reg_safe(reg, value);
+
+    FTS_DEBUG("%s fw_grip(%d) %s.\n", en ? "Enable" : "Disable",
+        ts_data->enable_fw_grip,
+        (ret == 0)  ? "successfully" : "unsuccessfully");
+    return ret;
+}
+
+int fts_set_palm_mode(struct fts_ts_data *ts_data, u8 palm_mode)
+{
+    int ret = 0;
+    bool en = palm_mode % 2;
+    u8 value = en ? ENABLE : DISABLE;
+    u8 reg = FTS_REG_PALM_EN;
+
+    ret = fts_write_reg_safe(reg, value);
+
+    FTS_DEBUG("%s fw_palm(%d) %s.\n", en ? "Enable" : "Disable",
+        ts_data->enable_fw_palm,
+        (ret == 0) ? "successfully" : "unsuccessfully");
+    return ret;
+}
+
+int fts_set_glove_mode(struct fts_ts_data *ts_data, bool en)
+{
+    int ret = 0;
+    u8 value = en ? ENABLE : DISABLE;
+    u8 reg = FTS_REG_GLOVE_MODE_EN;
+
+    ret = fts_write_reg_safe(reg, value);
+    if (ret == 0) {
+        ts_data->glove_mode = value;
+    }
+
+    FTS_DEBUG("%s fw_glove %s.\n", en ? "Enable" : "Disable",
+        (ret == 0) ? "successfully" : "unsuccessfully");
+    return ret;
+}
+
+/**
+ * fts_update_feature_setting()
+ *
+ * Restore the feature settings after the device resume.
+ *
+ * @param
+ *    [ in] ts_data: touch driver handle.
+ *
+ */
+void fts_update_feature_setting(struct fts_ts_data *ts_data)
+{
+    FTS_INFO("Restore touch feature settings.");
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    struct gti_fw_status_data gti_status_data = { 0 };
+
+    goog_notify_fw_status_changed(ts_data->gti, GTI_FW_STATUS_RESET, &gti_status_data);
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+}
+
+static int fts_ts_suspend(struct device *dev)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    FTS_FUNC_ENTER();
+
+    if (ts_data->suspended) {
+        FTS_INFO("Already in suspend state");
+        return 0;
+    }
+
+    if (ts_data->fw_loading) {
+        FTS_INFO("fw upgrade in process, can't suspend");
+        return 0;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_suspend();
+#endif
+
+    /* Disable irq */
+    fts_irq_disable();
+
+    FTS_DEBUG("make TP enter into sleep mode");
+    mutex_lock(&ts_data->reg_lock);
+    ret = fts_write_reg(FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP);
+    ts_data->is_deepsleep = true;
+    mutex_unlock(&ts_data->reg_lock);
+    if (ret < 0)
+      FTS_ERROR("set TP to sleep mode fail, ret=%d", ret);
+
+    if (!ts_data->ic_info.is_incell) {
+#if FTS_POWER_SOURCE_CUST_EN
+      ret = fts_power_source_suspend(ts_data);
+      if (ret < 0) {
+        FTS_ERROR("power enter suspend fail");
+      }
+#endif
+    }
+
+    fts_release_all_finger();
+    ts_data->suspended = true;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+
+/**
+ * Report a finger down event on the long press gesture area then immediately
+ * report a cancel event(MT_TOOL_PALM).
+ */
+static void fts_report_cancel_event(struct fts_ts_data *ts_data)
+{
+    FTS_INFO("Report cancel event for UDFPS");
+
+    mutex_lock(&ts_data->report_mutex);
+    /* Finger down on UDFPS area. */
+    input_mt_slot(ts_data->input_dev, 0);
+    input_report_key(ts_data->input_dev, BTN_TOUCH, 1);
+    input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 1);
+/*    input_report_abs(ts_data->input_dev, ABS_MT_POSITION_X,
+        ts_data->fts_gesture_data.coordinate_x[0]);
+    input_report_abs(ts_data->input_dev, ABS_MT_POSITION_Y,
+        ts_data->fts_gesture_data.coordinate_y[0]);
+    input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR,
+        ts_data->fts_gesture_data.major[0]);
+    input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MINOR,
+        ts_data->fts_gesture_data.minor[0]);
+        */
+#ifndef SKIP_PRESSURE
+    input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 1);
+#endif
+    //input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION,
+    //    ts_data->fts_gesture_data.orientation[0]);
+    input_sync(ts_data->input_dev);
+
+    /* Report MT_TOOL_PALM for canceling the touch event. */
+    input_mt_slot(ts_data->input_dev, 0);
+    input_report_key(ts_data->input_dev, BTN_TOUCH, 1);
+    input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_PALM, 1);
+    input_sync(ts_data->input_dev);
+
+    /* Release touches. */
+    input_mt_slot(ts_data->input_dev, 0);
+#ifndef SKIP_PRESSURE
+    input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 0);
+#endif
+    input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 0);
+    input_report_abs(ts_data->input_dev, ABS_MT_TRACKING_ID, -1);
+    input_report_key(ts_data->input_dev, BTN_TOUCH, 0);
+    input_sync(ts_data->input_dev);
+    mutex_unlock(&ts_data->report_mutex);
+}
+
+static void fts_check_finger_status(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    u8 power_mode = FTS_REG_POWER_MODE_SLEEP;
+    ktime_t timeout = ktime_add_ms(ktime_get(), 500); /* 500ms. */
+
+    /* If power mode is deep sleep mode, then reurn. */
+    ret = fts_read_reg(FTS_REG_POWER_MODE, &power_mode);
+    if (ret)
+        return;
+
+    if (power_mode == FTS_REG_POWER_MODE_SLEEP)
+        return;
+
+    while (ktime_get() < timeout) {
+        ret = fts_gesture_readdata(ts_data);
+        if (ret)
+            break;
+
+        if (ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_LPTW_DOWN) {
+            msleep(30);
+            continue;
+        }
+
+        if (ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_LPTW_UP ||
+            ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_STTW) {
+            fts_report_cancel_event(ts_data);
+        }
+        break;
+    }
+}
+
+static int fts_ts_resume(struct device *dev)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    if (!ts_data->suspended) {
+        FTS_DEBUG("Already in awake state");
+        return 0;
+    }
+
+    fts_release_all_finger();
+
+    if (!ts_data->ic_info.is_incell) {
+        if (!ts_data->gesture_mode) {
+#if FTS_POWER_SOURCE_CUST_EN
+            fts_power_source_resume(ts_data);
+#endif
+            fts_check_finger_status(ts_data);
+        }
+
+        fts_reset_proc(FTS_RESET_INTERVAL);
+    }
+
+    ret = fts_wait_tp_to_valid();
+    if (ret != 0) {
+        FTS_ERROR("Resume has been cancelled by wake up timeout");
+#if FTS_POWER_SOURCE_CUST_EN
+        if (!ts_data->gesture_mode)
+            fts_power_source_suspend(ts_data);
+#endif
+        return ret;
+    }
+
+    ts_data->is_deepsleep = false;
+    fts_ex_mode_recovery(ts_data);
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_resume();
+#endif
+
+    fts_irq_enable();
+
+    ts_data->suspended = false;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+static int fts_pm_suspend(struct device *dev)
+{
+    struct fts_ts_data *ts_data = dev_get_drvdata(dev);
+
+    FTS_INFO("system enters into pm_suspend");
+    ts_data->pm_suspend = true;
+    reinit_completion(&ts_data->pm_completion);
+    return 0;
+}
+
+static int fts_pm_resume(struct device *dev)
+{
+    struct fts_ts_data *ts_data = dev_get_drvdata(dev);
+
+    FTS_INFO("system resumes from pm_suspend");
+    ts_data->pm_suspend = false;
+    complete(&ts_data->pm_completion);
+    return 0;
+}
+
+static const struct dev_pm_ops fts_dev_pm_ops = {
+    .suspend = fts_pm_suspend,
+    .resume = fts_pm_resume,
+};
+#endif
+
+/*****************************************************************************
+* TP Driver
+*****************************************************************************/
+static int fts_ts_probe(struct spi_device *spi)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = NULL;
+
+    FTS_INFO("Touch Screen(SPI BUS) driver proboe...");
+    spi->mode = SPI_MODE_0;
+    spi->bits_per_word = 8;
+    spi->rt = true;
+    ret = spi_setup(spi);
+    if (ret) {
+        FTS_ERROR("spi setup fail");
+        return ret;
+    }
+
+    /* malloc memory for global struct variable */
+    ts_data = (struct fts_ts_data *)kzalloc(sizeof(*ts_data), GFP_KERNEL);
+    if (!ts_data) {
+        FTS_ERROR("allocate memory for fts_data fail");
+        return -ENOMEM;
+    }
+
+    fts_data = ts_data;
+    ts_data->spi = spi;
+    ts_data->dev = &spi->dev;
+    ts_data->log_level = FTS_KEY_LOG_LEVEL;
+
+    ts_data->bus_type = FTS_BUS_TYPE_SPI_V2;
+    spi_set_drvdata(spi, ts_data);
+    ts_data->spi_speed = spi->max_speed_hz;
+
+    ret = fts_ts_probe_entry(ts_data);
+    if (ret) {
+        FTS_ERROR("Touch Screen(SPI BUS) driver probe fail");
+        kfree_safe(ts_data);
+        return ret;
+    }
+
+    FTS_INFO("Touch Screen(SPI BUS) driver probe successfully");
+    return 0;
+}
+
+static void fts_ts_remove(struct spi_device *spi)
+{
+    fts_ts_remove_entry(spi_get_drvdata(spi));
+}
+
+static void fts_ts_shutdown(struct spi_device *spi)
+{
+    fts_ts_remove(spi);
+}
+
+static const struct spi_device_id fts_ts_id[] = {
+    {FTS_DRIVER_NAME, 0},
+    {},
+};
+static const struct of_device_id fts_dt_match[] = {
+    {.compatible = "focaltech,ts", },
+    {},
+};
+MODULE_DEVICE_TABLE(of, fts_dt_match);
+
+static struct spi_driver fts_ts_driver = {
+    .probe = fts_ts_probe,
+    .remove = fts_ts_remove,
+    .shutdown = fts_ts_shutdown,
+    .driver = {
+        .name = FTS_DRIVER_NAME,
+        .owner = THIS_MODULE,
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+        .pm = &fts_dev_pm_ops,
+#endif
+        .of_match_table = of_match_ptr(fts_dt_match),
+    },
+    .id_table = fts_ts_id,
+};
+
+static int __init fts_ts_init(void)
+{
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    ret = spi_register_driver(&fts_ts_driver);
+    if ( ret != 0 ) {
+        FTS_ERROR("Focaltech touch screen driver init failed!");
+    }
+    FTS_FUNC_EXIT();
+    return ret;
+}
+
+static void __exit fts_ts_exit(void)
+{
+    spi_unregister_driver(&fts_ts_driver);
+}
+
+module_init(fts_ts_init);
+module_exit(fts_ts_exit);
+
+MODULE_AUTHOR("FocalTech Driver Team");
+MODULE_DESCRIPTION("FocalTech Touchscreen Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/ft3683u/focaltech_core.h b/ft3683u/focaltech_core.h
new file mode 100644
index 0000000..f251320
--- /dev/null
+++ b/ft3683u/focaltech_core.h
@@ -0,0 +1,482 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+/*****************************************************************************
+*
+* File Name: focaltech_core.h
+
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+#ifndef __LINUX_FOCALTECH_CORE_H__
+#define __LINUX_FOCALTECH_CORE_H__
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/uaccess.h>
+#include <linux/firmware.h>
+#include <linux/debugfs.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/jiffies.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_qos.h>
+#include "focaltech_common.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_MAX_POINTS_SUPPORT              10 /* constant value, can't be changed */
+#define FTS_MAX_KEYS                        4
+#define FTS_KEY_DIM                         10
+#define FTS_ONE_TCH_LEN                     6
+#define FTS_TOUCH_DATA_LEN  (FTS_MAX_POINTS_SUPPORT * FTS_ONE_TCH_LEN + 3)
+
+#define FTS_MAX_ID                          0x0A
+#define FTS_TOUCH_X_H_POS                   3
+#define FTS_TOUCH_X_L_POS                   4
+#define FTS_TOUCH_Y_H_POS                   5
+#define FTS_TOUCH_Y_L_POS                   6
+#define FTS_TOUCH_PRE_POS                   7
+#define FTS_TOUCH_AREA_POS                  8
+#define FTS_TOUCH_POINT_NUM                 1
+#define FTS_TOUCH_EVENT_POS                 3
+#define FTS_TOUCH_ID_POS                    5
+#define FTS_COORDS_ARR_SIZE                 4
+#define FTS_X_MIN_DISPLAY_DEFAULT           0
+#define FTS_Y_MIN_DISPLAY_DEFAULT           0
+#define FTS_X_MAX_DISPLAY_DEFAULT           720
+#define FTS_Y_MAX_DISPLAY_DEFAULT           1280
+
+#define FTS_TOUCH_DOWN                      0
+#define FTS_TOUCH_UP                        1
+#define FTS_TOUCH_CONTACT                   2
+#define EVENT_DOWN(flag)                    ((FTS_TOUCH_DOWN == flag) || (FTS_TOUCH_CONTACT == flag))
+#define EVENT_UP(flag)                      (FTS_TOUCH_UP == flag)
+#define EVENT_NO_DOWN(data)                 (!data->point_num)
+
+#define FTS_MAX_COMPATIBLE_TYPE             4
+#define FTS_MAX_COMMMAND_LENGTH             16
+
+
+#define FTS_TOUCH_OFF_E_XH                  0
+#define FTS_TOUCH_OFF_XL                    1
+#define FTS_TOUCH_OFF_ID_YH                 2
+#define FTS_TOUCH_OFF_YL                    3
+#define FTS_TOUCH_OFF_PRE                   4
+#define FTS_TOUCH_OFF_MAJOR                 5
+#define FTS_TOUCH_OFF_MINOR                 6
+#define FTS_TOUCH_OFF_ORIENTATION           7
+
+#define FTS_TOUCH_E_NUM                     1
+#define FTS_ONE_TCH_LEN_V2                  8
+#define FTS_TOUCH_DATA_LEN_V2  (FTS_MAX_POINTS_SUPPORT * FTS_ONE_TCH_LEN_V2 + 4)
+#define FTS_HI_RES_X_MAX                    16
+#define FTS_TOUCH_HIRES_X                   10
+
+#define FTS_TOUCH_HIRES_EN                  1
+
+#if FTS_TOUCH_HIRES_EN
+#define FTS_TOUCH_HIRES(x) ((x) * FTS_TOUCH_HIRES_X / FTS_HI_RES_X_MAX)
+#else
+#define FTS_TOUCH_HIRES(x) ((x) / FTS_HI_RES_X_MAX)
+#endif // FTS_TOUCH_HIRES_EN
+
+#define FTS_PEN_HIRES_EN                    1
+#define FTS_PEN_HIRES_X                     10
+
+
+
+
+
+/*****************************************************************************
+*  Alternative mode (When something goes wrong, the modules may be able to solve the problem.)
+*****************************************************************************/
+/*
+ * For commnication error in PM(deep sleep) state
+ */
+#define FTS_PATCH_COMERR_PM                     0
+#define FTS_TIMEOUT_COMERR_PM                   700
+
+#define FTS_HIGH_REPORT                         0
+#define FTS_SIZE_DEFAULT                        15
+
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+struct ftxxxx_proc {
+    struct proc_dir_entry *proc_entry;
+    u8 opmode;
+    u8 cmd_len;
+    u8 cmd[FTS_MAX_COMMMAND_LENGTH];
+};
+
+struct fts_ts_platform_data {
+    u32 irq_gpio;
+    u32 reset_gpio;
+    struct drm_panel *panel;
+    u32 initial_panel_index;
+    bool have_key;
+    u32 key_number;
+    u32 keys[FTS_MAX_KEYS];
+    u32 key_y_coords[FTS_MAX_KEYS];
+    u32 key_x_coords[FTS_MAX_KEYS];
+    u32 x_max;
+    u32 y_max;
+    u32 x_min;
+    u32 y_min;
+    u32 max_touch_number;
+    u32 tx_ch_num;
+    u32 rx_ch_num;
+    /* convert mm to pixel for major and minor */
+    u8 mm2px;
+    char fw_name[FILE_NAME_LENGTH];
+    char test_limits_name[FILE_NAME_LENGTH];
+    int panel_id;
+};
+
+struct ts_event {
+    int x;      /*x coordinate */
+    int y;      /*y coordinate */
+    int p;      /* pressure */
+    int flag;   /* touch event flag: 0 -- down; 1-- up; 2 -- contact */
+    int id;     /*touch ID */
+    int major;
+    int minor;
+    int orientation;
+};
+
+struct pen_event {
+    int inrange;
+    int tip;
+    int x;      /*x coordinate */
+    int y;      /*y coordinate */
+    int p;      /* pressure */
+    int flag;   /* touch event flag: 0 -- down; 1-- up; 2 -- contact */
+    int id;     /*touch ID */
+    int tilt_x;
+    int tilt_y;
+    int tool_type;
+};
+
+struct fts_gesture_st {
+    union {
+        struct {
+            u8 gesture_id;
+            u8 coordinate_x_msb;
+            u8 coordinate_x_lsb;
+            u8 coordinate_y_msb;
+            u8 coordinate_y_lsb;
+            u8 orientation;
+            u8 major;
+            u8 minor;
+            u8 gesture_enable;
+            u8 point_id;
+            u8 point_num;
+            u8 FOD_area;
+            u8 touch_area;
+            u8 even;
+        } __attribute__((packed));
+        u8 data[14];
+    };
+};
+
+struct fw_status_ts {
+  union {
+    struct {
+      unsigned char B0_b0_abnormal_reset:3;
+      unsigned char B0_b3_water_state:1;
+      unsigned char B0_b4_grip_status:1;
+      unsigned char B0_b5_palm_status:1;
+      unsigned char B0_b6_edge_palm_status:1;
+      unsigned char B0_b7_reserved:1;
+
+      unsigned char B1_b0_baseline:3;
+      unsigned char B1_b3_noise_status:3;
+      unsigned char B1_b6_INT2_status:1;
+      unsigned char B1_b7_continuous_status:1;
+
+      unsigned char B2_b0_frequency_hopping:3;
+      unsigned char B2_b3_v_sync_status:1;
+      unsigned char B2_b4_reserved:4;
+
+      unsigned char B3_b0_glove_reg:1;
+      unsigned char B3_b1_grip_reg:1;
+      unsigned char B3_b2_palm_reg:1;
+      unsigned char B3_b3_reserved:1;
+      unsigned char B3_b4_continus_reg:1;
+      unsigned char B3_b5_reserved:1;
+      unsigned char B3_b6_heatmap_status:2;
+    } __attribute__((packed));
+    unsigned char data[4];
+  };
+};
+
+
+
+enum SS_TYPE {
+    SS_NORMAL,
+    SS_WATER,
+};
+
+struct fts_ts_data {
+    struct i2c_client *client;
+    struct spi_device *spi;
+    u32 spi_speed;
+    struct device *dev;
+    struct input_dev *input_dev;
+    struct input_dev *pen_dev;
+    struct fts_ts_platform_data *pdata;
+    struct ts_ic_info ic_info;
+    struct workqueue_struct *ts_workqueue;
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    struct delayed_work fwupg_work;
+#else
+    struct work_struct fwupg_work;
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    struct delayed_work esdcheck_work;
+    struct delayed_work prc_work;
+    struct work_struct resume_work;
+    struct work_struct suspend_work;
+    struct pm_qos_request pm_qos_req;
+    struct ftxxxx_proc proc;
+    spinlock_t irq_lock;
+    struct mutex report_mutex;
+    struct mutex bus_lock;
+    struct mutex reg_lock;
+    struct mutex device_mutex;
+    struct completion bus_resumed;
+    struct fts_gesture_st fts_gesture_data;
+    unsigned long intr_jiffies;
+    int irq;
+    int log_level;
+    int fw_is_running;      /* confirm fw is running when using spi:default 0 */
+    int dummy_byte;
+#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
+    struct completion pm_completion;
+    bool pm_suspend;
+#endif
+    bool suspended;
+    bool fw_loading;
+    bool irq_disabled;
+    bool power_disabled;
+    bool glove_mode;
+    bool cover_mode;
+    bool charger_mode;
+    bool gesture_mode;      /* gesture enable or disable, default: disable */
+    bool prc_mode;
+    bool driver_probed;
+    struct pen_event pevent;
+    /* multi-touch */
+    struct ts_event *events;
+    u8 *bus_tx_buf;
+    u8 *bus_rx_buf;
+    int bus_type;
+    u8 *point_buf;
+    int pnt_buf_size;
+    int touchs;
+    int key_state;
+    int touch_point;
+    int point_num;
+
+#if GOOGLE_REPORT_MODE
+    struct fw_status_ts current_host_status;
+#endif
+
+    u8 work_mode;
+
+    u8 enable_fw_grip;
+    u8 enable_fw_palm;
+    ktime_t isr_timestamp; /* Time that the event was first received from the
+                        * touch IC, acquired during hard interrupt, in
+                        * CLOCK_MONOTONIC */
+    ktime_t coords_timestamp;
+    bool is_deepsleep;
+    struct proc_dir_entry *proc_touch_entry;
+    struct regulator *avdd;
+    struct regulator *dvdd;
+#if FTS_PINCTRL_EN
+    struct pinctrl *pinctrl;
+    struct pinctrl_state *pins_active;
+    struct pinctrl_state *pins_suspend;
+#endif
+#if defined(CONFIG_FB) || defined(CONFIG_DRM)
+    struct notifier_block fb_notif;
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+    struct early_suspend early_suspend;
+#endif
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    struct goog_touch_interface *gti;
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+};
+
+enum FTS_BUS_TYPE {
+    FTS_BUS_TYPE_NONE,
+    FTS_BUS_TYPE_I2C,
+    FTS_BUS_TYPE_SPI,
+    FTS_BUS_TYPE_SPI_V2,
+};
+
+enum _FTS_TOUCH_ETYPE {
+    TOUCH_DEFAULT = 0x00,
+    TOUCH_PROTOCOL_v2 = 0x02,
+    TOUCH_EXTRA_MSG = 0x08,
+    TOUCH_PEN = 0x0B,
+    TOUCH_GESTURE = 0x80,
+    TOUCH_FW_INIT = 0x81,
+    TOUCH_DEFAULT_HI_RES = 0x82,
+    TOUCH_IGNORE = 0xFE,
+    TOUCH_ERROR = 0xFF,
+};
+
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+extern struct fts_ts_data *fts_data;
+
+/* communication interface */
+int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen);
+int fts_read_reg(u8 addr, u8 *value);
+int fts_write(u8 *writebuf, u32 writelen);
+int fts_write_reg(u8 addr, u8 value);
+void fts_hid2std(void);
+int fts_bus_init(struct fts_ts_data *ts_data);
+int fts_bus_exit(struct fts_ts_data *ts_data);
+int fts_spi_transfer_direct(u8 *writebuf, u32 writelen, u8 *readbuf, u32 readlen);
+int fts_bus_set_speed(struct fts_ts_data *ts_data, u32 speed);
+
+
+/* Gesture functions */
+int fts_gesture_init(struct fts_ts_data *ts_data);
+int fts_gesture_exit(struct fts_ts_data *ts_data);
+int fts_gesture_readdata(struct fts_ts_data *ts_data);
+
+int fts_set_heatmap_mode(struct fts_ts_data *ts_data, u8 heatmap_mode);
+int fts_set_grip_mode(struct fts_ts_data *ts_datam, u8 grip_mode);
+int fts_set_palm_mode(struct fts_ts_data *ts_data, u8 palm_mode);
+int fts_set_glove_mode(struct fts_ts_data *ts_data, bool en);
+
+/* Apk and functions */
+int fts_create_apk_debug_channel(struct fts_ts_data *);
+void fts_release_apk_debug_channel(struct fts_ts_data *);
+
+/* ADB functions */
+int fts_create_sysfs(struct fts_ts_data *ts_data);
+int fts_remove_sysfs(struct fts_ts_data *ts_data);
+
+/* ESD */
+#if FTS_ESDCHECK_EN
+int fts_esdcheck_init(struct fts_ts_data *ts_data);
+int fts_esdcheck_exit(struct fts_ts_data *ts_data);
+int fts_esdcheck_switch(bool enable);
+int fts_esdcheck_proc_busy(bool proc_debug);
+int fts_esdcheck_set_intr(bool intr);
+int fts_esdcheck_suspend(void);
+int fts_esdcheck_resume(void);
+#endif
+
+/* Production test */
+#if FTS_TEST_EN
+int fts_test_init(struct fts_ts_data *ts_data);
+int fts_test_exit(struct fts_ts_data *ts_data);
+#endif
+
+/* Point Report Check*/
+#if FTS_POINT_REPORT_CHECK_EN
+int fts_point_report_check_init(struct fts_ts_data *ts_data);
+int fts_point_report_check_exit(struct fts_ts_data *ts_data);
+void fts_prc_queue_work(struct fts_ts_data *ts_data);
+#endif
+
+/* FW upgrade */
+int fts_fwupg_init(struct fts_ts_data *ts_data);
+int fts_fwupg_exit(struct fts_ts_data *ts_data);
+int fts_upgrade_bin(char *fw_name, bool force);
+int fts_enter_test_environment(bool test_state);
+
+/* Other */
+int fts_reset_proc(int hdelayms);
+int fts_check_cid(struct fts_ts_data *ts_data, u8 id_h);
+int fts_wait_tp_to_valid(void);
+void fts_release_all_finger(void);
+void fts_tp_state_recovery(struct fts_ts_data *ts_data);
+int fts_ex_mode_init(struct fts_ts_data *ts_data);
+int fts_ex_mode_exit(struct fts_ts_data *ts_data);
+int fts_ex_mode_recovery(struct fts_ts_data *ts_data);
+void fts_update_feature_setting(struct fts_ts_data *ts_data);
+void fts_irq_disable(void);
+void fts_irq_enable(void);
+
+/* Power Control */
+#if FTS_PINCTRL_EN
+int fts_pinctrl_select_normal(struct fts_ts_data *ts);
+int fts_pinctrl_select_suspend(struct fts_ts_data *ts);
+#endif /* FTS_PINCTRL_EN */
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+void fts_irq_read_report(void);
+
+struct gti_scan_cmd;
+struct gti_palm_cmd;
+struct gti_screen_protector_mode_cmd;
+int gti_set_scan_mode(void *private_data, struct gti_scan_cmd *cmd);
+int gti_get_scan_mode(void *private_data, struct gti_scan_cmd *cmd);
+int gti_set_palm_mode(void *private_data, struct gti_palm_cmd *cmd);
+int gti_get_palm_mode(void *private_data, struct gti_palm_cmd *cmd);
+int gti_set_screen_protector_mode(void *private_data,
+    struct gti_screen_protector_mode_cmd *cmd);
+int gti_get_screen_protector_mode(void *private_data,
+    struct gti_screen_protector_mode_cmd *cmd);
+
+char *goog_get_test_limit_name(void);
+void goog_gti_probe(struct fts_ts_data *ts_data);
+void goog_gti_remove(struct fts_ts_data *ts_data);
+void goog_fts_input_report_b(struct fts_ts_data *data);
+int goog_parse_dt(struct device_node *np, struct fts_ts_platform_data *pdata);
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+
+#endif /* __LINUX_FOCALTECH_CORE_H__ */
diff --git a/ft3683u/focaltech_esdcheck.c b/ft3683u/focaltech_esdcheck.c
new file mode 100644
index 0000000..8bc40e8
--- /dev/null
+++ b/ft3683u/focaltech_esdcheck.c
@@ -0,0 +1,462 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_esdcheck.c
+*
+*    Author: Focaltech Driver Team
+*
+*   Created: 2016-08-03
+*
+*  Abstract: ESD check function
+*
+*   Version: v1.0
+*
+* Revision History:
+*        v1.0:
+*            First release. By luougojin 2016-08-03
+*        v1.1: By luougojin 2017-02-15
+*            1. Add LCD_ESD_PATCH to control idc_esdcheck_lcderror
+*****************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+#if FTS_ESDCHECK_EN
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define ESDCHECK_WAIT_TIME              1000    /* ms */
+#define LCD_ESD_PATCH                   0
+#define ESDCHECK_INTRCNT_MAX            2
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+struct fts_esdcheck_st {
+    u8      mode                : 1;    /* 1- need check esd 0- no esd check */
+    u8      suspend             : 1;
+    u8      proc_debug          : 1;    /* apk or adb use */
+    u8      intr                : 1;    /* 1- Interrupt trigger */
+    u8      unused              : 4;
+    u8      intr_cnt;
+    u8      flow_work_hold_cnt;         /* Flow Work Cnt(reg0x91) keep a same value for x times. >=5 times is ESD, need reset */
+    u8      flow_work_cnt_last;         /* Save Flow Work Cnt(reg0x91) value */
+    u32     hardware_reset_cnt;
+    u32     nack_cnt;
+    u32     dataerror_cnt;
+};
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+static struct fts_esdcheck_st fts_esdcheck_data;
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+#if LCD_ESD_PATCH
+int lcd_need_reset;
+static int tp_need_recovery; /* LCD reset cause Tp reset */
+int idc_esdcheck_lcderror(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    u8 val = 0;
+
+    FTS_DEBUG("check LCD ESD");
+    if ( (tp_need_recovery == 1) && (lcd_need_reset == 0) ) {
+        tp_need_recovery = 0;
+        /* LCD reset, need recover TP state */
+        fts_release_all_finger();
+        fts_tp_state_recovery(ts_data);
+    }
+
+    ret = fts_read_reg(FTS_REG_ESD_SATURATE, &val);
+    if ( ret < 0) {
+        FTS_ERROR("read reg0xED fail,ret:%d", ret);
+        return -EIO;
+    }
+
+    if (val == 0xAA) {
+        /*
+        * 1. Set flag lcd_need_reset = 1;
+        * 2. LCD driver need reset(recovery) LCD and set lcd_need_reset to 0
+        * 3. recover TP state
+        */
+        FTS_INFO("LCD ESD, need execute LCD reset");
+        lcd_need_reset = 1;
+        tp_need_recovery = 1;
+    }
+
+    return 0;
+}
+#endif
+
+static int fts_esdcheck_tp_reset(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    fts_esdcheck_data.flow_work_hold_cnt = 0;
+    fts_esdcheck_data.hardware_reset_cnt++;
+
+    fts_reset_proc(200);
+    fts_release_all_finger();
+    fts_tp_state_recovery(ts_data);
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static bool get_chip_id(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int i = 0;
+    u8 idh = 0;
+    u8 chip_id = ts_data->ic_info.ids.chip_idh;
+
+    for (i = 0; i < 3; i++) {
+        ret = fts_read_reg(FTS_REG_CHIP_ID, &idh);
+        if (ret < 0) {
+            FTS_ERROR("read chip id fail,ret:%d", ret);
+            fts_esdcheck_data.nack_cnt++;
+        } else {
+            if ((idh == chip_id) || (fts_check_cid(ts_data, idh) == 0)) {
+                break;
+            } else {
+                FTS_DEBUG("read chip_id:%x,retry:%d", idh, i);
+                fts_esdcheck_data.dataerror_cnt++;
+            }
+        }
+        msleep(10);
+    }
+
+    /* if can't get correct data in 3 times, then need hardware reset */
+    if (i >= 3) {
+        FTS_ERROR("read chip id 3 times fail, need execute TP reset");
+        return true;
+    }
+
+    return false;
+}
+
+/*****************************************************************************
+*  Name: get_flow_cnt
+*  Brief: Read flow cnt(0x91)
+*  Input:
+*  Output:
+*  Return:  1(true) - Reg 0x91(flow cnt) abnormal: hold a value for 5 times
+*           0(false) - Reg 0x91(flow cnt) normal
+*****************************************************************************/
+static bool get_flow_cnt(struct fts_ts_data *ts_data)
+{
+    int     ret = 0;
+    u8      reg_value = 0;
+    u8      reg_addr = 0;
+
+    reg_addr = FTS_REG_FLOW_WORK_CNT;
+    ret = fts_read(&reg_addr, 1, &reg_value, 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg0x91 fail,ret:%d", ret);
+        fts_esdcheck_data.nack_cnt++;
+    } else {
+        if ( reg_value == fts_esdcheck_data.flow_work_cnt_last ) {
+            FTS_DEBUG("reg0x91,val:%x,last:%x", reg_value,
+                      fts_esdcheck_data.flow_work_cnt_last);
+            fts_esdcheck_data.flow_work_hold_cnt++;
+        } else {
+            fts_esdcheck_data.flow_work_hold_cnt = 0;
+        }
+
+        fts_esdcheck_data.flow_work_cnt_last = reg_value;
+    }
+
+    /* Flow Work Cnt keep a value for 5 times, need execute TP reset */
+    if (fts_esdcheck_data.flow_work_hold_cnt >= 5) {
+        FTS_DEBUG("reg0x91 keep a value for 5 times, need execute TP reset");
+        return true;
+    }
+
+    return false;
+}
+
+static int esdcheck_algorithm(struct fts_ts_data *ts_data)
+{
+    int     ret = 0;
+    u8      reg_value = 0;
+    u8      reg_addr = 0;
+    bool    hardware_reset = 0;
+
+    /* 1. esdcheck is interrupt, then return */
+    if (fts_esdcheck_data.intr == 1) {
+        fts_esdcheck_data.intr_cnt++;
+        if (fts_esdcheck_data.intr_cnt > ESDCHECK_INTRCNT_MAX)
+            fts_esdcheck_data.intr = 0;
+        else
+            return 0;
+    }
+
+    /* 2. check power state, if suspend, no need check esd */
+    if (fts_esdcheck_data.suspend == 1) {
+        FTS_DEBUG("In suspend, not check esd");
+        /* because in suspend state, adb can be used, when upgrade FW, will
+         * active ESD check(active = 1); But in suspend, then will don't
+         * queue_delayed_work, when resume, don't check ESD again
+         */
+        return 0;
+    }
+
+    /* 3. check fts_esdcheck_data.proc_debug state, if 1-proc busy, no need check esd*/
+    if (fts_esdcheck_data.proc_debug == 1) {
+        FTS_INFO("In apk/adb command mode, not check esd");
+        return 0;
+    }
+
+    /* 4. In factory mode, can't check esd */
+    reg_addr = FTS_REG_WORKMODE;
+    ret = fts_read_reg(reg_addr, &reg_value);
+    if ( ret < 0 ) {
+        fts_esdcheck_data.nack_cnt++;
+    } else if ( (reg_value & 0x70) !=  FTS_REG_WORKMODE_WORK_VALUE) {
+        FTS_DEBUG("not in work mode(%x), no check esd", reg_value);
+        return 0;
+    }
+
+    /* 5. IDC esd check lcd  default:close */
+#if LCD_ESD_PATCH
+    idc_esdcheck_lcderror(ts_data);
+#endif
+
+    /* 6. Get Chip ID */
+    hardware_reset = get_chip_id(ts_data);
+
+    /* 7. get Flow work cnt: 0x91 If no change for 5 times, then ESD and reset */
+    if (!hardware_reset) {
+        hardware_reset = get_flow_cnt(ts_data);
+    }
+
+    /* 8. If need hardware reset, then handle it here */
+    if (hardware_reset == 1) {
+        FTS_DEBUG("NoACK=%d, Error Data=%d, Hardware Reset=%d",
+                  fts_esdcheck_data.nack_cnt,
+                  fts_esdcheck_data.dataerror_cnt,
+                  fts_esdcheck_data.hardware_reset_cnt);
+        fts_esdcheck_tp_reset(ts_data);
+    }
+
+    return 0;
+}
+
+static void esdcheck_func(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work,
+                                  struct fts_ts_data, esdcheck_work.work);
+
+    if (ENABLE == fts_esdcheck_data.mode) {
+        esdcheck_algorithm(ts_data);
+        queue_delayed_work(ts_data->ts_workqueue, &ts_data->esdcheck_work,
+                           msecs_to_jiffies(ESDCHECK_WAIT_TIME));
+    }
+
+}
+
+int fts_esdcheck_set_intr(bool intr)
+{
+    /* interrupt don't add debug message */
+    fts_esdcheck_data.intr = intr;
+    fts_esdcheck_data.intr_cnt = (u8)intr;
+    return 0;
+}
+
+static int fts_esdcheck_get_status(void)
+{
+    /* interrupt don't add debug message */
+    return fts_esdcheck_data.mode;
+}
+
+/*****************************************************************************
+*  Name: fts_esdcheck_proc_busy
+*  Brief: When APK or ADB command access TP via driver, then need set proc_debug,
+*         then will not check ESD.
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+int fts_esdcheck_proc_busy(bool proc_debug)
+{
+    fts_esdcheck_data.proc_debug = proc_debug;
+    return 0;
+}
+
+/*****************************************************************************
+*  Name: fts_esdcheck_switch
+*  Brief: FTS esd check function switch.
+*  Input:   enable:  1 - Enable esd check
+*                    0 - Disable esd check
+*  Output:
+*  Return:
+*****************************************************************************/
+int fts_esdcheck_switch(bool enable)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    FTS_FUNC_ENTER();
+    if (fts_esdcheck_data.mode == ENABLE) {
+        if (enable) {
+            FTS_DEBUG("ESD check start");
+            fts_esdcheck_data.flow_work_hold_cnt = 0;
+            fts_esdcheck_data.flow_work_cnt_last = 0;
+            fts_esdcheck_data.intr = 0;
+            fts_esdcheck_data.intr_cnt = 0;
+            queue_delayed_work(ts_data->ts_workqueue,
+                               &ts_data->esdcheck_work,
+                               msecs_to_jiffies(ESDCHECK_WAIT_TIME));
+        } else {
+            FTS_DEBUG("ESD check stop");
+            cancel_delayed_work_sync(&ts_data->esdcheck_work);
+        }
+    }
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_esdcheck_suspend(void)
+{
+    FTS_FUNC_ENTER();
+    fts_esdcheck_switch(DISABLE);
+    fts_esdcheck_data.suspend = 1;
+    fts_esdcheck_data.intr = 0;
+    fts_esdcheck_data.intr_cnt = 0;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_esdcheck_resume( void )
+{
+    FTS_FUNC_ENTER();
+    fts_esdcheck_switch(ENABLE);
+    fts_esdcheck_data.suspend = 0;
+    fts_esdcheck_data.intr = 0;
+    fts_esdcheck_data.intr_cnt = 0;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static ssize_t fts_esdcheck_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_DEBUG("enable esdcheck");
+        fts_esdcheck_data.mode = ENABLE;
+        fts_esdcheck_switch(ENABLE);
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_DEBUG("disable esdcheck");
+        fts_esdcheck_switch(DISABLE);
+        fts_esdcheck_data.mode = DISABLE;
+    }
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_esdcheck_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count = snprintf(buf, PAGE_SIZE, "Esd check: %s\n", \
+                     fts_esdcheck_get_status() ? "On" : "Off");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* sysfs esd node
+ *   read example: cat  fts_esd_mode        ---read esd mode
+ *   write example:echo 01 > fts_esd_mode   ---make esdcheck enable
+ *
+ */
+static DEVICE_ATTR (fts_esd_mode, S_IRUGO | S_IWUSR, fts_esdcheck_show, fts_esdcheck_store);
+
+static struct attribute *fts_esd_mode_attrs[] = {
+
+    &dev_attr_fts_esd_mode.attr,
+    NULL,
+};
+
+static struct attribute_group fts_esd_group = {
+    .attrs = fts_esd_mode_attrs,
+};
+
+int fts_create_esd_sysfs(struct device *dev)
+{
+    int ret = 0;
+
+    ret = sysfs_create_group(&dev->kobj, &fts_esd_group);
+    if ( ret != 0) {
+        FTS_ERROR("fts_create_esd_sysfs(sysfs) create fail");
+        sysfs_remove_group(&dev->kobj, &fts_esd_group);
+        return ret;
+    }
+    return 0;
+}
+
+int fts_esdcheck_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    if (ts_data->ts_workqueue) {
+        INIT_DELAYED_WORK(&ts_data->esdcheck_work, esdcheck_func);
+    } else {
+        FTS_ERROR("fts workqueue is NULL, can't run esd check function");
+        return -EINVAL;
+    }
+
+    memset((u8 *)&fts_esdcheck_data, 0, sizeof(struct fts_esdcheck_st));
+
+    fts_esdcheck_data.mode = ENABLE;
+    fts_esdcheck_data.intr = 0;
+    fts_esdcheck_data.intr_cnt = 0;
+    fts_esdcheck_switch(ENABLE);
+    fts_create_esd_sysfs(ts_data->dev);
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_esdcheck_exit(struct fts_ts_data *ts_data)
+{
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_esd_group);
+    return 0;
+}
+#endif /* FTS_ESDCHECK_EN */
+
diff --git a/ft3683u/focaltech_ex_fun.c b/ft3683u/focaltech_ex_fun.c
new file mode 100644
index 0000000..62143c2
--- /dev/null
+++ b/ft3683u/focaltech_ex_fun.c
@@ -0,0 +1,2813 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: Focaltech_ex_fun.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+#include <goog_touch_interface.h>
+
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define PROC_UPGRADE                            0
+#define PROC_READ_REGISTER                      1
+#define PROC_WRITE_REGISTER                     2
+#define PROC_AUTOCLB                            4
+#define PROC_UPGRADE_INFO                       5
+#define PROC_WRITE_DATA                         6
+#define PROC_READ_DATA                          7
+#define PROC_SET_TEST_FLAG                      8
+#define PROC_SET_SLAVE_ADDR                     10
+#define PROC_HW_RESET                           11
+#define PROC_READ_STATUS                        12
+#define PROC_SET_BOOT_MODE                      13
+#define PROC_ENTER_TEST_ENVIRONMENT             14
+#define PROC_WRITE_DATA_DIRECT                  16
+#define PROC_READ_DATA_DIRECT                   17
+#define PROC_CONFIGURE                          18
+#define PROC_CONFIGURE_INTR                     20
+#define PROC_NAME                               "ftxxxx-debug"
+#define PROC_BUF_SIZE                           512
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+enum {
+    RWREG_OP_READ = 0,
+    RWREG_OP_WRITE = 1,
+};
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+static struct rwreg_operation_t {
+    int type;           /*  0: read, 1: write */
+    int reg;            /*  register */
+    int len;            /*  read/write length */
+    int val;            /*  length = 1; read: return value, write: op return */
+    int res;            /*  0: success, otherwise: fail */
+    char *opbuf;        /*  length >= 1, read return value, write: op return */
+} rw_op;
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+static ssize_t fts_debug_write(
+    struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
+{
+    u8 *writebuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int buflen = count;
+    int writelen = 0;
+    int ret = 0;
+    char tmp[PROC_BUF_SIZE];
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 1) {
+        FTS_ERROR("apk proc count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        writebuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == writebuf) {
+            FTS_ERROR("apk proc write buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        writebuf = tmpbuf;
+    }
+
+    if (copy_from_user(writebuf, buff, buflen)) {
+        FTS_ERROR("[APK]: copy from user error!!");
+        ret = -EFAULT;
+        goto proc_write_err;
+    }
+
+    proc->opmode = writebuf[0];
+    switch (proc->opmode) {
+    case PROC_SET_TEST_FLAG:
+        FTS_DEBUG("[APK]: PROC_SET_TEST_FLAG = %x", writebuf[1]);
+        if (writebuf[1] == 0) {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(ENABLE);
+#endif
+        } else {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(DISABLE);
+#endif
+        }
+        break;
+
+    case PROC_READ_REGISTER:
+        proc->cmd[0] = writebuf[1];
+        break;
+
+    case PROC_WRITE_REGISTER:
+        ret = fts_write_reg(writebuf[1], writebuf[2]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_REGISTER write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_READ_DATA:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA:
+        writelen = buflen - 1;
+        ret = fts_write(writebuf + 1, writelen);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_SET_SLAVE_ADDR:
+        break;
+
+    case PROC_HW_RESET:
+        if (buflen < PROC_BUF_SIZE) {
+            snprintf(tmp, PROC_BUF_SIZE, "%s", writebuf + 1);
+            tmp[buflen - 1] = '\0';
+            if (strncmp(tmp, "focal_driver", 12) == 0) {
+                FTS_INFO("APK execute HW Reset");
+                fts_reset_proc(0);
+            }
+        }
+        break;
+
+    case PROC_SET_BOOT_MODE:
+        FTS_DEBUG("[APK]: PROC_SET_BOOT_MODE = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            ts_data->fw_is_running = true;
+        } else {
+            ts_data->fw_is_running = false;
+        }
+        break;
+    case PROC_ENTER_TEST_ENVIRONMENT:
+        FTS_DEBUG("[APK]: PROC_ENTER_TEST_ENVIRONMENT = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            fts_enter_test_environment(0);
+        } else {
+            fts_enter_test_environment(1);
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA_DIRECT) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA_DIRECT:
+        writelen = buflen - 1;
+        ret = fts_spi_transfer_direct(writebuf + 1, writelen, NULL, 0);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA_DIRECT write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE:
+        ts_data->spi->mode = writebuf[1];
+        ts_data->spi->bits_per_word = writebuf[2];
+        ts_data->spi->max_speed_hz = *(u32 *)(writebuf + 4);
+        FTS_INFO("spi,mode=%d,bits=%d,speed=%d", ts_data->spi->mode,
+                 ts_data->spi->bits_per_word, ts_data->spi->max_speed_hz);
+        ret = spi_setup(ts_data->spi);
+        if (ret) {
+            FTS_ERROR("spi setup fail");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE_INTR:
+        if (writebuf[1] == 0)
+            fts_irq_disable();
+        else
+            fts_irq_enable();
+        break;
+
+    default:
+        break;
+    }
+
+    ret = buflen;
+proc_write_err:
+    if ((buflen > PROC_BUF_SIZE) && writebuf) {
+        kfree(writebuf);
+        writebuf = NULL;
+    }
+    return ret;
+}
+
+static ssize_t fts_debug_read(
+    struct file *filp, char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    int num_read_chars = 0;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = tmpbuf;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+
+    switch (proc->opmode) {
+    case PROC_READ_REGISTER:
+        num_read_chars = 1;
+        ret = fts_read_reg(proc->cmd[0], &readbuf[0]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_REGISTER read error");
+            goto proc_read_err;
+        }
+        break;
+    case PROC_WRITE_REGISTER:
+        break;
+
+    case PROC_READ_DATA:
+        num_read_chars = buflen;
+        ret = fts_read(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        num_read_chars = buflen;
+        ret = fts_spi_transfer_direct(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA_DIRECT read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_WRITE_DATA:
+        break;
+
+    default:
+        break;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+
+    ret = num_read_chars;
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops fts_proc_fops = {
+    .proc_read   = fts_debug_read,
+    .proc_write  = fts_debug_write,
+};
+#else
+static const struct file_operations fts_proc_fops = {
+    .owner  = THIS_MODULE,
+    .read   = fts_debug_read,
+    .write  = fts_debug_write,
+};
+#endif
+
+#else
+static int fts_debug_write(
+    struct file *filp, const char __user *buff, unsigned long len, void *data)
+{
+    u8 *writebuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int buflen = count;
+    int writelen = 0;
+    int ret = 0;
+    char tmp[PROC_BUF_SIZE];
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 1) {
+        FTS_ERROR("apk proc write count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        writebuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == writebuf) {
+            FTS_ERROR("apk proc write buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        writebuf = tmpbuf;
+    }
+
+    if (copy_from_user(writebuf, buff, buflen)) {
+        FTS_ERROR("[APK]: copy from user error!!");
+        ret = -EFAULT;
+        goto proc_write_err;
+    }
+
+    proc->opmode = writebuf[0];
+    switch (proc->opmode) {
+    case PROC_SET_TEST_FLAG:
+        FTS_DEBUG("[APK]: PROC_SET_TEST_FLAG = %x", writebuf[1]);
+        if (writebuf[1] == 0) {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(ENABLE);
+#endif
+        } else {
+#if FTS_ESDCHECK_EN
+            fts_esdcheck_switch(DISABLE);
+#endif
+        }
+        break;
+
+    case PROC_READ_REGISTER:
+        proc->cmd[0] = writebuf[1];
+        break;
+
+    case PROC_WRITE_REGISTER:
+        ret = fts_write_reg(writebuf[1], writebuf[2]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_REGISTER write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_READ_DATA:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA:
+        writelen = buflen - 1;
+        ret = fts_write(writebuf + 1, writelen);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_SET_SLAVE_ADDR:
+        break;
+
+    case PROC_HW_RESET:
+        if (buflen < PROC_BUF_SIZE) {
+            snprintf(tmp, PROC_BUF_SIZE, "%s", writebuf + 1);
+            tmp[buflen - 1] = '\0';
+            if (strncmp(tmp, "focal_driver", 12) == 0) {
+                FTS_INFO("APK execute HW Reset");
+                fts_reset_proc(0);
+            }
+        }
+        break;
+
+    case PROC_SET_BOOT_MODE:
+        FTS_DEBUG("[APK]: PROC_SET_BOOT_MODE = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            ts_data->fw_is_running = true;
+        } else {
+            ts_data->fw_is_running = false;
+        }
+        break;
+    case PROC_ENTER_TEST_ENVIRONMENT:
+        FTS_DEBUG("[APK]: PROC_ENTER_TEST_ENVIRONMENT = %x", writebuf[1]);
+        if (0 == writebuf[1]) {
+            fts_enter_test_environment(0);
+        } else {
+            fts_enter_test_environment(1);
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        writelen = buflen - 1;
+        if (writelen >= FTS_MAX_COMMMAND_LENGTH) {
+            FTS_ERROR("cmd(PROC_READ_DATA_DIRECT) length(%d) fail", writelen);
+            goto proc_write_err;
+        }
+        memcpy(proc->cmd, writebuf + 1, writelen);
+        proc->cmd_len = writelen;
+        break;
+
+    case PROC_WRITE_DATA_DIRECT:
+        writelen = buflen - 1;
+        ret = fts_spi_transfer_direct(writebuf + 1, writelen, NULL, 0);
+        if (ret < 0) {
+            FTS_ERROR("PROC_WRITE_DATA_DIRECT write error");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE:
+        ts_data->spi->mode = writebuf[1];
+        ts_data->spi->bits_per_word = writebuf[2];
+        ts_data->spi->max_speed_hz = *(u32 *)(writebuf + 4);
+        FTS_INFO("spi,mode=%d,bits=%d,speed=%d", ts_data->spi->mode,
+                 ts_data->spi->bits_per_word, ts_data->spi->max_speed_hz);
+        ret = spi_setup(ts_data->spi);
+        if (ret) {
+            FTS_ERROR("spi setup fail");
+            goto proc_write_err;
+        }
+        break;
+
+    case PROC_CONFIGURE_INTR:
+        if (writebuf[1] == 0)
+            fts_irq_disable();
+        else
+            fts_irq_enable();
+        break;
+
+    default:
+        break;
+    }
+
+    ret = buflen;
+proc_write_err:
+    if ((buflen > PROC_BUF_SIZE) && writebuf) {
+        kfree(writebuf);
+        writebuf = NULL;
+    }
+    return ret;
+}
+
+static int fts_debug_read(
+    char *page, char **start, off_t off, int count, int *eof, void *data )
+{
+    int ret = 0;
+    int num_read_chars = 0;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 tmpbuf[PROC_BUF_SIZE] = { 0 };
+    struct fts_ts_data *ts_data = fts_data;
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = tmpbuf;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+
+    switch (proc->opmode) {
+    case PROC_READ_REGISTER:
+        num_read_chars = 1;
+        ret = fts_read_reg(proc->cmd[0], &readbuf[0]);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_REGISTER read error");
+            goto proc_read_err;
+        }
+        break;
+    case PROC_WRITE_REGISTER:
+        break;
+
+    case PROC_READ_DATA:
+        num_read_chars = buflen;
+        ret = fts_read(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_READ_DATA_DIRECT:
+        num_read_chars = buflen;
+        ret = fts_spi_transfer_direct(proc->cmd, proc->cmd_len, readbuf, num_read_chars);
+        if (ret < 0) {
+            FTS_ERROR("PROC_READ_DATA_DIRECT read error");
+            goto proc_read_err;
+        }
+        break;
+
+    case PROC_WRITE_DATA:
+        break;
+
+    default:
+        break;
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+
+    ret = num_read_chars;
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+#endif
+
+int fts_create_apk_debug_channel(struct fts_ts_data *ts_data)
+{
+    struct ftxxxx_proc *proc = &ts_data->proc;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+    proc->proc_entry = proc_create(PROC_NAME, 0777, NULL, &fts_proc_fops);
+    if (NULL == proc->proc_entry) {
+        FTS_ERROR("create proc entry fail");
+        return -ENOMEM;
+    }
+#else
+    proc->proc_entry = create_proc_entry(PROC_NAME, 0777, NULL);
+    if (NULL == proc->proc_entry) {
+        FTS_ERROR("create proc entry fail");
+        return -ENOMEM;
+    }
+    proc->proc_entry->write_proc = fts_debug_write;
+    proc->proc_entry->read_proc = fts_debug_read;
+#endif
+
+    FTS_INFO("Create proc entry success!");
+    return 0;
+}
+
+void fts_release_apk_debug_channel(struct fts_ts_data *ts_data)
+{
+    struct ftxxxx_proc *proc = &ts_data->proc;
+
+    if (proc->proc_entry) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+        proc_remove(proc->proc_entry);
+#else
+        remove_proc_entry(PROC_NAME, NULL);
+#endif
+    }
+}
+
+/************************************************************************
+ * sysfs interface
+ ***********************************************************************/
+/* fts_hw_reset interface */
+static ssize_t fts_hw_reset_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+    ssize_t count = 0;
+
+    mutex_lock(&input_dev->mutex);
+    fts_reset_proc(0);
+    count = snprintf(buf, PAGE_SIZE, "hw reset executed\n");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_hw_reset_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_irq interface */
+static ssize_t fts_irq_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    ssize_t count = 0;
+
+    count = snprintf(buf, PAGE_SIZE, "irq_enable:%d\n",
+        !fts_data->irq_disabled);
+
+    return count;
+}
+
+static ssize_t fts_irq_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_INFO("enable irq");
+        fts_irq_enable();
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_INFO("disable irq");
+        fts_irq_disable();
+    }
+    mutex_unlock(&input_dev->mutex);
+    return count;
+}
+
+/* fts_boot_mode interface */
+static ssize_t fts_bootmode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    mutex_lock(&input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_INFO("[EX-FUN]set to boot mode");
+        fts_data->fw_is_running = false;
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_INFO("[EX-FUN]set to fw mode");
+        fts_data->fw_is_running = true;
+    }
+    mutex_unlock(&input_dev->mutex);
+    FTS_FUNC_EXIT();
+
+    return count;
+}
+
+static ssize_t fts_bootmode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    ssize_t count = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    mutex_lock(&input_dev->mutex);
+    if (true == fts_data->fw_is_running) {
+        count = snprintf(buf, PAGE_SIZE, "tp is in fw mode\n");
+    } else {
+        count = snprintf(buf, PAGE_SIZE, "tp is in boot mode\n");
+    }
+    mutex_unlock(&input_dev->mutex);
+    FTS_FUNC_EXIT();
+
+    return count;
+}
+
+/* fts_tpfwver interface */
+static ssize_t fts_tpfwver_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+    ssize_t num_read_chars = 0;
+    u8 fw_major_ver = 0;
+    u8 fw_minor_ver = 0;
+
+    mutex_lock(&input_dev->mutex);
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+    ret = fts_read_reg(FTS_REG_FW_MAJOR_VER, &fw_major_ver);
+    if ((ret < 0) || (fw_major_ver == 0xFF) || (fw_major_ver == 0x00)) {
+        num_read_chars = snprintf(buf, PAGE_SIZE,
+                                  "get tp fw major version fail!\n");
+        mutex_unlock(&input_dev->mutex);
+        return num_read_chars;
+    }
+    ret = fts_read_reg(FTS_REG_FW_MINOR_VER, &fw_minor_ver);
+    if (ret < 0) {
+        num_read_chars = snprintf(buf, PAGE_SIZE,
+                                  "get tp fw minor version fail!\n");
+        mutex_unlock(&input_dev->mutex);
+        return num_read_chars;
+    }
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+    num_read_chars = snprintf(buf, PAGE_SIZE, "V%02x_D%02x\n", fw_major_ver,
+        fw_minor_ver);
+
+    mutex_unlock(&input_dev->mutex);
+    return num_read_chars;
+}
+
+static ssize_t fts_tpfwver_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_rw_reg */
+static ssize_t fts_tprwreg_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count;
+    int i;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+
+    if (rw_op.len < 0) {
+        count = snprintf(buf, PAGE_SIZE, "Invalid cmd line\n");
+    } else if (rw_op.len == 1) {
+        if (RWREG_OP_READ == rw_op.type) {
+            if (rw_op.res == 0) {
+                count = snprintf(buf, PAGE_SIZE, "Read %02X: %02X\n", rw_op.reg, rw_op.val);
+            } else {
+                count = snprintf(buf, PAGE_SIZE, "Read %02X failed, ret: %d\n", rw_op.reg,  rw_op.res);
+            }
+        } else {
+            if (rw_op.res == 0) {
+                count = snprintf(buf, PAGE_SIZE, "Write %02X, %02X success\n", rw_op.reg,  rw_op.val);
+            } else {
+                count = snprintf(buf, PAGE_SIZE, "Write %02X failed, ret: %d\n", rw_op.reg,  rw_op.res);
+            }
+        }
+    } else {
+        if (RWREG_OP_READ == rw_op.type) {
+            count = snprintf(buf, PAGE_SIZE, "Read Reg: [%02X]-[%02X]\n", rw_op.reg, rw_op.reg + rw_op.len);
+            count += snprintf(buf + count, PAGE_SIZE, "Result: ");
+            if (rw_op.res) {
+                count += snprintf(buf + count, PAGE_SIZE, "failed, ret: %d\n", rw_op.res);
+            } else {
+                if (rw_op.opbuf) {
+                    for (i = 0; i < rw_op.len; i++) {
+                        count += snprintf(buf + count, PAGE_SIZE, "%02X ", rw_op.opbuf[i]);
+                    }
+                    count += snprintf(buf + count, PAGE_SIZE, "\n");
+                }
+            }
+        } else {
+            ;
+            count = snprintf(buf, PAGE_SIZE, "Write Reg: [%02X]-[%02X]\n", rw_op.reg, rw_op.reg + rw_op.len - 1);
+            count += snprintf(buf + count, PAGE_SIZE, "Write Data: ");
+            if (rw_op.opbuf) {
+                for (i = 1; i < rw_op.len; i++) {
+                    count += snprintf(buf + count, PAGE_SIZE, "%02X ", rw_op.opbuf[i]);
+                }
+                count += snprintf(buf + count, PAGE_SIZE, "\n");
+            }
+            if (rw_op.res) {
+                count += snprintf(buf + count, PAGE_SIZE, "Result: failed, ret: %d\n", rw_op.res);
+            } else {
+                count += snprintf(buf + count, PAGE_SIZE, "Result: success\n");
+            }
+        }
+        /*if (rw_op.opbuf) {
+            kfree(rw_op.opbuf);
+            rw_op.opbuf = NULL;
+        }*/
+    }
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static int shex_to_int(const char *hex_buf, int size)
+{
+    int i;
+    int base = 1;
+    int value = 0;
+    char single;
+
+    for (i = size - 1; i >= 0; i--) {
+        single = hex_buf[i];
+
+        if ((single >= '0') && (single <= '9')) {
+            value += (single - '0') * base;
+        } else if ((single >= 'a') && (single <= 'z')) {
+            value += (single - 'a' + 10) * base;
+        } else if ((single >= 'A') && (single <= 'Z')) {
+            value += (single - 'A' + 10) * base;
+        } else {
+            return -EINVAL;
+        }
+
+        base *= 16;
+    }
+
+    return value;
+}
+
+
+static u8 shex_to_u8(const char *hex_buf, int size)
+{
+    return (u8)shex_to_int(hex_buf, size);
+}
+/*
+ * Format buf:
+ * [0]: '0' write, '1' read(reserved)
+ * [1-2]: addr, hex
+ * [3-4]: length, hex
+ * [5-6]...[n-(n+1)]: data, hex
+ */
+static int fts_parse_buf(const char *buf, size_t cmd_len)
+{
+    int length;
+    int i;
+    char *tmpbuf;
+
+    rw_op.reg = shex_to_u8(buf + 1, 2);
+    length = shex_to_int(buf + 3, 2);
+
+    if (buf[0] == '1') {
+        rw_op.len = length;
+        rw_op.type = RWREG_OP_READ;
+        FTS_DEBUG("read %02X, %d bytes", rw_op.reg, rw_op.len);
+    } else {
+        if (cmd_len < (length * 2 + 5)) {
+            pr_err("data invalided!\n");
+            return -EINVAL;
+        }
+        FTS_DEBUG("write %02X, %d bytes", rw_op.reg, length);
+
+        /* first byte is the register addr */
+        rw_op.type = RWREG_OP_WRITE;
+        rw_op.len = length + 1;
+    }
+
+    if (rw_op.len > 0) {
+        tmpbuf = (char *)kzalloc(rw_op.len, GFP_KERNEL);
+        if (!tmpbuf) {
+            FTS_ERROR("allocate memory failed!\n");
+            return -ENOMEM;
+        }
+
+        if (RWREG_OP_WRITE == rw_op.type) {
+            tmpbuf[0] = rw_op.reg & 0xFF;
+            FTS_DEBUG("write buffer: ");
+            for (i = 1; i < rw_op.len; i++) {
+                tmpbuf[i] = shex_to_u8(buf + 5 + i * 2 - 2, 2);
+                FTS_DEBUG("buf[%d]: %02X", i, tmpbuf[i] & 0xFF);
+            }
+        }
+        rw_op.opbuf = tmpbuf;
+    }
+
+    return rw_op.len;
+}
+
+static ssize_t fts_tprwreg_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+    ssize_t cmd_length = 0;
+
+    mutex_lock(&input_dev->mutex);
+    cmd_length = count - 1;
+
+    if (rw_op.opbuf) {
+        kfree(rw_op.opbuf);
+        rw_op.opbuf = NULL;
+    }
+
+    FTS_DEBUG("cmd len: %d, buf: %s", (int)cmd_length, buf);
+    /* compatible old ops */
+    if (2 == cmd_length) {
+        rw_op.type = RWREG_OP_READ;
+        rw_op.len = 1;
+        rw_op.reg = shex_to_int(buf, 2);
+    } else if (4 == cmd_length) {
+        rw_op.type = RWREG_OP_WRITE;
+        rw_op.len = 1;
+        rw_op.reg = shex_to_int(buf, 2);
+        rw_op.val = shex_to_int(buf + 2, 2);
+    } else if (cmd_length < 5) {
+        FTS_ERROR("Invalid cmd buffer");
+        mutex_unlock(&input_dev->mutex);
+        return -EINVAL;
+    } else {
+        rw_op.len = fts_parse_buf(buf, cmd_length);
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+    if (rw_op.len < 0) {
+        FTS_ERROR("cmd buffer error!");
+
+    } else {
+        if (RWREG_OP_READ == rw_op.type) {
+            if (rw_op.len == 1) {
+                u8 reg, val;
+                reg = rw_op.reg & 0xFF;
+                rw_op.res = fts_read_reg(reg, &val);
+                rw_op.val = val;
+            } else {
+                char reg;
+                reg = rw_op.reg & 0xFF;
+
+                rw_op.res = fts_read(&reg, 1, rw_op.opbuf, rw_op.len);
+            }
+
+            if (rw_op.res < 0) {
+                FTS_ERROR("Could not read 0x%02x", rw_op.reg);
+            } else {
+                FTS_INFO("read 0x%02x, %d bytes successful", rw_op.reg, rw_op.len);
+                rw_op.res = 0;
+            }
+
+        } else {
+            if (rw_op.len == 1) {
+                u8 reg, val;
+                reg = rw_op.reg & 0xFF;
+                val = rw_op.val & 0xFF;
+                rw_op.res = fts_write_reg(reg, val);
+            } else {
+                rw_op.res = fts_write(rw_op.opbuf, rw_op.len);
+            }
+            if (rw_op.res < 0) {
+                FTS_ERROR("Could not write 0x%02x", rw_op.reg);
+
+            } else {
+                FTS_INFO("Write 0x%02x, %d bytes successful", rw_op.val, rw_op.len);
+                rw_op.res = 0;
+            }
+        }
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* fts_upgrade_bin interface */
+static ssize_t fts_fwupgradebin_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    return -EPERM;
+}
+
+static ssize_t fts_fwupgradebin_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    if ((count <= 1) || (count >= FILE_NAME_LENGTH - 32)) {
+        FTS_ERROR("fw bin name's length(%d) fail", (int)count);
+        return -EINVAL;
+    }
+    memset(fwname, 0, sizeof(fwname));
+    snprintf(fwname, FILE_NAME_LENGTH, "%s", buf);
+    fwname[count - 1] = '\0';
+
+    FTS_INFO("upgrade with bin file through sysfs node");
+    mutex_lock(&input_dev->mutex);
+    fts_upgrade_bin(fwname, 0);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* fts_force_upgrade interface */
+static ssize_t fts_fwforceupg_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    return -EPERM;
+}
+
+static ssize_t fts_fwforceupg_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    char fwname[FILE_NAME_LENGTH];
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    if ((count <= 1) || (count >= FILE_NAME_LENGTH - 32)) {
+        FTS_ERROR("fw bin name's length(%d) fail", (int)count);
+        return -EINVAL;
+    }
+    memset(fwname, 0, sizeof(fwname));
+    snprintf(fwname, FILE_NAME_LENGTH, "%s", buf);
+    fwname[count - 1] = '\0';
+
+    FTS_INFO("force upgrade through sysfs node");
+    mutex_lock(&input_dev->mutex);
+    fts_upgrade_bin(fwname, 1);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/* fts_driver_info interface */
+static ssize_t fts_driverinfo_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count += snprintf(buf + count, PAGE_SIZE, "Driver Ver:%s\n",
+                      FTS_DRIVER_VERSION);
+
+    count += snprintf(buf + count, PAGE_SIZE, "Resolution:(%d,%d)~(%d,%d)\n",
+                      pdata->x_min, pdata->y_min, pdata->x_max, pdata->y_max);
+
+    count += snprintf(buf + count, PAGE_SIZE, "Max Touchs:%d\n",
+                      pdata->max_touch_number);
+
+    count += snprintf(buf + count, PAGE_SIZE,
+                      "reset gpio:%d,int gpio:%d,irq:%d\n",
+                      pdata->reset_gpio, pdata->irq_gpio, ts_data->irq);
+
+    count += snprintf(buf + count, PAGE_SIZE, "IC ID:0x%02x%02x\n",
+                      ts_data->ic_info.ids.chip_idh,
+                      ts_data->ic_info.ids.chip_idl);
+
+    if (ts_data->bus_type == FTS_BUS_TYPE_I2C) {
+        count += snprintf(buf + count, PAGE_SIZE, "BUS:%s,addr:0x%x\n",
+                          "I2C", ts_data->client->addr);
+    } else {
+        count += snprintf(buf + count, PAGE_SIZE,
+                          "BUS:%s,mode:%d,max_freq:%d\n", "SPI",
+                          ts_data->spi->mode, ts_data->spi->max_speed_hz);
+    }
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_driverinfo_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_dump_reg interface */
+static ssize_t fts_dumpreg_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(1);
+#endif
+    fts_read_reg(FTS_REG_POWER_MODE, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Power Mode:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_FW_MAJOR_VER, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "FW Major Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_FW_MINOR_VER, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "FW Minor Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_LIC_VER, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "LCD Initcode Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_IDE_PARA_VER_ID, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Param Ver:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_IDE_PARA_STATUS, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Param status:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_VENDOR_ID, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Vendor ID:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_GESTURE_EN, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "Gesture Mode:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_CHARGER_MODE_EN, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "charge stat:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_INT_CNT, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "INT count:0x%02x\n", val);
+
+    fts_read_reg(FTS_REG_FLOW_WORK_CNT, &val);
+    count += snprintf(buf + count, PAGE_SIZE, "ESD count:0x%02x\n", val);
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_proc_busy(0);
+#endif
+
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_dumpreg_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_dump_reg interface */
+static ssize_t fts_tpbuf_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    int i = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count += snprintf(buf + count, PAGE_SIZE, "touch point buffer:\n");
+    for (i = 0; i < fts_data->pnt_buf_size; i++) {
+        count += snprintf(buf + count, PAGE_SIZE, "%02x ", fts_data->point_buf[i]);
+    }
+    count += snprintf(buf + count, PAGE_SIZE, "\n");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_tpbuf_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+/* fts_log_level interface */
+static ssize_t fts_log_level_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    count += snprintf(buf + count, PAGE_SIZE, "log level:%d\n",
+                      fts_data->log_level);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_log_level_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int value = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    FTS_FUNC_ENTER();
+    mutex_lock(&input_dev->mutex);
+    sscanf(buf, "%d", &value);
+    FTS_DEBUG("log level:%d->%d", fts_data->log_level, value);
+    fts_data->log_level = value;
+    mutex_unlock(&input_dev->mutex);
+    FTS_FUNC_EXIT();
+
+    return count;
+}
+
+/* get the fw version  example:cat fw_version */
+static DEVICE_ATTR(fts_fw_version, S_IRUGO | S_IWUSR, fts_tpfwver_show, fts_tpfwver_store);
+
+/* read and write register(s)
+*   All data type is **HEX**
+*   Single Byte:
+*       read:   echo 88 > rw_reg ---read register 0x88
+*       write:  echo 8807 > rw_reg ---write 0x07 into register 0x88
+*   Multi-bytes:
+*       [0:rw-flag][1-2: reg addr, hex][3-4: length, hex][5-6...n-n+1: write data, hex]
+*       rw-flag: 0, write; 1, read
+*       read:  echo 10005           > rw_reg ---read reg 0x00-0x05
+*       write: echo 000050102030405 > rw_reg ---write reg 0x00-0x05 as 01,02,03,04,05
+*  Get result:
+*       cat rw_reg
+*/
+static DEVICE_ATTR(fts_rw_reg, S_IRUGO | S_IWUSR, fts_tprwreg_show, fts_tprwreg_store);
+/*  upgrade from fw bin file   example:echo "*.bin" > fts_upgrade_bin */
+static DEVICE_ATTR(fts_upgrade_bin, S_IRUGO | S_IWUSR, fts_fwupgradebin_show, fts_fwupgradebin_store);
+static DEVICE_ATTR(fts_force_upgrade, S_IRUGO | S_IWUSR, fts_fwforceupg_show, fts_fwforceupg_store);
+static DEVICE_ATTR(fts_driver_info, S_IRUGO | S_IWUSR, fts_driverinfo_show, fts_driverinfo_store);
+static DEVICE_ATTR(fts_dump_reg, S_IRUGO | S_IWUSR, fts_dumpreg_show, fts_dumpreg_store);
+static DEVICE_ATTR(fts_hw_reset, S_IRUGO | S_IWUSR, fts_hw_reset_show, fts_hw_reset_store);
+static DEVICE_ATTR(fts_irq, S_IRUGO | S_IWUSR, fts_irq_show, fts_irq_store);
+static DEVICE_ATTR(fts_boot_mode, S_IRUGO | S_IWUSR, fts_bootmode_show, fts_bootmode_store);
+static DEVICE_ATTR(fts_touch_point, S_IRUGO | S_IWUSR, fts_tpbuf_show, fts_tpbuf_store);
+static DEVICE_ATTR(fts_log_level, S_IRUGO | S_IWUSR, fts_log_level_show, fts_log_level_store);
+
+/* add your attr in here*/
+static struct attribute *fts_attributes[] = {
+    &dev_attr_fts_fw_version.attr,
+    &dev_attr_fts_rw_reg.attr,
+    &dev_attr_fts_dump_reg.attr,
+    &dev_attr_fts_upgrade_bin.attr,
+    &dev_attr_fts_force_upgrade.attr,
+    &dev_attr_fts_driver_info.attr,
+    &dev_attr_fts_hw_reset.attr,
+    &dev_attr_fts_irq.attr,
+    &dev_attr_fts_boot_mode.attr,
+    &dev_attr_fts_touch_point.attr,
+    &dev_attr_fts_log_level.attr,
+    NULL
+};
+
+static struct attribute_group fts_attribute_group = {
+    .attrs = fts_attributes
+};
+
+static ssize_t proc_fw_update_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    struct fts_ts_data *ts_data = pde_data(file_inode(filp));
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+    int buflen = count;
+
+    FTS_INFO("upgrade with bin file through proc node");
+    if (!ts_data) {
+        FTS_ERROR("ts_data is null");
+        return -EINVAL;
+    }
+
+    if ((buflen <= 0) || (buflen >= FILE_NAME_LENGTH)) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(fwname, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+    fwname[buflen - 1] = '\0';
+
+    mutex_lock(&ts_data->input_dev->mutex);
+    fts_upgrade_bin(fwname, 0);
+    mutex_unlock(&ts_data->input_dev->mutex);
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_fw_update_fops = {
+    .proc_write  = proc_fw_update_write,
+};
+#else
+static const struct file_operations proc_fw_update_fops = {
+    .owner  = THIS_MODULE,
+    .write = proc_fw_update_write,
+};
+#endif
+
+/* scan modes */
+static ssize_t proc_scan_modes_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int cnt = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "scan_modes=0,1,2,3,4");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "0:Auto mode");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+         "1:Normal Active");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "2:Normal Idle");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "3:Low Power Active");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "%s\n",
+        "4:Low Power Idle");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_scan_modes_fops = {
+    .proc_read   = proc_scan_modes_read,
+};
+#else
+static const struct file_operations proc_scan_modes_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_scan_modes_read,
+};
+#endif
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+/* touch scan mode */
+int gti_get_scan_mode(void *private_data, struct gti_scan_cmd *cmd)
+{
+    int ret = 0;
+    u8 gesture_mode = 0;
+    u8 power_mode = 0;
+    u8 monitor_ctrl = 0;
+
+    ret = fts_read_reg(FTS_REG_GESTURE_EN, &gesture_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xD0 fails");
+        return ret;
+    }
+
+    ret = fts_read_reg(FTS_REG_POWER_MODE, &power_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xA5 fails");
+        return ret;
+    }
+
+    ret = fts_read_reg(FTS_REG_MONITOR_CTRL, &monitor_ctrl);
+    if (ret < 0) {
+        FTS_ERROR("read reg0x86 fails");
+        return ret;
+    }
+
+    if (!(0 == gesture_mode || 1 == gesture_mode) ||
+        !(0 == power_mode || 1 == power_mode) ||
+        !(0 == monitor_ctrl || 1 == monitor_ctrl || 3 == monitor_ctrl)) {
+        FTS_ERROR("invalid value: gesture %u, power %u, monitor %u",
+                  gesture_mode, power_mode, monitor_ctrl);
+        return -EINVAL;
+    }
+
+    if (gesture_mode) {
+        if (power_mode == 0)
+            cmd->setting = GTI_SCAN_MODE_LP_ACTIVE;
+        else if (power_mode == 1)
+            cmd->setting = GTI_SCAN_MODE_LP_IDLE;
+    } else {
+      if (monitor_ctrl == 3) {
+            cmd->setting = GTI_SCAN_MODE_NORMAL_IDLE;
+      } else if (monitor_ctrl == 1) {
+            cmd->setting = GTI_SCAN_MODE_AUTO;
+      } else {
+            cmd->setting = GTI_SCAN_MODE_NORMAL_ACTIVE;
+      }
+    }
+
+    return 0;
+}
+
+/* Scan mode definition
+ * Gesture PowerMode MointorCtrl
+ *   0       N/A          1      : Auto mode
+ *   0        0           0      : Normal Active
+ *   0        1           3      : Normal Idle
+ *   1        0           0      : Low Power Active
+ *   1        1           3      : Low Power Idle
+ */
+
+int gti_set_scan_mode(void *private_data, struct gti_scan_cmd *cmd)
+{
+    struct fts_ts_data *ts_data = private_data;
+    int ret = 0;
+    u8 power_mode;
+    u8 gesture_en;
+    u8 monitor_ctrl;
+
+    switch (cmd->setting) {
+    case GTI_SCAN_MODE_AUTO:
+        fts_reset_proc(0);
+        fts_wait_tp_to_valid();
+        fts_update_feature_setting(ts_data);
+        return 0;
+        break;
+    case GTI_SCAN_MODE_NORMAL_ACTIVE:
+        gesture_en = 0;
+        power_mode = 0;
+        monitor_ctrl = 0;
+        break;
+    case GTI_SCAN_MODE_NORMAL_IDLE:
+        gesture_en = 0;
+        power_mode = 1;
+        monitor_ctrl = 3;
+        break;
+    case GTI_SCAN_MODE_LP_ACTIVE:
+        gesture_en = 1;
+        power_mode = 0;
+        monitor_ctrl = 0;
+        break;
+    case GTI_SCAN_MODE_LP_IDLE:
+        gesture_en = 1;
+        power_mode = 1;
+        monitor_ctrl = 3;
+        break;
+    default:
+        FTS_ERROR("Input index of mode is out of range!");
+        return 0;
+    }
+
+    ret = fts_write_reg(FTS_REG_POWER_MODE, power_mode);
+    if (ret < 0) {
+      FTS_ERROR("write reg0xA5 fails");
+      return ret;
+    }
+
+    ret = fts_write_reg(FTS_REG_GESTURE_EN, gesture_en);
+    if (ret < 0) {
+      FTS_ERROR("write reg0xD0 fails");
+      return ret;
+    }
+
+    ret = fts_write_reg(FTS_REG_MONITOR_CTRL, monitor_ctrl);
+    if (ret < 0) {
+      FTS_ERROR("write reg0x86 fails");
+      return ret;
+    }
+
+    return 0;
+}
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+
+/* lpwg */
+static ssize_t proc_lpwg_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 gesture_mode = 0;
+    u8 gesture_function = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_GESTURE_EN, &gesture_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xD0 fails");
+        return ret;
+    }
+
+    ret = fts_read_reg(FTS_REG_GESTURE_SWITCH, &gesture_function);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xCF fails");
+        return ret;
+    }
+
+    if (gesture_function == 1)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Gesture_mode: STTW\n");
+    else if (gesture_function == 2)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Gesture_mode: LPTW\n");
+    else if (gesture_function == 3)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Gesture_mode: STTW + LPTW\n");
+    else if (gesture_function == 0)
+        cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Disable STTW and LPTW\n");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_lpwg_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int gesture_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &gesture_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch gesture mode to %d", gesture_mode);
+
+    switch (gesture_mode) {
+    case 0:
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 0);
+         if (ret < 0) {
+            FTS_ERROR("write reg 0xCF fails");
+            return ret;
+         }
+         break;
+
+    case 1:    //Single tap
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 1);
+         if (ret < 0) {
+             FTS_ERROR("write reg 0xCF fails");
+             return ret;
+         }
+         FTS_INFO("switch gesture function to STTW");
+         break;
+
+    case 2:    //Long press
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 2);
+         if (ret < 0) {
+             FTS_ERROR("write reg 0xCF fails");
+             return ret;
+         }
+         FTS_INFO("switch gesture function to LPTW");
+         break;
+
+    case 3:    //Single tap + Long press
+         ret = fts_write_reg(FTS_REG_GESTURE_SWITCH, 3);
+         if (ret < 0) {
+             FTS_ERROR("write reg 0xCF fails");
+             return ret;
+          }
+         FTS_INFO("switch gesture function to STTW + LPTW");
+         break;
+
+    default:
+         break;
+   }
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_lpwg_fops = {
+    .proc_read   = proc_lpwg_read,
+    .proc_write  = proc_lpwg_write,
+};
+#else
+static const struct file_operations proc_lpwg_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_lpwg_read,
+    .write  = proc_lpwg_write,
+};
+#endif
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+/* screen protector */
+int gti_get_screen_protector_mode(void *private_data,
+        struct gti_screen_protector_mode_cmd *cmd)
+{
+    int ret = 0;
+    u8 screen_protector_mode = 0;
+
+    ret = fts_read_reg(FTS_REG_GLOVE_MODE_EN, &screen_protector_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xC0 fails");
+        return ret;
+    }
+
+    cmd->setting = screen_protector_mode ?
+        GTI_SCREEN_PROTECTOR_MODE_ENABLE : GTI_SCREEN_PROTECTOR_MODE_DISABLE;
+
+    return 0;
+}
+
+int gti_set_screen_protector_mode(void *private_data,
+    struct gti_screen_protector_mode_cmd *cmd)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+
+    ret = fts_set_glove_mode(ts_data,
+        cmd->setting == GTI_SCREEN_PROTECTOR_MODE_ENABLE);
+
+    return ret;
+}
+
+/* palm */
+int gti_get_palm_mode(void *private_data, struct gti_palm_cmd *cmd)
+{
+    int ret = 0;
+    u8 palm_mode = 0;
+
+    ret = fts_read_reg(FTS_REG_PALM_EN, &palm_mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xC5 fails");
+        return ret;
+    }
+
+    FTS_DEBUG("fw_palm = %d", palm_mode);
+    cmd->setting = palm_mode ? GTI_PALM_ENABLE : GTI_PALM_DISABLE;
+
+    return 0;
+}
+
+/* Set palm rejection mode.
+ * 0 - Disable fw palm rejection.
+ * 1 - Enable fw palm rejection.
+ */
+int gti_set_palm_mode(void *private_data, struct gti_palm_cmd *cmd)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    ts_data->enable_fw_palm = (cmd->setting == GTI_GRIP_ENABLE) ?
+        FW_GRIP_ENABLE : FW_GRIP_DISABLE;
+
+    FTS_INFO("switch fw_aplm to %u\n", ts_data->enable_fw_palm);
+
+    ret = fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+
+/* grip */
+static ssize_t proc_grip_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    FTS_DEBUG("fw_grip = %u", ts_data->enable_fw_grip);
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "%u\n", ts_data->enable_fw_grip);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+/* Set Grip suppression mode.
+ * 0 - Disable fw grip suppression.
+ * 1 - Enable fw grip suppression.
+ * 2 - Force disable fw grip suppression.
+ * 3 - Force enable fw grip suppression.
+ */
+static ssize_t proc_grip_write(struct file *filp, const char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int grip_mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &grip_mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+    if (grip_mode < 0 || grip_mode > 3) {
+        FTS_ERROR("get mode fails, grip_mode should be in [0,1,2,3].");
+        return -EINVAL;
+    }
+
+    ts_data->enable_fw_grip = grip_mode;
+    FTS_INFO("switch fw_grip to %u\n", ts_data->enable_fw_grip);
+
+    ret = fts_set_grip_mode(ts_data, grip_mode);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_grip_fops = {
+    .proc_read   = proc_grip_read,
+    .proc_write  = proc_grip_write,
+};
+#else
+static const struct file_operations proc_grip_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_grip_read,
+    .write  = proc_grip_write,
+};
+#endif
+
+enum FTS_COORDINATE_FILTER {
+    COORDINATE_FILTER_MAPPING,
+    COORDINATE_FILTER_SMOOTH,
+    COORDINATE_FILTER_STABLE,
+    COORDINATE_FILTER_END,
+};
+
+static ssize_t proc_coordinate_filter_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_COORDINATE_FILTER, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xE6 fails");
+        return ret;
+    }
+
+    cnt += scnprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+          "Coordinator filter : Mapping(%s), Smooth(%s), Stable(%s)\n"
+          ,mode & (1 << COORDINATE_FILTER_MAPPING) ? "Y" : "N"
+          ,mode & (1 << COORDINATE_FILTER_SMOOTH) ? "Y" : "N"
+          ,mode & (1 << COORDINATE_FILTER_STABLE) ? "Y" : "N");
+
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_coordinate_filter_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    if (mode < COORDINATE_FILTER_MAPPING || mode >= (1 << COORDINATE_FILTER_END)) {
+        FTS_ERROR("This mode not supported");
+        return -EINVAL;
+    }
+
+    FTS_INFO("Coordinator filter : Mapping(%s), Smooth(%s), Stable(%s)\n"
+          ,mode & (1 << COORDINATE_FILTER_MAPPING) ? "Y" : "N"
+          ,mode & (1 << COORDINATE_FILTER_SMOOTH) ? "Y" : "N"
+          ,mode & (1 << COORDINATE_FILTER_STABLE) ? "Y" : "N");
+
+    ret = fts_write_reg(FTS_REG_COORDINATE_FILTER, mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg0xE6 fails");
+        return ret;
+    }
+
+    return count;
+}
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_coordinate_filter_fops = {
+    .proc_read   = proc_coordinate_filter_read,
+    .proc_write  = proc_coordinate_filter_write,
+};
+#else
+static const struct file_operations proc_coordinate_filter_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_coordinate_filter_read,
+    .write  = proc_coordinate_filter_write,
+};
+#endif
+
+/* sense on and off */
+static ssize_t proc_sense_onoff_read(struct file *filp, char __user *buff,
+    size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_SENSE_ONOFF, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xEA fails");
+        return ret;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Sensing mode:%s\n",
+        mode ? "Enable" : "Disable");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_sense_onoff_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch touch sense on/off to %d", mode);
+    ret = fts_write_reg(FTS_REG_SENSE_ONOFF, !!mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg0xEA fails");
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_sense_onoff_fops = {
+    .proc_read   = proc_sense_onoff_read,
+    .proc_write  = proc_sense_onoff_write,
+};
+#else
+static const struct file_operations proc_sense_onoff_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_sense_onoff_read,
+    .write  = proc_sense_onoff_write,
+};
+#endif
+
+/* IRQ on and off */
+static ssize_t proc_irq_onoff_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_IRQ_ONOFF, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xEB fails");
+        return ret;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "touch IRQ:%s\n",
+        mode ? "Enable" : "Disable");
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_irq_onoff_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch touch IRQ on/off to %d", mode);
+    ret = fts_write_reg(FTS_REG_IRQ_ONOFF, !!mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg_0xEB fails");
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_irq_onoff_fops = {
+    .proc_read   = proc_irq_onoff_read,
+    .proc_write  = proc_irq_onoff_write,
+};
+#else
+static const struct file_operations proc_irq_onoff_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_irq_onoff_read,
+    .write  = proc_irq_onoff_write,
+};
+#endif
+
+/* INT2 control */
+static ssize_t proc_int2_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_INT2, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xBF fails");
+        return ret;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "INT2 mode:%d\n",
+        mode);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_int2_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    if (mode == 0) {
+      FTS_INFO("switch touch INT2 to keeping LOW");
+    } else if (mode == 1) {
+      FTS_INFO("switch touch INT2 to keeping Hight");
+    } else {
+        FTS_ERROR("This mode not supported");
+        return -EINVAL;
+    }
+
+    ret = fts_write_reg(FTS_REG_INT2, mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg_0xBF fails");
+        return ret;
+    }
+
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_int2_fops = {
+    .proc_read   = proc_int2_read,
+    .proc_write  = proc_int2_write,
+};
+#else
+static const struct file_operations proc_int2_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_int2_read,
+    .write  = proc_int2_write,
+};
+#endif
+
+/* heatmap on and off */
+static ssize_t proc_heatmap_onoff_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_HEATMAP_98, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0x%X fails", FTS_REG_HEATMAP_98);
+        return ret;
+    }
+
+    switch (mode) {
+    case FW_HEATMAP_MODE_DISABLE:
+        cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "heatmap is off\n");
+        break;
+    case FW_HEATMAP_MODE_DIFF:
+        cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "heatmap is Diff\n");
+        break;
+    case FW_HEATMAP_MODE_BASELINE:
+        cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "heatmap is Baseline\n");
+        break;
+    case FW_HEATMAP_MODE_RAWDATA:
+        cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "heatmap is Rawdata\n");
+        break;
+    default:
+        cnt += snprintf(tmpbuf + cnt,  PROC_BUF_SIZE - cnt, "unknown value %02x \n", mode);
+        break;
+    }
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_heatmap_onoff_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    if (mode < FW_HEATMAP_MODE_DISABLE || mode > FW_HEATMAP_MODE_BASELINE) {
+        FTS_ERROR("Please input the parameters in \n \
+             0: Disable firmware heatmap. \n \
+             1: Enable firmware Diff heatmap. \n \
+             2: Enable firmware Baseline heatmap. \n \
+             3: Enable firmware Rawdata heatmap.");
+        return -EINVAL;
+    }
+
+    FTS_INFO("switch heatmap on/off to %d", mode);
+    fts_set_heatmap_mode(ts_data, mode);
+    return count;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_heatmap_onoff_fops = {
+    .proc_read   = proc_heatmap_onoff_read,
+    .proc_write  = proc_heatmap_onoff_write,
+};
+#else
+static const struct file_operations proc_heatmap_onoff_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_heatmap_onoff_read,
+    .write  = proc_heatmap_onoff_write,
+};
+#endif
+
+
+static ssize_t proc_LPTW_setting_write(
+    struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+
+    int buflen = count;
+    int lptw_write_data[FTS_LPTW_BUF_LEN] = {0};
+    u8  write_data[FTS_LPTW_BUF_LEN] = {0};
+
+    u32 data_length = 0;
+    int i;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x", &lptw_write_data[0],
+        &lptw_write_data[1], &lptw_write_data[2], &lptw_write_data[3],
+        &lptw_write_data[4], &lptw_write_data[5], &lptw_write_data[6],
+        &lptw_write_data[7], &lptw_write_data[8], &lptw_write_data[9],
+        &lptw_write_data[10], &lptw_write_data[11], &lptw_write_data[12],
+        &lptw_write_data[13], &lptw_write_data[14], &lptw_write_data[15],
+        &lptw_write_data[16], &lptw_write_data[17], &lptw_write_data[18],
+        &lptw_write_data[19]);
+
+    if(lptw_write_data[0] == FTS_LPTW_REG_SET_E3)
+        data_length = FTS_LPTW_E3_BUF_LEN;
+    else if (lptw_write_data[0] == FTS_LPTW_REG_SET_E4)
+        data_length = FTS_LPTW_E4_BUF_LEN;
+    else
+        data_length = 0;
+
+    for (i = 0; i < data_length; i++)
+        write_data[i] = (char)lptw_write_data[i];
+
+    if (data_length != 0){
+        ret=fts_write(write_data, data_length);
+        if (ret < 0) {
+            FTS_ERROR("write data to register E3/E4 fail");
+            return ret;
+        }
+    }
+
+ return count;
+}
+
+/*LPTW setting read*/
+static ssize_t proc_LPTW_setting_read(
+    struct file *filp, char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+    u8 cmd[2] = {0};
+    int num_read_chars = 0;
+    loff_t pos = *ppos;
+    int buflen = count;
+    u8 *readbuf = NULL;
+    u8 read_tmpbuf[20] = {0};
+
+    if (pos)
+        return 0;
+
+    if (buflen <= 0) {
+        FTS_ERROR("apk proc read count(%d) fail", buflen);
+        return -EINVAL;
+    }
+
+    if (buflen > PROC_BUF_SIZE) {
+        readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL);
+        if (NULL == readbuf) {
+            FTS_ERROR("apk proc buf zalloc fail");
+            return -ENOMEM;
+        }
+    } else {
+        readbuf = read_tmpbuf;
+    }
+
+    cmd[0] = FTS_LPTW_REG_SET_E3;
+    cmd[1] = FTS_LPTW_REG_SET_E4;
+    ret = fts_read(&cmd[0], 1, readbuf, FTS_LPTW_E3_BUF_LEN - 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE3 fails");
+        goto proc_read_err;
+    }
+
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "==LPTW Gesture setting(E3)==\n");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "min_x :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[0] & 0xFF) << 8) + (readbuf[1] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "max_x :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[2] & 0xFF) << 8) + (readbuf[3] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "min_y :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[4] & 0xFF) << 8) + (readbuf[5] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "max_y :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[6] & 0xFF) << 8) + (readbuf[7] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_frame_count :%3d\n",(readbuf[8] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "jitter :%3d\n" ,(readbuf[9] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "max_touch_size :%3d\n\n", (readbuf[10] & 0xFF));
+
+    ret = fts_read(&cmd[1], 1, readbuf, FTS_LPTW_E4_BUF_LEN - 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE4 fails");
+        goto proc_read_err;
+    }
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "==LPTW Gesture setting(E4)==\n");
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "motion_boundary:%4d\n",
+        ((readbuf[0] & 0xFF) << 8) + (readbuf[1] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "INT2_deassert_min_x:%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[2] & 0xFF) << 8) + (readbuf[3] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "INT2_deassert_min_y:%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[4] & 0xFF) << 8) + (readbuf[5] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "INT2_deassert_max_x:%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[6] & 0xFF) << 8) + (readbuf[7] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "INT2_deassert_max_y:%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[8] & 0xFF) << 8) + (readbuf[9] & 0xFF)));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_min_x :%2d\n", (readbuf[10] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_max_x :%2d\n", (readbuf[11] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_min_y :%2d\n", (readbuf[12] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "marginal_max_y :%2d\n", (readbuf[13] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_min_tx :%2d\n", (readbuf[14] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_max_tx :%2d\n", (readbuf[15] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_min_rx :%2d\n", (readbuf[16] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "monitor_channel_max_rx :%2d\n", (readbuf[17] & 0xFF));
+    cnt += snprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+        "min_node_count :%2d\n", (readbuf[18] & 0xFF));
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+
+proc_read_err:
+    if (copy_to_user(buff, readbuf, num_read_chars)) {
+        FTS_ERROR("copy to user error");
+        ret = -EFAULT;
+    }
+
+    if ((buflen > PROC_BUF_SIZE) && readbuf) {
+        kfree(readbuf);
+        readbuf = NULL;
+    }
+    return ret;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops LPTW_setting_fops = {
+    .proc_read   = proc_LPTW_setting_read,
+    .proc_write  = proc_LPTW_setting_write,
+};
+#else
+static const struct file_operations LPTW_setting_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_LPTW_setting_read,
+    .write  = proc_LPTW_setting_write,
+};
+#endif
+
+static ssize_t proc_STTW_setting_write(
+    struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+    int buflen = count;
+    int sttw_write_data[FTS_STTW_E5_BUF_LEN] = {0};
+    u8  write_data[FTS_STTW_E5_BUF_LEN] = {0};
+
+    u8 data_length = 0;
+    int i = 0;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%x%x%x%x%x%x%x%x%x%x%x%x%x%x", &sttw_write_data[0],
+        &sttw_write_data[1], &sttw_write_data[2], &sttw_write_data[3],
+        &sttw_write_data[4], &sttw_write_data[5], &sttw_write_data[6],
+        &sttw_write_data[7], &sttw_write_data[8], &sttw_write_data[9],
+        &sttw_write_data[10], &sttw_write_data[11], &sttw_write_data[12],
+        &sttw_write_data[13]);
+
+    if (sttw_write_data[0] == FTS_STTW_REG_SET_E5) {
+        data_length = FTS_STTW_E5_BUF_LEN;
+        for (i = 0; i < data_length; i++)
+            write_data[i] = (char)sttw_write_data[i];
+
+        ret = fts_write(write_data,data_length);
+        if (ret < 0) {
+            FTS_ERROR("write data to register E5 fail");
+            return ret;
+        }
+    }
+
+ return count;
+}
+
+/*STTW setting read*/
+size_t google_internal_sttw_setting_read(char *buf, size_t buf_size)
+{
+    u8 readbuf[FTS_STTW_E5_BUF_LEN] = {0};
+    u8 cmd[2] = {0};
+    int cnt = 0;
+    int ret = 0;
+
+    if (buf == NULL) {
+      return 0;
+    }
+
+    cmd[0] = FTS_STTW_REG_SET_E5;
+
+    ret = fts_read(&cmd[0], 1, readbuf, FTS_STTW_E5_BUF_LEN - 1);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE5 fails");
+        return 0;
+    }
+
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "==STTW Gesture setting(E5)==\n");
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "min_x :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[0] & 0xFF) << 8) +(readbuf[1] & 0xFF)));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "min_y :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[2] & 0xFF) << 8) +(readbuf[3] & 0xFF)));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "max_x :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[4] & 0xFF) << 8) +(readbuf[5] & 0xFF)));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "max_y :%4d\n",
+        FTS_TOUCH_HIRES(((readbuf[6] & 0xFF) << 8) +(readbuf[7] & 0xFF)));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "max_frame_count :%4d\n",((readbuf[8] & 0xFF) << 8) +(readbuf[9] & 0xFF));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "min_frame_count :%3d\n", (readbuf[10] & 0xFF));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "jitter :%3d\n",(readbuf[11] & 0xFF));
+    cnt += snprintf(buf + cnt, buf_size - cnt,
+        "tap_max_touch_size :%3d\n", (readbuf[12] & 0xFF));
+
+    return cnt;
+}
+
+static ssize_t proc_STTW_setting_read(
+    struct file *filp, char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    char tmpbuf[PROC_BUF_SIZE] = {0};
+    loff_t pos = *ppos;
+
+    if (pos)
+        return 0;
+
+    cnt = google_internal_sttw_setting_read(tmpbuf, PROC_BUF_SIZE);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops STTW_setting_fops = {
+    .proc_read   = proc_STTW_setting_read,
+    .proc_write  = proc_STTW_setting_write,
+};
+#else
+static const struct file_operations STTW_setting_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_STTW_setting_read,
+    .write  = proc_STTW_setting_write,
+};
+#endif
+
+static ssize_t proc_continuous_report_read(struct file *filp,
+    char __user *buff, size_t count, loff_t *ppos)
+{
+    int cnt = 0;
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    u8 mode = 0;
+    loff_t pos = *ppos;
+    bool is_continuous = false;
+    u8 continuous_frame = 0;
+
+    if (pos)
+        return 0;
+
+    ret = fts_read_reg(FTS_REG_CONTINUOUS_EN, &mode);
+    if (ret < 0) {
+        FTS_ERROR("read reg_0xE7 fails");
+        return ret;
+    }
+
+    // Bit   [0]: 0 -> Non Continuous, 1 -> Continuous
+    // Bit [7:1]: continuous frame number
+
+    is_continuous = mode & 0x01;
+    continuous_frame = (mode >> 1);
+
+    cnt += scnprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt, "Continuous mode: %s\n",
+                    is_continuous ? "continuous" : "non-continuous");
+    cnt += scnprintf(tmpbuf + cnt, PROC_BUF_SIZE - cnt,
+                    "Continuous frame: %u\n", continuous_frame);
+
+    if (copy_to_user(buff, tmpbuf, cnt)) {
+        FTS_ERROR("copy to user error");
+        return -EFAULT;
+    }
+
+    *ppos = pos + cnt;
+    return cnt;
+}
+
+static ssize_t proc_continuous_report_write(struct file *filp,
+    const char __user *buff, size_t count, loff_t *ppos)
+{
+    int ret = 0;
+    char tmpbuf[PROC_BUF_SIZE] = { 0 };
+    int mode = 0xFF;
+    int buflen = count;
+    bool is_continuous = false;
+    u8 continuous_frame = 0;
+
+    if (buflen >= PROC_BUF_SIZE) {
+        FTS_ERROR("proc write length(%d) fails", buflen);
+        return -EINVAL;
+    }
+
+    if (copy_from_user(tmpbuf, buff, buflen)) {
+        FTS_ERROR("copy from user error");
+        return -EFAULT;
+    }
+
+    ret = sscanf(tmpbuf, "%d", &mode);
+    if (ret != 1) {
+        FTS_ERROR("get mode fails,ret=%d", ret);
+        return -EINVAL;
+    }
+
+    // Bit   [0]: 0 -> Non Continuous, 1 -> Continuous
+    // Bit [7:1]: continuous frame number
+
+    is_continuous = mode & 0x01;
+    continuous_frame = (mode >> 1) & 0xFF;
+
+    FTS_INFO("Set continuous mode: %s\n",
+             is_continuous ? "continuous" : "non-continuous");
+    FTS_INFO("Set continuous frame: %u\n", continuous_frame);
+
+    ret = fts_write_reg(FTS_REG_CONTINUOUS_EN, mode);
+    if (ret < 0) {
+        FTS_ERROR("write reg_0xE7 fails");
+        return ret;
+    }
+
+    return count;
+}
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_continuous_report_fops = {
+    .proc_read   = proc_continuous_report_read,
+    .proc_write  = proc_continuous_report_write,
+};
+#else
+static const struct file_operations proc_continuous_report_fops = {
+    .owner  = THIS_MODULE,
+    .read   = proc_continuous_report_read,
+    .write  = proc_continuous_report_write,
+};
+#endif
+
+struct proc_dir_entry *proc_fw_update;
+struct proc_dir_entry *proc_scan_modes;
+struct proc_dir_entry *proc_lpwg;
+struct proc_dir_entry *proc_grip;
+struct proc_dir_entry *proc_coordinate_filter;
+struct proc_dir_entry *proc_sense_onoff;
+struct proc_dir_entry *proc_irq_onoff;
+struct proc_dir_entry *proc_int2;
+struct proc_dir_entry *proc_heatmap_onoff;
+struct proc_dir_entry *proc_LPTW_setting;
+struct proc_dir_entry *proc_STTW_setting;
+struct proc_dir_entry *proc_continuous_report;
+
+static int fts_create_ctrl_procs(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    proc_fw_update = proc_create_data("fw_update", S_IWUSR,
+        ts_data->proc_touch_entry, &proc_fw_update_fops, ts_data);
+    if (!proc_fw_update) {
+        FTS_ERROR("create proc_fw_update entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_scan_modes = proc_create_data("scan_modes", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_scan_modes_fops, ts_data);
+    if (!proc_scan_modes) {
+        FTS_ERROR("create proc_scan_modes entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_lpwg = proc_create_data("lpwg", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_lpwg_fops, ts_data);
+    if (!proc_lpwg) {
+        FTS_ERROR("create proc_lpwg entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_grip = proc_create_data("fw_grip", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_grip_fops, ts_data);
+    if (!proc_grip) {
+        FTS_ERROR("create proc_grip entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_coordinate_filter = proc_create_data("coordinate_filter", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_coordinate_filter_fops, ts_data);
+    if (!proc_coordinate_filter) {
+        FTS_ERROR("create proc_coordinate_filter entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_sense_onoff = proc_create_data("sense_onoff", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_sense_onoff_fops, ts_data);
+    if (!proc_sense_onoff) {
+        FTS_ERROR("create proc_sense_onoff entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_irq_onoff = proc_create_data("irq_onoff", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_irq_onoff_fops, ts_data);
+    if (!proc_irq_onoff) {
+        FTS_ERROR("create proc_irq_onoff entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_int2 = proc_create_data("int2", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_int2_fops, ts_data);
+    if (!proc_int2) {
+        FTS_ERROR("create proc_int2 entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_heatmap_onoff = proc_create_data("heatmap_onoff", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_heatmap_onoff_fops, ts_data);
+    if (!proc_heatmap_onoff) {
+        FTS_ERROR("create proc_heatmap_onoff entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_LPTW_setting = proc_create_data("LPTW_setting", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &LPTW_setting_fops, ts_data);
+    if (!proc_LPTW_setting) {
+        FTS_ERROR("create proc_LPTW_settingentry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_STTW_setting = proc_create_data("STTW_setting", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &STTW_setting_fops, ts_data);
+    if (!proc_STTW_setting) {
+        FTS_ERROR("create proc_STTW_settingentry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+    proc_continuous_report = proc_create_data("continuous_report", S_IRUSR|S_IWUSR,
+        ts_data->proc_touch_entry, &proc_continuous_report_fops, ts_data);
+    if (!proc_continuous_report) {
+        FTS_ERROR("create proc_continuous_report entry fail");
+        ret = -ENOMEM;
+        return ret;
+    }
+
+
+    FTS_INFO("create control procs succeeds");
+    return 0;
+}
+
+static void fts_free_ctrl_procs(void)
+{
+    if (proc_fw_update)
+        proc_remove(proc_fw_update);
+
+    if (proc_scan_modes)
+        proc_remove(proc_scan_modes);
+
+    if (proc_lpwg)
+        proc_remove(proc_lpwg);
+
+    if (proc_grip)
+        proc_remove(proc_grip);
+
+    if (proc_coordinate_filter)
+        proc_remove(proc_coordinate_filter);
+
+    if (proc_sense_onoff)
+        proc_remove(proc_sense_onoff);
+
+    if (proc_irq_onoff)
+        proc_remove(proc_irq_onoff);
+
+    if (proc_int2)
+        proc_remove(proc_int2);
+
+    if (proc_heatmap_onoff)
+        proc_remove(proc_heatmap_onoff);
+
+    if (proc_LPTW_setting)
+        proc_remove(proc_LPTW_setting);
+
+    if (proc_STTW_setting)
+        proc_remove(proc_STTW_setting);
+
+    if (proc_continuous_report)
+        proc_remove(proc_continuous_report);
+}
+
+int fts_create_sysfs(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    ret = sysfs_create_group(&ts_data->dev->kobj, &fts_attribute_group);
+    if (ret) {
+        FTS_ERROR("[EX]: sysfs_create_group() failed!!");
+        sysfs_remove_group(&ts_data->dev->kobj, &fts_attribute_group);
+        return -ENOMEM;
+    } else {
+        FTS_INFO("[EX]: sysfs_create_group() succeeded!!");
+    }
+
+    ts_data->proc_touch_entry = proc_mkdir("focaltech_touch", NULL);
+    if (!ts_data->proc_touch_entry) {
+        FTS_ERROR("create proc/focaltech_touch fails");
+    }
+
+    ret = fts_create_ctrl_procs(ts_data);
+    if (ret) {
+        FTS_ERROR("Create ctrl procs fails");
+    }
+
+    return ret;
+}
+
+int fts_remove_sysfs(struct fts_ts_data *ts_data)
+{
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_attribute_group);
+    fts_free_ctrl_procs();
+    if (ts_data->proc_touch_entry)
+        proc_remove(fts_data->proc_touch_entry);
+    return 0;
+}
diff --git a/ft3683u/focaltech_ex_mode.c b/ft3683u/focaltech_ex_mode.c
new file mode 100644
index 0000000..14091f9
--- /dev/null
+++ b/ft3683u/focaltech_ex_mode.c
@@ -0,0 +1,306 @@
+/*
+ *
+ * FocalTech ftxxxx TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_ex_mode.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-31
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* 2.Private constant and macro definitions using #define
+*****************************************************************************/
+
+/*****************************************************************************
+* 3.Private enumerations, structures and unions using typedef
+*****************************************************************************/
+enum _ex_mode {
+    MODE_GLOVE = 0,
+    MODE_COVER,
+    MODE_CHARGER,
+};
+
+/*****************************************************************************
+* 4.Static variables
+*****************************************************************************/
+
+/*****************************************************************************
+* 5.Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* 6.Static function prototypes
+*******************************************************************************/
+static int fts_ex_mode_switch(enum _ex_mode mode, u8 value)
+{
+    int ret = 0;
+    u8 m_val = 0;
+
+    if (value)
+        m_val = 0x01;
+    else
+        m_val = 0x00;
+
+    switch (mode) {
+    case MODE_GLOVE:
+        ret = fts_write_reg(FTS_REG_GLOVE_MODE_EN, m_val);
+        if (ret < 0) {
+            FTS_ERROR("MODE_GLOVE switch to %d fail", m_val);
+        }
+        break;
+    case MODE_COVER:
+        ret = fts_write_reg(FTS_REG_COVER_MODE_EN, m_val);
+        if (ret < 0) {
+            FTS_ERROR("MODE_COVER switch to %d fail", m_val);
+        }
+        break;
+    case MODE_CHARGER:
+        ret = fts_write_reg(FTS_REG_CHARGER_MODE_EN, m_val);
+        if (ret < 0) {
+            FTS_ERROR("MODE_CHARGER switch to %d fail", m_val);
+        }
+        break;
+    default:
+        FTS_ERROR("mode(%d) unsupport", mode);
+        ret = -EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+static ssize_t fts_glove_mode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    fts_read_reg(FTS_REG_GLOVE_MODE_EN, &val);
+    count = snprintf(buf + count, PAGE_SIZE, "Glove Mode:%s\n",
+                     ts_data->glove_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Glove Reg(0xC0):%d\n", val);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_glove_mode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        if (!ts_data->glove_mode) {
+            FTS_DEBUG("enter glove mode");
+            ret = fts_ex_mode_switch(MODE_GLOVE, ENABLE);
+            if (ret >= 0) {
+                ts_data->glove_mode = ENABLE;
+            }
+        }
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        if (ts_data->glove_mode) {
+            FTS_DEBUG("exit glove mode");
+            ret = fts_ex_mode_switch(MODE_GLOVE, DISABLE);
+            if (ret >= 0) {
+                ts_data->glove_mode = DISABLE;
+            }
+        }
+    }
+
+    FTS_DEBUG("glove mode:%d", ts_data->glove_mode);
+    return count;
+}
+
+
+static ssize_t fts_cover_mode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    fts_read_reg(FTS_REG_COVER_MODE_EN, &val);
+    count = snprintf(buf + count, PAGE_SIZE, "Cover Mode:%s\n",
+                     ts_data->cover_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Cover Reg(0xC1):%d\n", val);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_cover_mode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        if (!ts_data->cover_mode) {
+            FTS_DEBUG("enter cover mode");
+            ret = fts_ex_mode_switch(MODE_COVER, ENABLE);
+            if (ret >= 0) {
+                ts_data->cover_mode = ENABLE;
+            }
+        }
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        if (ts_data->cover_mode) {
+            FTS_DEBUG("exit cover mode");
+            ret = fts_ex_mode_switch(MODE_COVER, DISABLE);
+            if (ret >= 0) {
+                ts_data->cover_mode = DISABLE;
+            }
+        }
+    }
+
+    FTS_DEBUG("cover mode:%d", ts_data->cover_mode);
+    return count;
+}
+
+static ssize_t fts_charger_mode_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    fts_read_reg(FTS_REG_CHARGER_MODE_EN, &val);
+    count = snprintf(buf + count, PAGE_SIZE, "Charger Mode:%s\n",
+                     ts_data->charger_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Charger Reg(0x8B):%d\n", val);
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_charger_mode_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        if (!ts_data->charger_mode) {
+            FTS_DEBUG("enter charger mode");
+            ret = fts_ex_mode_switch(MODE_CHARGER, ENABLE);
+            if (ret >= 0) {
+                ts_data->charger_mode = ENABLE;
+            }
+        }
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        if (ts_data->charger_mode) {
+            FTS_DEBUG("exit charger mode");
+            ret = fts_ex_mode_switch(MODE_CHARGER, DISABLE);
+            if (ret >= 0) {
+                ts_data->charger_mode = DISABLE;
+            }
+        }
+    }
+
+    FTS_DEBUG("charger mode:%d", ts_data->glove_mode);
+    return count;
+}
+
+
+/* read and write charger mode
+ * read example: cat fts_glove_mode        ---read  glove mode
+ * write example:echo 1 > fts_glove_mode   ---write glove mode to 01
+ */
+static DEVICE_ATTR(fts_glove_mode, S_IRUGO | S_IWUSR,
+                   fts_glove_mode_show, fts_glove_mode_store);
+
+static DEVICE_ATTR(fts_cover_mode, S_IRUGO | S_IWUSR,
+                   fts_cover_mode_show, fts_cover_mode_store);
+
+static DEVICE_ATTR(fts_charger_mode, S_IRUGO | S_IWUSR,
+                   fts_charger_mode_show, fts_charger_mode_store);
+
+static struct attribute *fts_touch_mode_attrs[] = {
+    &dev_attr_fts_glove_mode.attr,
+    &dev_attr_fts_cover_mode.attr,
+    &dev_attr_fts_charger_mode.attr,
+    NULL,
+};
+
+static struct attribute_group fts_touch_mode_group = {
+    .attrs = fts_touch_mode_attrs,
+};
+
+int fts_ex_mode_recovery(struct fts_ts_data *ts_data)
+{
+    /* update firmware feature settings. */
+    fts_update_feature_setting(ts_data);
+
+    if (ts_data->cover_mode) {
+        fts_ex_mode_switch(MODE_COVER, ENABLE);
+    }
+
+    if (ts_data->charger_mode) {
+        fts_ex_mode_switch(MODE_CHARGER, ENABLE);
+    }
+
+    return 0;
+}
+
+int fts_ex_mode_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    ts_data->glove_mode = DISABLE;
+    ts_data->cover_mode = DISABLE;
+    ts_data->charger_mode = DISABLE;
+
+    ret = sysfs_create_group(&ts_data->dev->kobj, &fts_touch_mode_group);
+    if (ret < 0) {
+        FTS_ERROR("create sysfs(ex_mode) fail");
+        sysfs_remove_group(&ts_data->dev->kobj, &fts_touch_mode_group);
+        return ret;
+    } else {
+        FTS_DEBUG("create sysfs(ex_mode) succeedfully");
+    }
+
+    return 0;
+}
+
+int fts_ex_mode_exit(struct fts_ts_data *ts_data)
+{
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_touch_mode_group);
+    return 0;
+}
diff --git a/ft3683u/focaltech_flash.c b/ft3683u/focaltech_flash.c
new file mode 100644
index 0000000..236a739
--- /dev/null
+++ b/ft3683u/focaltech_flash.c
@@ -0,0 +1,2080 @@
+/*
+ *
+ * FocalTech fts TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_flash.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+#include "focaltech_flash.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_FW_REQUEST_SUPPORT                      1
+#define FTS_FW_NAME                                 "focaltech_ts_fw"
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+u8 fw_file[] = {
+//#include FTS_UPGRADE_FW_FILE
+};
+
+u8 fw_file2[] = {
+//#include FTS_UPGRADE_FW2_FILE
+};
+
+u8 fw_file3[] = {
+//#include FTS_UPGRADE_FW3_FILE
+};
+
+struct upgrade_module module_list[] = {
+    {FTS_MODULE_ID, FTS_MODULE_NAME, fw_file, sizeof(fw_file)},
+    {FTS_MODULE2_ID, FTS_MODULE2_NAME, fw_file2, sizeof(fw_file2)},
+    {FTS_MODULE3_ID, FTS_MODULE3_NAME, fw_file3, sizeof(fw_file3)},
+};
+
+struct upgrade_func *upgrade_func_list[] = {
+    &upgrade_func_ft5008,
+};
+
+struct fts_upgrade *fwupgrade;
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+static bool fts_fwupg_check_state(
+    struct fts_upgrade *upg, enum FW_STATUS rstate);
+
+/************************************************************************
+* Name: fts_fwupg_get_boot_state
+* Brief: read boot id(rom/pram/bootloader), confirm boot environment
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+static int fts_fwupg_get_boot_state(
+    struct fts_upgrade *upg,
+    enum FW_STATUS *fw_sts)
+{
+    int ret = 0;
+    u8 cmd[4] = { 0 };
+    u32 cmd_len = 0;
+    u8 val[2] = { 0 };
+    struct ft_chip_t *ids = NULL;
+
+    FTS_INFO("**********read boot id**********");
+    if ((!upg) || (!upg->func) || (!upg->ts_data) || (!fw_sts)) {
+        FTS_ERROR("upg/func/ts_data/fw_sts is null");
+        return -EINVAL;
+    }
+
+    if (upg->func->hid_supported)
+        fts_hid2std();
+
+    cmd[0] = FTS_CMD_START1;
+    cmd[1] = FTS_CMD_START2;
+    if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)
+        cmd_len = 1;
+    else
+        cmd_len = 2;
+    ret = fts_write(cmd, cmd_len);
+    if (ret < 0) {
+        FTS_ERROR("write 55 cmd fail");
+        return ret;
+    }
+
+    msleep(FTS_CMD_START_DELAY);
+    cmd[0] = FTS_CMD_READ_ID;
+    cmd[1] = cmd[2] = cmd[3] = 0x00;
+    if (fts_data->ic_info.is_incell ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0))
+        cmd_len = FTS_CMD_READ_ID_LEN_INCELL;
+    else
+        cmd_len = FTS_CMD_READ_ID_LEN;
+    ret = fts_read(cmd, cmd_len, val, 2);
+    if (ret < 0) {
+        FTS_ERROR("write 90 cmd fail");
+        return ret;
+    }
+    FTS_INFO("read boot id:0x%02x%02x", val[0], val[1]);
+
+    ids = &upg->ts_data->ic_info.ids;
+    if ((val[0] == ids->rom_idh) && (val[1] == ids->rom_idl)) {
+        FTS_INFO("tp run in romboot");
+        *fw_sts = FTS_RUN_IN_ROM;
+    } else if ((val[0] == ids->pb_idh) && (val[1] == ids->pb_idl)) {
+        FTS_INFO("tp run in pramboot");
+        *fw_sts = FTS_RUN_IN_PRAM;
+    } else if ((val[0] == ids->bl_idh) && (val[1] == ids->bl_idl)) {
+        FTS_INFO("tp run in bootloader");
+        *fw_sts = FTS_RUN_IN_BOOTLOADER;
+    }
+
+    return 0;
+}
+
+static int fts_fwupg_reset_to_boot(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    u8 reg = FTS_REG_UPGRADE;
+
+    FTS_INFO("send 0xAA and 0x55 to FW, reset to boot environment");
+    if (upg && upg->func && upg->func->is_reset_register_BC) {
+        reg = FTS_REG_UPGRADE2;
+    }
+
+    ret = fts_write_reg(reg, FTS_UPGRADE_AA);
+    if (ret < 0) {
+        FTS_ERROR("write FC=0xAA fail");
+        return ret;
+    }
+    msleep(FTS_DELAY_UPGRADE_AA);
+
+    ret = fts_write_reg(reg, FTS_UPGRADE_55);
+    if (ret < 0) {
+        FTS_ERROR("write FC=0x55 fail");
+        return ret;
+    }
+
+    msleep(FTS_DELAY_UPGRADE_RESET);
+
+    return 0;
+}
+
+/************************************************************************
+* Name: fts_fwupg_reset_to_romboot
+* Brief: reset to romboot, to load pramboot
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+static int fts_fwupg_reset_to_romboot(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cmd = FTS_CMD_RESET;
+    enum FW_STATUS state = FTS_RUN_IN_ERROR;
+
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("pram/rom/bootloader reset cmd write fail");
+        return ret;
+    }
+    mdelay(10);
+
+    for (i = 0; i < FTS_UPGRADE_LOOP; i++) {
+        ret = fts_fwupg_get_boot_state(upg, &state);
+        if (FTS_RUN_IN_ROM == state)
+            break;
+        mdelay(5);
+    }
+    if (i >= FTS_UPGRADE_LOOP) {
+        FTS_ERROR("reset to romboot fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static u16 fts_crc16_calc_host(u8 *pbuf, u32 length)
+{
+    u16 ecc = 0;
+    u32 i = 0;
+    u32 j = 0;
+
+    for ( i = 0; i < length; i += 2 ) {
+        ecc ^= ((pbuf[i] << 8) | (pbuf[i + 1]));
+        for (j = 0; j < 16; j ++) {
+            if (ecc & 0x01)
+                ecc = (u16)((ecc >> 1) ^ AL2_FCS_COEF);
+            else
+                ecc >>= 1;
+        }
+    }
+
+    return ecc;
+}
+
+static u16 fts_pram_ecc_calc_host(u8 *pbuf, u32 length)
+{
+    return fts_crc16_calc_host(pbuf, length);
+}
+
+static int fts_pram_ecc_cal_algo(
+    struct fts_upgrade *upg,
+    u32 start_addr,
+    u32 ecc_length)
+{
+    int ret = 0;
+    int i = 0;
+    int ecc = 0;
+    u8 val[2] = { 0 };
+    u8 tmp = 0;
+    u8 cmd[FTS_ROMBOOT_CMD_ECC_NEW_LEN] = { 0 };
+
+    FTS_INFO("read out pramboot checksum");
+    if ((!upg) || (!upg->func)) {
+        FTS_ERROR("upg/func is null");
+        return -EINVAL;
+    }
+
+    cmd[0] = FTS_ROMBOOT_CMD_ECC;
+    cmd[1] = BYTE_OFF_16(start_addr);
+    cmd[2] = BYTE_OFF_8(start_addr);
+    cmd[3] = BYTE_OFF_0(start_addr);
+    cmd[4] = BYTE_OFF_16(ecc_length);
+    cmd[5] = BYTE_OFF_8(ecc_length);
+    cmd[6] = BYTE_OFF_0(ecc_length);
+    ret = fts_write(cmd, FTS_ROMBOOT_CMD_ECC_NEW_LEN);
+    if (ret < 0) {
+        FTS_ERROR("write pramboot ecc cal cmd fail");
+        return ret;
+    }
+
+    cmd[0] = FTS_ROMBOOT_CMD_ECC_FINISH;
+    for (i = 0; i < FTS_ECC_FINISH_TIMEOUT; i++) {
+        msleep(1);
+        ret = fts_read(cmd, 1, val, 1);
+        if (ret < 0) {
+            FTS_ERROR("ecc_finish read cmd fail");
+            return ret;
+        }
+        if (upg->func->new_return_value_from_ic ||
+            (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+            tmp = FTS_ROMBOOT_CMD_ECC_FINISH_OK_A5;
+        } else {
+            tmp = FTS_ROMBOOT_CMD_ECC_FINISH_OK_00;
+        }
+        if (tmp == val[0])
+            break;
+    }
+    if (i >= FTS_ECC_FINISH_TIMEOUT) {
+        FTS_ERROR("wait ecc finish fail");
+        return -EIO;
+    }
+
+    cmd[0] = FTS_ROMBOOT_CMD_ECC_READ;
+    ret = fts_read(cmd, 1, val, 2);
+    if (ret < 0) {
+        FTS_ERROR("read pramboot ecc fail");
+        return ret;
+    }
+
+    ecc = ((u16)(val[0] << 8) + val[1]) & 0x0000FFFF;
+    return ecc;
+}
+
+static int fts_pram_ecc_cal_xor(void)
+{
+    int ret = 0;
+    u8 reg_val = 0;
+
+    FTS_INFO("read out pramboot checksum");
+
+    ret = fts_read_reg(FTS_ROMBOOT_CMD_ECC, &reg_val);
+    if (ret < 0) {
+        FTS_ERROR("read pramboot ecc fail");
+        return ret;
+    }
+
+    return (int)reg_val;
+}
+
+static int fts_pram_ecc_cal(struct fts_upgrade *upg, u32 saddr, u32 len)
+{
+    if ((!upg) || (!upg->func)) {
+        FTS_ERROR("upg/func is null");
+        return -EINVAL;
+    }
+
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->pram_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        return fts_pram_ecc_cal_algo(upg, saddr, len);
+    } else {
+        return fts_pram_ecc_cal_xor();
+    }
+}
+
+static int fts_pram_write_buf(struct fts_upgrade *upg, u8 *buf, u32 len)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 j = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u32 packet_number;
+    u32 packet_len = 0;
+    u8 packet_buf[FTS_FLASH_PACKET_LENGTH + FTS_CMD_WRITE_LEN] = { 0 };
+    u8 ecc_tmp = 0;
+    int ecc_in_host = 0;
+    u32 cmdlen = 0;
+
+    FTS_INFO("write pramboot to pram");
+    if ((!upg) || (!upg->func) || !buf) {
+        FTS_ERROR("upg/func/buf is null");
+        return -EINVAL;
+    }
+
+    FTS_INFO("pramboot len=%d", len);
+    if ((len < PRAMBOOT_MIN_SIZE) || (len > PRAMBOOT_MAX_SIZE)) {
+        FTS_ERROR("pramboot length(%d) fail", len);
+        return -EINVAL;
+    }
+
+    packet_number = len / FTS_FLASH_PACKET_LENGTH;
+    remainder = len % FTS_FLASH_PACKET_LENGTH;
+    if (remainder > 0)
+        packet_number++;
+    packet_len = FTS_FLASH_PACKET_LENGTH;
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * FTS_FLASH_PACKET_LENGTH;
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            packet_buf[0] = FTS_ROMBOOT_CMD_SET_PRAM_ADDR;
+            packet_buf[1] = BYTE_OFF_16(offset);
+            packet_buf[2] = BYTE_OFF_8(offset);
+            packet_buf[3] = BYTE_OFF_0(offset);
+
+            ret = fts_write(packet_buf, FTS_ROMBOOT_CMD_SET_PRAM_ADDR_LEN);
+            if (ret < 0) {
+                FTS_ERROR("pramboot set write address(%d) fail", i);
+                return ret;
+            }
+
+            packet_buf[0] = FTS_ROMBOOT_CMD_WRITE;
+            cmdlen = 1;
+        } else {
+            packet_buf[0] = FTS_ROMBOOT_CMD_WRITE;
+            packet_buf[1] = BYTE_OFF_16(offset);
+            packet_buf[2] = BYTE_OFF_8(offset);
+            packet_buf[3] = BYTE_OFF_0(offset);
+
+            packet_buf[4] = BYTE_OFF_8(packet_len);
+            packet_buf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = 6;
+        }
+
+        for (j = 0; j < packet_len; j++) {
+            packet_buf[cmdlen + j] = buf[offset + j];
+            if (ECC_CHECK_MODE_XOR == upg->func->pram_ecc_check_mode) {
+                ecc_tmp ^= packet_buf[cmdlen + j];
+            }
+        }
+
+        ret = fts_write(packet_buf, packet_len + cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("pramboot write data(%d) fail", i);
+            return ret;
+        }
+    }
+
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->pram_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc_in_host = (int)fts_pram_ecc_calc_host(buf, len);
+    } else {
+        ecc_in_host = (int)ecc_tmp;
+    }
+
+    return ecc_in_host;
+}
+
+static int fts_pram_start(void)
+{
+    u8 cmd = FTS_ROMBOOT_CMD_START_APP;
+    int ret = 0;
+
+    FTS_INFO("remap to start pramboot");
+
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("write start pram cmd fail");
+        return ret;
+    }
+    msleep(FTS_DELAY_PRAMBOOT_START);
+
+    return 0;
+}
+
+static int fts_pram_write_remap(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    int ecc_in_host = 0;
+    int ecc_in_tp = 0;
+    u8 *pb_buf = NULL;
+    u32 pb_len = 0;
+
+    FTS_INFO("write pram and remap");
+    if (!upg || !upg->func || !upg->func->pramboot) {
+        FTS_ERROR("upg/func/pramboot is null");
+        return -EINVAL;
+    }
+
+    if (upg->func->pb_length < FTS_MIN_LEN) {
+        FTS_ERROR("pramboot length(%d) fail", upg->func->pb_length);
+        return -EINVAL;
+    }
+
+    pb_buf = upg->func->pramboot;
+    pb_len = upg->func->pb_length;
+
+    /* write pramboot to pram */
+    ecc_in_host = fts_pram_write_buf(upg, pb_buf, pb_len);
+    if (ecc_in_host < 0) {
+        FTS_ERROR( "write pramboot fail");
+        return ecc_in_host;
+    }
+
+    /* read out checksum */
+    ecc_in_tp = fts_pram_ecc_cal(upg, 0, pb_len);
+    if (ecc_in_tp < 0) {
+        FTS_ERROR( "read pramboot ecc fail");
+        return ecc_in_tp;
+    }
+
+    FTS_INFO("pram ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host);
+    /*  pramboot checksum != fw checksum, upgrade fail */
+    if (ecc_in_host != ecc_in_tp) {
+        FTS_ERROR("pramboot ecc check fail");
+        return -EIO;
+    }
+
+    /*start pram*/
+    ret = fts_pram_start();
+    if (ret < 0) {
+        FTS_ERROR("pram start fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_pram_init(void)
+{
+    int ret = 0;
+    u8 reg_val = 0;
+    u8 wbuf[3] = { 0 };
+
+    FTS_INFO("pramboot initialization");
+
+    /* read flash ID */
+    wbuf[0] = FTS_CMD_FLASH_TYPE;
+    ret = fts_read(wbuf, 1, &reg_val, 1);
+    if (ret < 0) {
+        FTS_ERROR("read flash type fail");
+        return ret;
+    }
+
+    /* set flash clk */
+    wbuf[0] = FTS_CMD_FLASH_TYPE;
+    wbuf[1] = reg_val;
+    wbuf[2] = 0x00;
+    ret = fts_write(wbuf, 3);
+    if (ret < 0) {
+        FTS_ERROR("write flash type fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_pram_write_init(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool state = 0;
+    enum FW_STATUS status = FTS_RUN_IN_ERROR;
+
+    FTS_INFO("**********pram write and init**********");
+    if ((NULL == upg) || (NULL == upg->func)) {
+        FTS_ERROR("upgrade/func is null");
+        return -EINVAL;
+    }
+
+    if (!upg->func->pramboot_supported) {
+        FTS_ERROR("ic not support pram");
+        return -EINVAL;
+    }
+
+    FTS_DEBUG("check whether tp is in romboot or not ");
+    /* need reset to romboot when non-romboot state */
+    ret = fts_fwupg_get_boot_state(upg, &status);
+    if (status != FTS_RUN_IN_ROM) {
+        if (FTS_RUN_IN_PRAM == status) {
+            FTS_INFO("tp is in pramboot, need send reset cmd before upgrade");
+            ret = fts_pram_init();
+            if (ret < 0) {
+                FTS_ERROR("pramboot(before) init fail");
+                return ret;
+            }
+        }
+
+        FTS_INFO("tp isn't in romboot, need send reset to romboot");
+        ret = fts_fwupg_reset_to_romboot(upg);
+        if (ret < 0) {
+            FTS_ERROR("reset to romboot fail");
+            return ret;
+        }
+    }
+
+    /* check the length of the pramboot */
+    ret = fts_pram_write_remap(upg);
+    if (ret < 0) {
+        FTS_ERROR("pram write fail, ret=%d", ret);
+        return ret;
+    }
+
+    FTS_DEBUG("after write pramboot, confirm run in pramboot");
+    state = fts_fwupg_check_state(upg, FTS_RUN_IN_PRAM);
+    if (!state) {
+        FTS_ERROR("not in pramboot");
+        return -EIO;
+    }
+
+    ret = fts_pram_init();
+    if (ret < 0) {
+        FTS_ERROR("pramboot init fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static bool fts_fwupg_check_fw_valid(void)
+{
+    int ret = 0;
+
+    ret = fts_wait_tp_to_valid();
+    if (ret < 0) {
+        FTS_INFO("tp fw invaild");
+        return false;
+    }
+
+    FTS_INFO("tp fw vaild");
+    return true;
+}
+
+/************************************************************************
+* Name: fts_fwupg_check_state
+* Brief: confirm tp run in which mode: romboot/pramboot/bootloader
+* Input:
+* Output:
+* Return: return true if state is match, otherwise return false
+***********************************************************************/
+static bool fts_fwupg_check_state(
+    struct fts_upgrade *upg, enum FW_STATUS rstate)
+{
+    int ret = 0;
+    int i = 0;
+    enum FW_STATUS cstate = FTS_RUN_IN_ERROR;
+
+    for (i = 0; i < FTS_UPGRADE_LOOP; i++) {
+        ret = fts_fwupg_get_boot_state(upg, &cstate);
+        /* FTS_DEBUG("fw state=%d, retries=%d", cstate, i); */
+        if (cstate == rstate)
+            return true;
+        msleep(FTS_DELAY_READ_ID);
+    }
+
+    return false;
+}
+
+/************************************************************************
+* Name: fts_fwupg_reset_in_boot
+* Brief: RST CMD(07), reset to romboot(bootloader) in boot environment
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+int fts_fwupg_reset_in_boot(void)
+{
+    int ret = 0;
+    u8 cmd = FTS_CMD_RESET;
+
+    FTS_INFO("reset in boot environment");
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("pram/rom/bootloader reset cmd write fail");
+        return ret;
+    }
+
+    msleep(FTS_DELAY_UPGRADE_RESET);
+    return 0;
+}
+
+/************************************************************************
+* Name: fts_fwupg_enter_into_boot
+* Brief: enter into boot environment, ready for upgrade
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+int fts_fwupg_enter_into_boot(void)
+{
+    int ret = 0;
+    bool fwvalid = false;
+    bool state = false;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO("***********enter into pramboot/bootloader***********");
+    if ((!upg) || (NULL == upg->func)) {
+        FTS_ERROR("upgrade/func is null");
+        return -EINVAL;
+    }
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if (fwvalid) {
+        ret = fts_fwupg_reset_to_boot(upg);
+        if (ret < 0) {
+            FTS_ERROR("enter into romboot/bootloader fail");
+            return ret;
+        }
+    } else if (upg->func->read_boot_id_need_reset) {
+        ret = fts_fwupg_reset_in_boot();
+        if (ret < 0) {
+            FTS_ERROR("reset before read boot id when fw invalid fail");
+            return ret;
+        }
+    }
+
+    if (upg->func->pramboot_supported) {
+        FTS_INFO("pram supported, write pramboot and init");
+        /* pramboot */
+        if (upg->func->write_pramboot_private)
+            ret = upg->func->write_pramboot_private();
+        else
+            ret = fts_pram_write_init(upg);
+        if (ret < 0) {
+            FTS_ERROR("pram write_init fail");
+            return ret;
+        }
+    } else {
+        FTS_DEBUG("pram not supported, confirm in bootloader");
+        /* bootloader */
+        state = fts_fwupg_check_state(upg, FTS_RUN_IN_BOOTLOADER);
+        if (!state) {
+            FTS_ERROR("fw not in bootloader, fail");
+            return -EIO;
+        }
+    }
+
+    return 0;
+}
+
+/************************************************************************
+ * Name: fts_fwupg_check_flash_status
+ * Brief: read status from tp
+ * Input: flash_status: correct value from tp
+ *        retries: read retry times
+ *        retries_delay: retry delay
+ * Output:
+ * Return: return true if flash status check pass, otherwise return false
+***********************************************************************/
+static bool fts_fwupg_check_flash_status(
+    u16 flash_status,
+    int retries,
+    int retries_delay)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cmd = 0;
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    u16 read_status = 0;
+
+    for (i = 0; i < retries; i++) {
+        cmd = FTS_CMD_FLASH_STATUS;
+        ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN);
+        read_status = (((u16)val[0]) << 8) + val[1];
+        if (flash_status == read_status) {
+            /* FTS_DEBUG("[UPGRADE]flash status ok"); */
+            return true;
+        }
+        /* FTS_DEBUG("flash status fail,ok:%04x read:%04x, retries:%d", flash_status, read_status, i); */
+        msleep(retries_delay);
+    }
+
+    return false;
+}
+
+/************************************************************************
+ * Name: fts_fwupg_erase
+ * Brief: erase flash area
+ * Input: delay - delay after erase
+ * Output:
+ * Return: return 0 if success, otherwise return error code
+ ***********************************************************************/
+int fts_fwupg_erase(u32 delay)
+{
+    int ret = 0;
+    u8 cmd = 0;
+    bool flag = false;
+
+    FTS_INFO("**********erase now**********");
+
+    /*send to erase flash*/
+    cmd = FTS_CMD_ERASE_APP;
+    ret = fts_write(&cmd, 1);
+    if (ret < 0) {
+        FTS_ERROR("erase cmd fail");
+        return ret;
+    }
+    msleep(delay);
+
+    /* read status 0xF0AA: success */
+    flag = fts_fwupg_check_flash_status(FTS_CMD_FLASH_STATUS_ERASE_OK,
+                                        FTS_RETRIES_REASE,
+                                        FTS_RETRIES_DELAY_REASE);
+    if (!flag) {
+        FTS_ERROR("ecc flash status check fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/************************************************************************
+ * Name: fts_fwupg_ecc_cal
+ * Brief: calculate and get ecc from tp
+ * Input: saddr - start address need calculate ecc
+ *        len - length need calculate ecc
+ * Output:
+ * Return: return data ecc of tp if success, otherwise return error code
+ ***********************************************************************/
+int fts_fwupg_ecc_cal(u32 saddr, u32 len)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 cmdlen = FTS_CMD_ECC_CAL_LEN;
+    u8 wbuf[FTS_CMD_ECC_CAL_LEN] = { 0 };
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    int ecc = 0;
+    int ecc_len = 0;
+    u32 packet_num = 0;
+    u32 packet_len = 0;
+    u32 remainder = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    bool bflag = false;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO( "**********read out checksum**********");
+    if ((NULL == upg) || (NULL == upg->func)) {
+        FTS_ERROR("upgrade/func is null");
+        return -EINVAL;
+    }
+
+    /* check sum init */
+    wbuf[0] = FTS_CMD_ECC_INIT;
+    ret = fts_write(wbuf, 1);
+    if (ret < 0) {
+        FTS_ERROR("ecc init cmd write fail");
+        return ret;
+    }
+
+    if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) {
+        packet_num = 1;
+        remainder = 0;
+        packet_len = len;
+    } else {
+        packet_num = len / FTS_MAX_LEN_ECC_CALC;
+        remainder = len % FTS_MAX_LEN_ECC_CALC;
+        if (remainder)
+            packet_num++;
+        packet_len = FTS_MAX_LEN_ECC_CALC;
+    }
+    FTS_INFO("ecc calc num:%d, remainder:%d", packet_num, remainder);
+
+    /* send commond to start checksum */
+    wbuf[0] = FTS_CMD_ECC_CAL;
+    for (i = 0; i < packet_num; i++) {
+        offset = FTS_MAX_LEN_ECC_CALC * i;
+        addr = saddr + offset;
+        wbuf[1] = BYTE_OFF_16(addr);
+        wbuf[2] = BYTE_OFF_8(addr);
+        wbuf[3] = BYTE_OFF_0(addr);
+
+        if ((upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+            wbuf[4] = BYTE_OFF_16(packet_len);
+            wbuf[5] = BYTE_OFF_8(packet_len);
+            wbuf[6] = BYTE_OFF_0(packet_len);
+            cmdlen = FTS_CMD_ECC_CAL_LEN;
+        } else {
+            if ((i == (packet_num - 1)) && remainder)
+                packet_len = remainder;
+            wbuf[4] = BYTE_OFF_8(packet_len);
+            wbuf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = FTS_CMD_ECC_CAL_LEN - 1;
+        }
+
+        FTS_DEBUG("ecc calc startaddr:0x%04x, len:%d", addr, packet_len);
+        ret = fts_write(wbuf, cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("ecc calc cmd write fail");
+            return ret;
+        }
+
+        msleep(packet_len / 256);
+
+        /* read status if check sum is finished */
+        bflag = fts_fwupg_check_flash_status(FTS_CMD_FLASH_STATUS_ECC_OK,
+                                             FTS_RETRIES_ECC_CAL,
+                                             FTS_RETRIES_DELAY_ECC_CAL);
+        if (!bflag) {
+            FTS_ERROR("ecc flash status read fail");
+            return -EIO;
+        }
+    }
+
+    ecc_len = 1;
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->fw_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc_len = 2;
+    }
+
+    /* read out check sum */
+    wbuf[0] = FTS_CMD_ECC_READ;
+    ret = fts_read(wbuf, 1, val, ecc_len);
+    if (ret < 0) {
+        FTS_ERROR( "ecc read cmd write fail");
+        return ret;
+    }
+
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->fw_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc = (int)((u16)(val[0] << 8) + val[1]);
+    } else {
+        ecc = (int)val[0];
+    }
+
+    return ecc;
+}
+
+/************************************************************************
+ * Name: fts_flash_write_buf
+ * Brief: write buf data to flash address
+ * Input: saddr - start address data write to flash
+ *        buf - data buffer
+ *        len - data length
+ *        delay - delay after write
+ * Output:
+ * Return: return data ecc of host if success, otherwise return error code
+ ***********************************************************************/
+int fts_flash_write_buf(
+    u32 saddr,
+    u8 *buf,
+    u32 len,
+    u32 delay)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 j = 0;
+    u32 packet_number = 0;
+    u32 packet_len = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u32 cmdlen = 0;
+    u8 packet_buf[FTS_FLASH_PACKET_LENGTH + FTS_CMD_WRITE_LEN] = { 0 };
+    u8 ecc_tmp = 0;
+    int ecc_in_host = 0;
+    u8 cmd = 0;
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    u16 read_status = 0;
+    u16 wr_ok = 0;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO( "**********write data to flash**********");
+    if ((!upg) || (!upg->func || !buf || !len)) {
+        FTS_ERROR("upgrade/func/buf/len is invalid");
+        return -EINVAL;
+    }
+
+    FTS_INFO("data buf start addr=0x%x, len=0x%x", saddr, len);
+    packet_number = len / FTS_FLASH_PACKET_LENGTH;
+    remainder = len % FTS_FLASH_PACKET_LENGTH;
+    if (remainder > 0)
+        packet_number++;
+    packet_len = FTS_FLASH_PACKET_LENGTH;
+    FTS_INFO("write data, num:%d remainder:%d", packet_number, remainder);
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * FTS_FLASH_PACKET_LENGTH;
+        addr = saddr + offset;
+
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            packet_buf[0] = FTS_CMD_SET_WFLASH_ADDR;
+            packet_buf[1] = BYTE_OFF_16(addr);
+            packet_buf[2] = BYTE_OFF_8(addr);
+            packet_buf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(packet_buf, FTS_LEN_SET_ADDR);
+            if (ret < 0) {
+                FTS_ERROR("set flash address fail");
+                return ret;
+            }
+
+            packet_buf[0] = FTS_CMD_WRITE;
+            cmdlen = 1;
+        } else {
+            packet_buf[0] = FTS_CMD_WRITE;
+            packet_buf[1] = BYTE_OFF_16(addr);
+            packet_buf[2] = BYTE_OFF_8(addr);
+            packet_buf[3] = BYTE_OFF_0(addr);
+            packet_buf[4] = BYTE_OFF_8(packet_len);
+            packet_buf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = 6;
+        }
+
+        for (j = 0; j < packet_len; j++) {
+            packet_buf[cmdlen + j] = buf[offset + j];
+            ecc_tmp ^= packet_buf[cmdlen + j];
+        }
+
+        ret = fts_write(packet_buf, packet_len + cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("app write fail");
+            return ret;
+        }
+        mdelay(delay);
+
+        /* read status */
+        wr_ok = FTS_CMD_FLASH_STATUS_WRITE_OK + addr / packet_len;
+        for (j = 0; j < FTS_RETRIES_WRITE; j++) {
+            cmd = FTS_CMD_FLASH_STATUS;
+            ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN);
+            read_status = (((u16)val[0]) << 8) + val[1];
+            /*  FTS_INFO("%x %x", wr_ok, read_status); */
+            if (wr_ok == read_status) {
+                break;
+            }
+            mdelay(FTS_RETRIES_DELAY_WRITE);
+        }
+    }
+
+    ecc_in_host = (int)ecc_tmp;
+    if ((ECC_CHECK_MODE_CRC16 == upg->func->fw_ecc_check_mode) ||
+        (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) {
+        ecc_in_host = (int)fts_crc16_calc_host(buf, len);
+    }
+
+    return ecc_in_host;
+}
+
+/************************************************************************
+ * Name: fts_flash_read_buf
+ * Brief: read data from flash
+ * Input: saddr - start address data write to flash
+ *        buf - buffer to store data read from flash
+ *        len - read length
+ * Output:
+ * Return: return 0 if success, otherwise return error code
+ *
+ * Warning: can't call this function directly, need call in boot environment
+ ***********************************************************************/
+int fts_flash_read_buf(u32 saddr, u8 *buf, u32 len)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 packet_number = 0;
+    u32 packet_len = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u8 wbuf[FTS_CMD_READ_LEN_SPI] = { 0 };
+    struct fts_upgrade *upg = fwupgrade;
+
+    if (!upg || !buf || !len) {
+        FTS_ERROR("upgrade/buf is NULL or len is 0");
+        return -EINVAL;
+    }
+
+    packet_number = len / FTS_FLASH_PACKET_LENGTH;
+    remainder = len % FTS_FLASH_PACKET_LENGTH;
+    if (remainder > 0) {
+        packet_number++;
+    }
+    packet_len = FTS_FLASH_PACKET_LENGTH;
+    FTS_INFO("read packet_number:%d, remainder:%d", packet_number, remainder);
+
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * FTS_FLASH_PACKET_LENGTH;
+        addr = saddr + offset;
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (upg->ts_data->bus_type == FTS_BUS_TYPE_I2C) {
+            wbuf[0] = FTS_CMD_READ;
+            wbuf[1] = BYTE_OFF_16(addr);
+            wbuf[2] = BYTE_OFF_8(addr);
+            wbuf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(wbuf, FTS_CMD_READ_LEN);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader write 03 command fail");
+                return ret;
+            }
+
+            msleep(FTS_CMD_READ_DELAY); /* must wait, otherwise read wrong data */
+            ret = fts_read(NULL, 0, buf + offset, packet_len);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader read 03 command fail");
+                return ret;
+            }
+        } else if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            wbuf[0] = FTS_CMD_SET_RFLASH_ADDR;
+            wbuf[1] = BYTE_OFF_16(addr);
+            wbuf[2] = BYTE_OFF_8(addr);
+            wbuf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(wbuf, FTS_LEN_SET_ADDR);
+            if (ret < 0) {
+                FTS_ERROR("set flash address fail");
+                return ret;
+            }
+
+            msleep(FTS_CMD_READ_DELAY);
+            wbuf[0] = FTS_CMD_READ;
+            ret = fts_read(wbuf, 1, buf + offset, packet_len);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader read 03(SPI_V2) command fail");
+                return ret;
+            }
+        } else if (upg->ts_data->bus_type == FTS_BUS_TYPE_SPI) {
+            wbuf[0] = FTS_CMD_READ;
+            wbuf[1] = BYTE_OFF_16(addr);
+            wbuf[2] = BYTE_OFF_8(addr);
+            wbuf[3] = BYTE_OFF_0(addr);
+            wbuf[4] = BYTE_OFF_8(packet_len);
+            wbuf[5] = BYTE_OFF_0(packet_len);
+            ret = fts_read(wbuf, FTS_CMD_READ_LEN_SPI, \
+                           buf + offset, packet_len);
+            if (ret < 0) {
+                FTS_ERROR("pram/bootloader read 03(SPI) command fail");
+                return ret;
+            }
+        }
+    }
+
+    return 0;
+}
+
+/************************************************************************
+ * Name: fts_flash_read
+ * Brief:
+ * Input:  addr  - address of flash
+ *         len   - length of read
+ * Output: buf   - data read from flash
+ * Return: return 0 if success, otherwise return error code
+ ***********************************************************************/
+static int fts_flash_read(u32 addr, u8 *buf, u32 len)
+{
+    int ret = 0;
+
+    FTS_INFO("***********read flash***********");
+    if ((NULL == buf) || (0 == len)) {
+        FTS_ERROR("buf is NULL or len is 0");
+        return -EINVAL;
+    }
+
+    ret = fts_fwupg_enter_into_boot();
+    if (ret < 0) {
+        FTS_ERROR("enter into pramboot/bootloader fail");
+        goto read_flash_err;
+    }
+
+    ret = fts_flash_read_buf(addr, buf, len);
+    if (ret < 0) {
+        FTS_ERROR("read flash fail");
+        goto read_flash_err;
+    }
+
+read_flash_err:
+    /* reset to normal boot */
+    ret = fts_fwupg_reset_in_boot();
+    if (ret < 0) {
+        FTS_ERROR("reset to normal boot fail");
+    }
+    return ret;
+}
+
+int fts_upgrade_bin(char *fw_name, bool force)
+{
+    int ret = 0;
+    u32 fw_file_len = 0;
+    u8 *fw_file_buf = NULL;
+    const struct firmware *fw = NULL;
+    struct fts_upgrade *upg = fwupgrade;
+
+    FTS_INFO("start upgrade with fw bin");
+    if ((!upg) || (!upg->func) || !upg->ts_data) {
+        FTS_ERROR("upgrade/func/ts_data is null");
+        return -EINVAL;
+    }
+
+    upg->ts_data->fw_loading = 1;
+    fts_irq_disable();
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    ret = request_firmware(&fw, fw_name, upg->ts_data->dev);
+    if (ret) {
+        FTS_ERROR("read fw bin file(%s) fail, len:%d", fw_name, ret);
+        goto err_bin;
+    }
+
+    fw_file_len = (u32)fw->size;
+    fw_file_buf = (u8 *)fw->data;
+    FTS_INFO("request fw succeeds, file len:%d", fw_file_len);
+    if (force) {
+        if (upg->func->force_upgrade) {
+            ret = upg->func->force_upgrade(fw_file_buf, fw_file_len);
+        } else {
+            FTS_INFO("force_upgrade function is null, no upgrade");
+            goto err_bin;
+        }
+    } else {
+#if FTS_AUTO_LIC_UPGRADE_EN
+        if (upg->func->lic_upgrade) {
+            ret = upg->func->lic_upgrade(fw_file_buf, fw_file_len);
+        } else {
+            FTS_INFO("lic_upgrade function is null, no upgrade");
+        }
+#endif
+        if (upg->func->upgrade) {
+            ret = upg->func->upgrade(fw_file_buf, fw_file_len);
+        } else {
+            FTS_INFO("upgrade function is null, no upgrade");
+        }
+    }
+
+    if (ret < 0) {
+        FTS_ERROR("upgrade fw bin failed");
+        fts_fwupg_reset_in_boot();
+        goto err_bin;
+    }
+
+    FTS_INFO("upgrade fw bin success");
+    ret = 0;
+
+err_bin:
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    /* Update firmware feature settings after flashing firmware. */
+    fts_update_feature_setting(upg->ts_data);
+
+    fts_irq_enable();
+    upg->ts_data->fw_loading = 0;
+
+    if (fw != NULL) {
+        release_firmware(fw);
+        fw = NULL;
+    }
+    return ret;
+}
+
+int fts_enter_test_environment(bool test_state)
+{
+    return 0;
+}
+#if FTS_AUTO_LIC_UPGRADE_EN
+static int fts_lic_get_vid_in_tp(u16 *vid)
+{
+    int ret = 0;
+    u8 val[2] = { 0 };
+
+    if (NULL == vid) {
+        FTS_ERROR("vid is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_VENDOR_ID, &val[0]);
+    if (fts_data->ic_info.is_incell)
+        ret = fts_read_reg(FTS_REG_MODULE_ID, &val[1]);
+    if (ret < 0) {
+        FTS_ERROR("read vid from tp fail");
+        return ret;
+    }
+
+    *vid = *(u16 *)val;
+    return 0;
+}
+
+static int fts_lic_get_vid_in_host(struct fts_upgrade *upg, u16 *vid)
+{
+    u8 val[2] = { 0 };
+    u8 *licbuf = NULL;
+    u32 conf_saddr = 0;
+
+    if (!upg || !upg->func || !upg->lic || !vid) {
+        FTS_ERROR("upgrade/func/get_hlic_ver/lic/vid is null");
+        return -EINVAL;
+    }
+
+    if (upg->lic_length < FTS_MAX_LEN_SECTOR) {
+        FTS_ERROR("lic length(%x) fail", upg->lic_length);
+        return -EINVAL;
+    }
+
+    licbuf  = upg->lic;
+    conf_saddr = upg->func->fwcfgoff;
+    val[0] = licbuf[conf_saddr + FTS_CONIFG_VENDORID_OFF];
+    if (fts_data->ic_info.is_incell)
+        val[1] = licbuf[conf_saddr + FTS_CONIFG_MODULEID_OFF];
+
+    *vid = *(u16 *)val;
+    return 0;
+}
+
+static int fts_lic_get_ver_in_tp(u8 *ver)
+{
+    int ret = 0;
+
+    if (NULL == ver) {
+        FTS_ERROR("ver is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_LIC_VER, ver);
+    if (ret < 0) {
+        FTS_ERROR("read lcd initcode ver from tp fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_lic_get_ver_in_host(struct fts_upgrade *upg, u8 *ver)
+{
+    int ret = 0;
+
+    if (!upg || !upg->func || !upg->func->get_hlic_ver || !upg->lic) {
+        FTS_ERROR("upgrade/func/get_hlic_ver/lic is null");
+        return -EINVAL;
+    }
+
+    ret = upg->func->get_hlic_ver(upg->lic);
+    if (ret < 0) {
+        FTS_ERROR("get host lcd initial code version fail");
+        return ret;
+    }
+
+    *ver = (u8)ret;
+    return ret;
+}
+
+static bool fts_lic_need_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    u8 initcode_ver_in_tp = 0;
+    u8 initcode_ver_in_host = 0;
+    u16 vid_in_tp = 0;
+    u16 vid_in_host = 0;
+    bool fwvalid = false;
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if ( !fwvalid) {
+        FTS_INFO("fw is invalid, no upgrade lcd init code");
+        return false;
+    }
+
+    ret = fts_lic_get_vid_in_host(upg, &vid_in_host);
+    if (ret < 0) {
+        FTS_ERROR("vendor id in host invalid");
+        return false;
+    }
+
+    ret = fts_lic_get_vid_in_tp(&vid_in_tp);
+    if (ret < 0) {
+        FTS_ERROR("vendor id in tp invalid");
+        return false;
+    }
+
+    FTS_DEBUG("vid in tp:0x%04x, host:0x%04x", vid_in_tp, vid_in_host);
+    if (vid_in_tp != vid_in_host) {
+        FTS_INFO("vendor id in tp&host are different, no upgrade lic");
+        return false;
+    }
+
+    ret = fts_lic_get_ver_in_host(upg, &initcode_ver_in_host);
+    if (ret < 0) {
+        FTS_ERROR("init code in host invalid");
+        return false;
+    }
+
+    ret = fts_lic_get_ver_in_tp(&initcode_ver_in_tp);
+    if (ret < 0) {
+        FTS_ERROR("read reg0xE4 fail");
+        return false;
+    }
+
+    FTS_DEBUG("lcd initial code version in tp:%x, host:%x",
+              initcode_ver_in_tp, initcode_ver_in_host);
+    if (0xA5 == initcode_ver_in_tp) {
+        FTS_INFO("lcd init code ver is 0xA5, don't upgade init code");
+        return false;
+    } else if (0xFF == initcode_ver_in_tp) {
+        FTS_DEBUG("lcd init code in tp is invalid, need upgrade init code");
+        return true;
+    } else if (initcode_ver_in_tp < initcode_ver_in_host)
+        return true;
+    else
+        return false;
+}
+
+static int fts_lic_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool hlic_upgrade = false;
+    int upgrade_count = 0;
+    u8 ver = 0;
+
+    FTS_INFO("lcd initial code auto upgrade function");
+    if ((!upg) || (!upg->func) || (!upg->func->lic_upgrade)) {
+        FTS_ERROR("lcd upgrade function is null");
+        return -EINVAL;
+    }
+
+    hlic_upgrade = fts_lic_need_upgrade(upg);
+    FTS_INFO("lcd init code upgrade flag:%d", hlic_upgrade);
+    if (hlic_upgrade) {
+        FTS_INFO("lcd initial code need upgrade, upgrade begin...");
+        do {
+            FTS_INFO("lcd initial code upgrade times:%d", upgrade_count);
+            upgrade_count++;
+
+            ret = upg->func->lic_upgrade(upg->lic, upg->lic_length);
+            if (ret < 0) {
+                fts_fwupg_reset_in_boot();
+            } else {
+                fts_lic_get_ver_in_tp(&ver);
+                FTS_INFO("success upgrade to lcd initcode ver:%02x", ver);
+                break;
+            }
+        } while (upgrade_count < 2);
+    } else {
+        FTS_INFO("lcd initial code don't need upgrade");
+    }
+
+    return ret;
+}
+#endif /* FTS_AUTO_LIC_UPGRADE_EN */
+
+
+static int fts_param_get_ver_in_tp(u8 *ver)
+{
+    int ret = 0;
+
+    if (NULL == ver) {
+        FTS_ERROR("ver is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_IDE_PARA_VER_ID, ver);
+    if (ret < 0) {
+        FTS_ERROR("read fw param ver from tp fail");
+        return ret;
+    }
+
+    if ((0x00 == *ver) || (0xFF == *ver)) {
+        FTS_INFO("param version in tp invalid");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int fts_param_get_ver_in_host(struct fts_upgrade *upg, u8 *ver)
+{
+    if ((!upg) || (!upg->func) || (!upg->fw) || (!ver)) {
+        FTS_ERROR("fts_data/upgrade/func/fw/ver is NULL");
+        return -EINVAL;
+    }
+
+    if (upg->fw_length < upg->func->paramcfgveroff) {
+        FTS_ERROR("fw len(%x) < paramcfg ver offset(%x)",
+                  upg->fw_length, upg->func->paramcfgveroff);
+        return -EINVAL;
+    }
+
+    FTS_INFO("fw paramcfg version offset:%x", upg->func->paramcfgveroff);
+    *ver = upg->fw[upg->func->paramcfgveroff];
+
+    if ((0x00 == *ver) || (0xFF == *ver)) {
+        FTS_INFO("param version in host invalid");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * return: < 0 : error
+ *         == 0: no ide
+ *         == 1: ide
+ */
+static int fts_param_ide_in_host(struct fts_upgrade *upg)
+{
+    u32 off = 0;
+
+    if ((!upg) || (!upg->func) || (!upg->fw)) {
+        FTS_ERROR("fts_data/upgrade/func/fw is NULL");
+        return -EINVAL;
+    }
+
+    if (upg->fw_length < upg->func->paramcfgoff + FTS_FW_IDE_SIG_LEN) {
+        FTS_INFO("fw len(%x) < paramcfg offset(%x), no IDE",
+                 upg->fw_length, upg->func->paramcfgoff + FTS_FW_IDE_SIG_LEN);
+        return 0;
+    }
+
+    off = upg->func->paramcfgoff;
+    if (0 == memcmp(&upg->fw[off], FTS_FW_IDE_SIG, FTS_FW_IDE_SIG_LEN)) {
+        FTS_INFO("fw in host is IDE version");
+        return 1;
+    }
+
+    FTS_INFO("fw in host isn't IDE version");
+    return 0;
+}
+
+/*
+ * return: < 0 : error
+ *         0   : no ide
+ *         1   : ide
+ */
+static int fts_param_ide_in_tp(u8 *val)
+{
+    int ret = 0;
+
+    ret = fts_read_reg(FTS_REG_IDE_PARA_STATUS, val);
+    if (ret < 0) {
+        FTS_ERROR("read IDE PARAM STATUS in tp fail");
+        return ret;
+    }
+
+    if ((*val != 0xFF) && ((*val & 0x80) == 0x80)) {
+        FTS_INFO("fw in tp is IDE version");
+        return 1;
+    }
+
+    FTS_INFO("fw in tp isn't IDE version");
+    return 0;
+}
+
+/************************************************************************
+ * fts_param_need_upgrade - check fw paramcfg need upgrade or not
+ *
+ * Return:  < 0 : error if paramcfg need upgrade
+ *          0   : no need upgrade
+ *          1   : need upgrade app + param
+ *          2   : need upgrade param
+ ***********************************************************************/
+static int fts_param_need_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    u8 val = 0;
+    int ide_in_host = 0;
+    int ide_in_tp = 0;
+    u8 ver_in_host = 0;
+    u8 ver_in_tp = 0;
+    bool fwvalid = false;
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if ( !fwvalid) {
+        FTS_INFO("fw is invalid, upgrade app+param");
+        return 1;
+    }
+
+    ide_in_host = fts_param_ide_in_host(upg);
+    if (ide_in_host < 0) {
+        FTS_INFO("fts_param_ide_in_host fail");
+        return ide_in_host;
+    }
+
+    ide_in_tp = fts_param_ide_in_tp(&val);
+    if (ide_in_tp < 0) {
+        FTS_INFO("fts_param_ide_in_tp fail");
+        return ide_in_tp;
+    }
+
+    if ((0 == ide_in_host) && (0 == ide_in_tp)) {
+        FTS_INFO("fw in host&tp are both no ide");
+        return 0;
+    } else if (ide_in_host != ide_in_tp) {
+        FTS_INFO("fw in host&tp not equal, need upgrade app+param");
+        return 1;
+    } else if ((1 == ide_in_host) && (1 == ide_in_tp)) {
+        FTS_INFO("fw in host&tp are both ide");
+        if ((val & 0x7F) != 0x00) {
+            FTS_INFO("param invalid, need upgrade param");
+            return 2;
+        }
+
+        ret = fts_param_get_ver_in_host(upg, &ver_in_host);
+        if (ret < 0) {
+            FTS_ERROR("param version in host invalid");
+            return ret;
+        }
+
+        ret = fts_param_get_ver_in_tp(&ver_in_tp);
+        if (ret < 0) {
+            FTS_ERROR("get IDE param ver in tp fail");
+            return ret;
+        }
+
+        FTS_INFO("fw paramcfg version in tp:%x, host:%x",
+                 ver_in_tp, ver_in_host);
+        if (ver_in_tp != ver_in_host) {
+            return 2;
+        }
+    }
+
+    return 0;
+}
+
+static int fts_fwupg_get_ver_in_tp(u8 *ver)
+{
+    int ret = 0;
+
+    if (NULL == ver) {
+        FTS_ERROR("ver is NULL");
+        return -EINVAL;
+    }
+
+    ret = fts_read_reg(FTS_REG_FW_MAJOR_VER, ver);
+    if (ret < 0) {
+        FTS_ERROR("read fw major ver from tp fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int fts_fwupg_get_ver_in_host(struct fts_upgrade *upg, u8 *ver)
+{
+    if ((!upg) || (!upg->func) || (!upg->fw) || (!ver)) {
+        FTS_ERROR("fts_data/upgrade/func/fw/ver is NULL");
+        return -EINVAL;
+    }
+
+    if (upg->fw_length < upg->func->fwveroff) {
+        FTS_ERROR("fw len(0x%0x) < fw ver offset(0x%x)",
+                  upg->fw_length, upg->func->fwveroff);
+        return -EINVAL;
+    }
+
+    FTS_INFO("fw version offset:0x%x", upg->func->fwveroff);
+    *ver = upg->fw[upg->func->fwveroff];
+    return 0;
+}
+
+static bool fts_fwupg_need_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool fwvalid = false;
+    u8 fw_ver_in_host = 0;
+    u8 fw_ver_in_tp = 0;
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if (fwvalid) {
+        ret = fts_fwupg_get_ver_in_host(upg, &fw_ver_in_host);
+        if (ret < 0) {
+            FTS_ERROR("get fw ver in host fail");
+            return false;
+        }
+
+        ret = fts_fwupg_get_ver_in_tp(&fw_ver_in_tp);
+        if (ret < 0) {
+            FTS_ERROR("get fw ver in tp fail");
+            return false;
+        }
+
+        FTS_INFO("fw major version in tp:%x, host:%x", fw_ver_in_tp, fw_ver_in_host);
+        if (fw_ver_in_tp != fw_ver_in_host) {
+            return true;
+        }
+    } else {
+        FTS_INFO("fw invalid, need upgrade fw");
+        return true;
+    }
+
+    return false;
+}
+
+/************************************************************************
+ * Name: fts_fw_upgrade
+ * Brief: fw upgrade main entry, run in following steps
+ *        1. check fw version(A6), not equal, will upgrade app(+param)
+ *        2. if fw version equal, will check ide, will upgrade app(+param)
+ *        in the follow situation
+ *          a. host&tp IDE's type are not equal, will upgrade app+param
+ *          b. host&tp are both IDE's type, and param's version are not
+ *          equal, will upgrade param
+ * Input:
+ * Output:
+ * Return: return 0 if success, otherwise return error code
+ ***********************************************************************/
+int fts_fwupg_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool upgrade_flag = false;
+    int upgrade_count = 0;
+    u8 ver = 0;
+
+    FTS_INFO("fw auto upgrade function");
+    if ((NULL == upg) || (NULL == upg->func)) {
+        FTS_ERROR("upg/upg->func is null");
+        return -EINVAL;
+    }
+
+    upgrade_flag = fts_fwupg_need_upgrade(upg);
+    FTS_INFO("fw upgrade flag:%d", upgrade_flag);
+    do {
+        upgrade_count++;
+        if (upgrade_flag) {
+            FTS_INFO("upgrade fw app(times:%d)", upgrade_count);
+            if (upg->func->upgrade) {
+                ret = upg->func->upgrade(upg->fw, upg->fw_length);
+                if (ret < 0) {
+                    fts_fwupg_reset_in_boot();
+                } else {
+                    fts_fwupg_get_ver_in_tp(&ver);
+                    FTS_INFO("success upgrade to fw version %02x", ver);
+                    break;
+                }
+            } else {
+                FTS_ERROR("upgrade func/upgrade is null, return immediately");
+                ret = -ENODATA;
+                break;
+            }
+        } else {
+            if (upg->func->param_upgrade) {
+                ret = fts_param_need_upgrade(upg);
+                if (ret <= 0) {
+                    FTS_INFO("param don't need upgrade");
+                    break;
+                } else if (1 == ret) {
+                    FTS_INFO("force upgrade fw app(times:%d)", upgrade_count);
+                    if (upg->func->upgrade) {
+                        ret = upg->func->upgrade(upg->fw, upg->fw_length);
+                        if (ret < 0) {
+                            fts_fwupg_reset_in_boot();
+                        } else {
+                            break;
+                        }
+                    }
+                } else if (2 == ret) {
+                    FTS_INFO("upgrade param area(times:%d)", upgrade_count);
+                    ret = upg->func->param_upgrade(upg->fw, upg->fw_length);
+                    if (ret < 0) {
+                        fts_fwupg_reset_in_boot();
+                    } else {
+                        fts_param_get_ver_in_tp(&ver);
+                        FTS_INFO("success upgrade to fw param version %02x", ver);
+                        break;
+                    }
+                } else
+                    break;
+            } else {
+                break;
+            }
+        }
+    } while (upgrade_count < 2);
+
+    return ret;
+}
+
+/************************************************************************
+ * fts_fwupg_auto_upgrade - upgrade main entry
+ ***********************************************************************/
+static void fts_fwupg_auto_upgrade(struct fts_upgrade *upg)
+{
+    int ret = 0;
+
+    FTS_INFO("********************FTS enter upgrade********************");
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return ;
+    }
+
+    ret = fts_fwupg_upgrade(upg);
+    if (ret < 0)
+        FTS_ERROR("**********tp fw(app/param) upgrade failed**********");
+    else
+        FTS_INFO("**********tp fw(app/param) no upgrade/upgrade success**********");
+
+#if FTS_AUTO_LIC_UPGRADE_EN
+    ret = fts_lic_upgrade(upg);
+    if (ret < 0)
+        FTS_ERROR("**********lcd init code upgrade failed**********");
+    else
+        FTS_INFO("**********lcd init code no upgrade/upgrade success**********");
+#endif
+
+    FTS_INFO("********************FTS exit upgrade********************");
+}
+
+static int fts_fwupg_get_vendorid(struct fts_upgrade *upg, int *vid)
+{
+    int ret = 0;
+    bool fwvalid = false;
+    u8 vendor_id = 0;
+    u8 module_id = 0;
+    u32 fwcfg_addr = 0;
+    u8 cfgbuf[FTS_HEADER_LEN] = { 0 };
+
+    FTS_INFO("read vendor id from tp");
+    if ((!upg) || (!upg->func) || (!upg->ts_data) || (!vid)) {
+        FTS_ERROR("upgrade/func/ts_data/vid is null");
+        return -EINVAL;
+    }
+
+    fwvalid = fts_fwupg_check_fw_valid();
+    if (fwvalid) {
+        ret = fts_read_reg(FTS_REG_VENDOR_ID, &vendor_id);
+        if (upg->ts_data->ic_info.is_incell)
+            ret = fts_read_reg(FTS_REG_MODULE_ID, &module_id);
+    } else {
+        fwcfg_addr =  upg->func->fwcfgoff;
+        ret = fts_flash_read(fwcfg_addr, cfgbuf, FTS_HEADER_LEN);
+
+        if ((cfgbuf[FTS_CONIFG_VENDORID_OFF] +
+             cfgbuf[FTS_CONIFG_VENDORID_OFF + 1]) == 0xFF)
+            vendor_id = cfgbuf[FTS_CONIFG_VENDORID_OFF];
+        if (upg->ts_data->ic_info.is_incell) {
+            if ((cfgbuf[FTS_CONIFG_MODULEID_OFF] +
+                 cfgbuf[FTS_CONIFG_MODULEID_OFF + 1]) == 0xFF)
+                module_id = cfgbuf[FTS_CONIFG_MODULEID_OFF];
+        }
+    }
+
+    if (ret < 0) {
+        FTS_ERROR("fail to get vendor id from tp");
+        return ret;
+    }
+
+    *vid = (int)((module_id << 8) + vendor_id);
+    return 0;
+}
+
+static int fts_fwupg_get_module_info(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    int i = 0;
+    struct upgrade_module *info = &module_list[0];
+
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return -EINVAL;
+    }
+
+    if (FTS_GET_MODULE_NUM > 1) {
+        /* support multi modules, must read correct module id(vendor id) */
+        ret = fts_fwupg_get_vendorid(upg, &upg->module_id);
+        if (ret < 0) {
+            FTS_ERROR("get vendor id failed");
+            return ret;
+        }
+        FTS_INFO("module id:%04x", upg->module_id);
+        for (i = 0; i < FTS_GET_MODULE_NUM; i++) {
+            info = &module_list[i];
+            if (upg->module_id == info->id) {
+                FTS_INFO("module id match, get module info pass");
+                break;
+            }
+        }
+        if (i >= FTS_GET_MODULE_NUM) {
+            FTS_ERROR("no module id match, don't get file");
+            return -ENODATA;
+        }
+    }
+
+    upg->module_info = info;
+    return 0;
+}
+
+static int fts_get_fw_file_via_request_firmware(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    const struct firmware *fw = NULL;
+    u8 *tmpbuf = NULL;
+
+    if (!upg || !upg->ts_data || !upg->ts_data->dev || !upg->ts_data->pdata) {
+        FTS_ERROR("upg/ts_data/dev is null");
+        return -EINVAL;
+    }
+
+    ret = request_firmware(&fw, upg->ts_data->pdata->fw_name, upg->ts_data->dev);
+    if (0 == ret) {
+        FTS_INFO("firmware(%s) request successfully",
+                 upg->ts_data->pdata->fw_name);
+        tmpbuf = vmalloc(fw->size);
+        if (NULL == tmpbuf) {
+            FTS_ERROR("fw buffer vmalloc fail");
+            ret = -ENOMEM;
+        } else {
+            memcpy(tmpbuf, fw->data, fw->size);
+            upg->fw = tmpbuf;
+            upg->fw_length = fw->size;
+            upg->fw_from_request = 1;
+        }
+    } else {
+        FTS_INFO("firmware(%s) request fail,ret=%d",
+                 upg->ts_data->pdata->fw_name, ret);
+    }
+
+    if (fw != NULL) {
+        release_firmware(fw);
+        fw = NULL;
+    }
+
+    return ret;
+}
+
+static int fts_get_fw_file_via_i(struct fts_upgrade *upg)
+{
+    upg->fw = upg->module_info->fw_file;
+    upg->fw_length = upg->module_info->fw_len;
+    upg->fw_from_request = 0;
+
+    return 0;
+}
+
+/*****************************************************************************
+ *  Name: fts_fwupg_get_fw_file
+ *  Brief: get fw image/file,
+ *         If support muitl modules, please set FTS_GET_MODULE_NUM, and FTS_-
+ *         MODULE_ID/FTS_MODULE_NAME;
+ *         If get fw via .i file, please set FTS_FW_REQUEST_SUPPORT=0, and F-
+ *         TS_MODULE_ID; will use module id to distingwish different modules;
+ *         If get fw via reques_firmware(), please set FTS_FW_REQUEST_SUPPORT
+ *         =1, and FTS_MODULE_NAME; fw file name will be composed of "focalt-
+ *         ech_ts_fw_" & FTS_VENDOR_NAME;
+ *
+ *         If have flash, module_id=vendor_id, If non-flash,module_id need
+ *         transfer from LCD driver(gpio or lcm_id or ...);
+ *  Input:
+ *  Output:
+ *  Return: return 0 if success, otherwise return error code
+ *****************************************************************************/
+static int fts_fwupg_get_fw_file(struct fts_upgrade *upg)
+{
+    int ret = 0;
+    bool get_fw_i_flag = false;
+
+    FTS_DEBUG("get upgrade fw file");
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return -EINVAL;
+    }
+
+    ret = fts_fwupg_get_module_info(upg);
+    if ((ret < 0) || (!upg->module_info)) {
+        FTS_ERROR("get module info fail");
+        return ret;
+    }
+
+    if (FTS_FW_REQUEST_SUPPORT) {
+        ret = fts_get_fw_file_via_request_firmware(upg);
+        if (ret != 0) {
+            get_fw_i_flag = true;
+        }
+    } else {
+        get_fw_i_flag = true;
+    }
+
+    if (get_fw_i_flag) {
+        ret = fts_get_fw_file_via_i(upg);
+    }
+
+    upg->lic = upg->fw;
+    upg->lic_length = upg->fw_length;
+
+    FTS_INFO("upgrade fw file len:%d", upg->fw_length);
+    if (upg->fw_length < FTS_MIN_LEN) {
+        FTS_ERROR("fw file len(%d) fail", upg->fw_length);
+        return -ENODATA;
+    }
+
+    return ret;
+}
+
+static void fts_fwupg_init_ic_detail(struct fts_upgrade *upg)
+{
+    if (upg && upg->func && upg->func->init) {
+        upg->func->init(upg->fw, upg->fw_length);
+    }
+}
+
+/*****************************************************************************
+ *  Name: fts_fwupg_work
+ *  Brief: 1. get fw image/file
+ *         2. ic init if have
+ *         3. call upgrade main function(fts_fwupg_auto_upgrade)
+ *  Input:
+ *  Output:
+ *  Return:
+ *****************************************************************************/
+static void fts_fwupg_work(struct work_struct *work)
+{
+    int ret = 0;
+    struct fts_upgrade *upg = fwupgrade;
+
+#if !FTS_AUTO_UPGRADE_EN
+    FTS_INFO("FTS_AUTO_UPGRADE_EN is disabled, not upgrade when power on");
+    return ;
+#endif
+
+    FTS_INFO("fw upgrade work function");
+    if (!upg || !upg->ts_data) {
+        FTS_ERROR("upg/ts_data is null");
+        return ;
+    }
+
+    upg->ts_data->fw_loading = 1;
+    fts_irq_disable();
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    /* get fw */
+    ret = fts_fwupg_get_fw_file(upg);
+    if (ret < 0) {
+        FTS_ERROR("get file fail, can't upgrade");
+    } else {
+        /* ic init if have */
+        fts_fwupg_init_ic_detail(upg);
+        /* run auto upgrade */
+        fts_fwupg_auto_upgrade(upg);
+    }
+
+#if FTS_ESDCHECK_EN
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    /* Update firmware feature settings after flashing firmware. */
+    fts_update_feature_setting(upg->ts_data);
+
+    fts_irq_enable();
+    upg->ts_data->fw_loading = 0;
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    goog_gti_probe(upg->ts_data);
+#endif
+}
+
+int fts_fwupg_init(struct fts_ts_data *ts_data)
+{
+    int i = 0;
+    int j = 0;
+    u16 ic_stype = 0;
+    struct upgrade_func *func = upgrade_func_list[0];
+    int func_count = sizeof(upgrade_func_list) / sizeof(upgrade_func_list[0]);
+
+    FTS_INFO("fw upgrade init function");
+
+    if (!ts_data || !ts_data->ts_workqueue) {
+        FTS_ERROR("ts_data/workqueue is NULL, can't run upgrade function");
+        return -EINVAL;
+    }
+
+    if (0 == func_count) {
+        FTS_ERROR("no upgrade function in tp driver");
+        return -ENODATA;
+    }
+
+    fwupgrade = (struct fts_upgrade *)kzalloc(sizeof(*fwupgrade), GFP_KERNEL);
+    if (NULL == fwupgrade) {
+        FTS_ERROR("malloc memory for upgrade fail");
+        return -ENOMEM;
+    }
+
+    ic_stype = ts_data->ic_info.ids.type;
+    if (1 == func_count) {
+        fwupgrade->func = func;
+    } else {
+        for (i = 0; i < func_count; i++) {
+            func = upgrade_func_list[i];
+            for (j = 0; j < FTS_MAX_COMPATIBLE_TYPE; j++) {
+                if (0 == func->ctype[j])
+                    break;
+                else if (func->ctype[j] == ic_stype) {
+                    FTS_INFO("match upgrade function,type:%x", (int)func->ctype[j]);
+                    fwupgrade->func = func;
+                }
+            }
+        }
+    }
+
+    if (NULL == fwupgrade->func) {
+        FTS_ERROR("no upgrade function match, can't upgrade");
+        kfree(fwupgrade);
+        fwupgrade = NULL;
+        return -ENODATA;
+    }
+
+    fwupgrade->ts_data = ts_data;
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+    INIT_DELAYED_WORK(&ts_data->fwupg_work, fts_fwupg_work);
+    queue_delayed_work(ts_data->ts_workqueue, &ts_data->fwupg_work,
+        msecs_to_jiffies(400));
+#else
+    INIT_WORK(&ts_data->fwupg_work, fts_fwupg_work);
+    queue_work(ts_data->ts_workqueue, &ts_data->fwupg_work);
+#endif // IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+    return 0;
+}
+
+int fts_fwupg_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    if (fwupgrade) {
+        if (fwupgrade->fw_from_request) {
+            vfree(fwupgrade->fw);
+            fwupgrade->fw = NULL;
+        }
+
+        kfree(fwupgrade);
+        fwupgrade = NULL;
+    }
+    FTS_FUNC_EXIT();
+    return 0;
+}
diff --git a/ft3683u/focaltech_flash.h b/ft3683u/focaltech_flash.h
new file mode 100644
index 0000000..1cf37b6
--- /dev/null
+++ b/ft3683u/focaltech_flash.h
@@ -0,0 +1,219 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R)£¬All Rights Reserved.
+*
+* File Name: focaltech_flash.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-07
+*
+* Abstract:
+*
+************************************************************************/
+#ifndef __LINUX_FOCALTECH_FLASH_H__
+#define __LINUX_FOCALTECH_FLASH_H__
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_CMD_RESET                               0x07
+#define FTS_ROMBOOT_CMD_SET_PRAM_ADDR               0xAD
+#define FTS_ROMBOOT_CMD_SET_PRAM_ADDR_LEN           4
+#define FTS_ROMBOOT_CMD_WRITE                       0xAE
+#define FTS_ROMBOOT_CMD_START_APP                   0x08
+#define FTS_DELAY_PRAMBOOT_START                    100
+#define FTS_ROMBOOT_CMD_ECC                         0xCC
+#define FTS_PRAM_SADDR                              0x000000
+#define FTS_DRAM_SADDR                              0xD00000
+
+#define FTS_CMD_READ                                0x03
+#define FTS_CMD_READ_DELAY                          1
+#define FTS_CMD_READ_LEN                            4
+#define FTS_CMD_READ_LEN_SPI                        6
+#define FTS_CMD_FLASH_TYPE                          0x05
+#define FTS_CMD_FLASH_MODE                          0x09
+#define FLASH_MODE_WRITE_FLASH_VALUE                0x0A
+#define FLASH_MODE_UPGRADE_VALUE                    0x0B
+#define FLASH_MODE_LIC_VALUE                        0x0C
+#define FLASH_MODE_PARAM_VALUE                      0x0D
+#define FTS_CMD_ERASE_APP                           0x61
+#define FTS_REASE_APP_DELAY                         1350
+#define FTS_ERASE_SECTOR_DELAY                      60
+#define FTS_RETRIES_REASE                           50
+#define FTS_RETRIES_DELAY_REASE                     400
+#define FTS_CMD_FLASH_STATUS                        0x6A
+#define FTS_CMD_FLASH_STATUS_LEN                    2
+#define FTS_CMD_FLASH_STATUS_NOP                    0x0000
+#define FTS_CMD_FLASH_STATUS_ECC_OK                 0xF055
+#define FTS_CMD_FLASH_STATUS_ERASE_OK               0xF0AA
+#define FTS_CMD_FLASH_STATUS_WRITE_OK               0x1000
+#define FTS_CMD_ECC_INIT                            0x64
+#define FTS_CMD_ECC_CAL                             0x65
+#define FTS_CMD_ECC_CAL_LEN                         7
+#define FTS_RETRIES_ECC_CAL                         10
+#define FTS_RETRIES_DELAY_ECC_CAL                   50
+#define FTS_CMD_ECC_READ                            0x66
+#define FTS_CMD_DATA_LEN                            0xB0
+#define FTS_CMD_APP_DATA_LEN_INCELL                 0x7A
+#define FTS_CMD_DATA_LEN_LEN                        4
+#define FTS_CMD_SET_WFLASH_ADDR                     0xAB
+#define FTS_CMD_SET_RFLASH_ADDR                     0xAC
+#define FTS_LEN_SET_ADDR                            4
+#define FTS_CMD_WRITE                               0xBF
+#define FTS_RETRIES_WRITE                           100
+#define FTS_RETRIES_DELAY_WRITE                     1
+#define FTS_CMD_WRITE_LEN                           6
+#define FTS_DELAY_READ_ID                           20
+#define FTS_DELAY_UPGRADE_RESET                     80
+#define PRAMBOOT_MIN_SIZE                           0x120
+#define PRAMBOOT_MAX_SIZE                           (64*1024)
+#define FTS_FLASH_PACKET_LENGTH                     128     /* max=128 */
+#define FTS_MAX_LEN_ECC_CALC                        0xFFFE /* must be even */
+#define FTS_MIN_LEN                                 0x120
+#define FTS_MAX_LEN_FILE                            (256 * 1024)
+#define FTS_MAX_LEN_APP                             (64 * 1024)
+#define FTS_MAX_LEN_SECTOR                          (4 * 1024)
+#define FTS_CONIFG_VENDORID_OFF                     0x04
+#define FTS_CONIFG_MODULEID_OFF                     0x1E
+#define FTS_CONIFG_PROJECTID_OFF                    0x20
+#define FTS_APPINFO_OFF                             0x100
+#define FTS_APPINFO_APPLEN_OFF                      0x00
+#define FTS_APPINFO_APPLEN2_OFF                     0x12
+#define FTS_REG_UPGRADE                             0xFC
+#define FTS_REG_UPGRADE2                            0xBC
+#define FTS_REG_BOOTLOADER_PREOUT                   0xF1
+#define FTS_BOOTLOADER_PREOUT_A0                    0xA0
+#define FTS_UPGRADE_AA                              0xAA
+#define FTS_UPGRADE_55                              0x55
+#define FTS_DELAY_UPGRADE_AA                        10
+#define FTS_UPGRADE_LOOP                            30
+#define FTS_HEADER_LEN                              32
+#define FTS_FW_BIN_FILEPATH                         "/sdcard/"
+#define FTS_FW_IDE_SIG                              "IDE_"
+#define FTS_FW_IDE_SIG_LEN                          4
+#define MAX_MODULE_VENDOR_NAME_LEN                  16
+
+#define FTS_ROMBOOT_CMD_ECC_NEW_LEN                 7
+#define FTS_ECC_FINISH_TIMEOUT                      100
+#define FTS_ROMBOOT_CMD_ECC_FINISH                  0xCE
+#define FTS_ROMBOOT_CMD_ECC_FINISH_OK_A5            0xA5
+#define FTS_ROMBOOT_CMD_ECC_FINISH_OK_00            0x00
+#define FTS_ROMBOOT_CMD_ECC_READ                    0xCD
+#define AL2_FCS_COEF                ((1 << 15) + (1 << 10) + (1 << 3))
+
+#define FTS_APP_INFO_OFFSET                         0x100
+
+enum FW_STATUS {
+    FTS_RUN_IN_ERROR,
+    FTS_RUN_IN_APP,
+    FTS_RUN_IN_ROM,
+    FTS_RUN_IN_PRAM,
+    FTS_RUN_IN_BOOTLOADER,
+};
+
+enum FW_FLASH_MODE {
+    FLASH_MODE_APP,
+    FLASH_MODE_LIC,
+    FLASH_MODE_PARAM,
+    FLASH_MODE_ALL,
+};
+
+enum ECC_CHECK_MODE {
+    ECC_CHECK_MODE_XOR,
+    ECC_CHECK_MODE_CRC16,
+};
+
+enum UPGRADE_SPEC {
+    UPGRADE_SPEC_V_1_0 = 0x0100,
+};
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+/* IC info */
+struct upgrade_func {
+    u16 ctype[FTS_MAX_COMPATIBLE_TYPE];
+    u32 fwveroff;
+    u32 fwcfgoff;
+    u32 appoff;
+    u32 licoff;
+    u32 paramcfgoff;
+    u32 paramcfgveroff;
+    u32 paramcfg2off;
+    int pram_ecc_check_mode;
+    int fw_ecc_check_mode;
+    int upgspec_version;
+    bool new_return_value_from_ic;
+    bool appoff_handle_in_ic;
+    bool is_reset_register_BC;
+    bool read_boot_id_need_reset;
+    bool hid_supported;
+    bool pramboot_supported;
+    u8 *pramboot;
+    u32 pb_length;
+    int (*init)(u8 *, u32);
+    int (*write_pramboot_private)(void);
+    int (*upgrade)(u8 *, u32);
+    int (*get_hlic_ver)(u8 *);
+    int (*lic_upgrade)(u8 *, u32);
+    int (*param_upgrade)(u8 *, u32);
+    int (*force_upgrade)(u8 *, u32);
+};
+
+struct upgrade_setting_nf {
+    u8 rom_idh;
+    u8 rom_idl;
+    u16 reserved;
+    u32 app2_offset;
+    u32 ecclen_max;
+    u8 eccok_val;
+    u8 upgsts_boot;
+    u8 delay_init;
+    bool spi_pe;
+    bool half_length;
+    bool fd_check;
+    bool drwr_support;
+};
+
+struct upgrade_module {
+    int id;
+    char vendor_name[MAX_MODULE_VENDOR_NAME_LEN];
+    u8 *fw_file;
+    u32 fw_len;
+};
+
+struct fts_upgrade {
+    struct fts_ts_data *ts_data;
+    struct upgrade_module *module_info;
+    struct upgrade_func *func;
+    struct upgrade_setting_nf *setting_nf;
+    int module_id;
+    bool fw_from_request;
+    u8 *fw;
+    u32 fw_length;
+    u8 *lic;
+    u32 lic_length;
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+extern struct upgrade_func upgrade_func_ft5008;
+
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+int fts_fwupg_reset_in_boot(void);
+int fts_fwupg_enter_into_boot(void);
+int fts_fwupg_erase(u32 delay);
+int fts_fwupg_ecc_cal(u32 saddr, u32 len);
+int fts_flash_write_buf(u32 saddr, u8 *buf, u32 len, u32 delay);
+int fts_flash_read_buf(u32 saddr, u8 *buf, u32 len);
+int fts_fwupg_upgrade(struct fts_upgrade *upg);
+#endif
diff --git a/ft3683u/focaltech_flash/Makefile b/ft3683u/focaltech_flash/Makefile
new file mode 100644
index 0000000..92064c5
--- /dev/null
+++ b/ft3683u/focaltech_flash/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_upgrade_ft3683u.o
\ No newline at end of file
diff --git a/ft3683u/focaltech_flash/focaltech_upgrade_ft3683u.c b/ft3683u/focaltech_flash/focaltech_upgrade_ft3683u.c
new file mode 100644
index 0000000..faca5ca
--- /dev/null
+++ b/ft3683u/focaltech_flash/focaltech_upgrade_ft3683u.c
@@ -0,0 +1,330 @@
+/*
+ *
+ * FocalTech fts TouchScreen driver.
+ *
+ * Copyright (c) 2012-2022, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_upgrade_ft5008.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2022-10-09
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "../focaltech_flash.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_DELAY_ERASE_PAGE            6
+#define FTS_SIZE_PAGE                   256
+#define FTS_FLASH_PACKET_SIZE           1024//max 2048
+
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+/************************************************************************
+* Name: fts_ft5008_crc16_calc_host
+* Brief:
+* Input:
+* Output:
+* Return: return ecc
+***********************************************************************/
+static u16 fts_ft5008_crc16_calc_host(u8 *pbuf, u32 length)
+{
+    u16 ecc = 0;
+    u32 i = 0;
+    u32 j = 0;
+
+    for ( i = 0; i < length; i += 2 ) {
+        ecc ^= ((pbuf[i] << 8) | (pbuf[i + 1]));
+        for (j = 0; j < 16; j ++) {
+            if (ecc & 0x01)
+                ecc = (u16)((ecc >> 1) ^ AL2_FCS_COEF);
+            else
+                ecc >>= 1;
+        }
+    }
+
+    return ecc;
+}
+
+/************************************************************************
+ * Name: fts_ft5008_flash_write_buf
+ * Brief: write buf data to flash address
+ * Input: saddr - start address data write to flash
+ *        buf - data buffer
+ *        len - data length
+ *        delay - delay after write
+ * Output:
+ * Return: return data ecc of host if success, otherwise return error code
+ ***********************************************************************/
+static int fts_ft5008_flash_write_buf(u32 saddr, u8 *buf, u32 len, u32 delay)
+{
+    int ret = 0;
+    u32 i = 0;
+    u32 j = 0;
+    u32 packet_number = 0;
+    u32 packet_len = 0;
+    u32 addr = 0;
+    u32 offset = 0;
+    u32 remainder = 0;
+    u32 cmdlen = 0;
+    u8 packet_buf[FTS_FLASH_PACKET_SIZE + FTS_CMD_WRITE_LEN] = { 0 };
+    int ecc_in_host = 0;
+    u8 cmd = 0;
+    u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 };
+    u16 read_status = 0;
+    u16 wr_ok = 0;
+    u32 flash_packet_size = FTS_FLASH_PACKET_SIZE;
+
+    FTS_INFO( "**********write data to flash**********");
+    if (!buf || !len || (len > FTS_MAX_LEN_FILE)) {
+        FTS_ERROR("buf/len(%d) is invalid", len);
+        return -EINVAL;
+    }
+
+    FTS_INFO("data buf start addr=0x%x, len=0x%x", saddr, len);
+    packet_number = len / flash_packet_size;
+    remainder = len % flash_packet_size;
+    if (remainder > 0)
+        packet_number++;
+    packet_len = flash_packet_size;
+    FTS_INFO("write data, num:%d remainder:%d", packet_number, remainder);
+
+    for (i = 0; i < packet_number; i++) {
+        offset = i * flash_packet_size;
+        addr = saddr + offset;
+
+        /* last packet */
+        if ((i == (packet_number - 1)) && remainder)
+            packet_len = remainder;
+
+        if (fts_data->bus_type == FTS_BUS_TYPE_SPI_V2) {
+            packet_buf[0] = FTS_CMD_SET_WFLASH_ADDR;
+            packet_buf[1] = BYTE_OFF_16(addr);
+            packet_buf[2] = BYTE_OFF_8(addr);
+            packet_buf[3] = BYTE_OFF_0(addr);
+            ret = fts_write(packet_buf, FTS_LEN_SET_ADDR);
+            if (ret < 0) {
+                FTS_ERROR("set flash address fail");
+                return ret;
+            }
+
+            packet_buf[0] = FTS_CMD_WRITE;
+            cmdlen = 1;
+        } else {
+            packet_buf[0] = FTS_CMD_WRITE;
+            packet_buf[1] = BYTE_OFF_16(addr);
+            packet_buf[2] = BYTE_OFF_8(addr);
+            packet_buf[3] = BYTE_OFF_0(addr);
+            packet_buf[4] = BYTE_OFF_8(packet_len);
+            packet_buf[5] = BYTE_OFF_0(packet_len);
+            cmdlen = 6;
+        }
+
+        for (j = 0; j < packet_len; j++) {
+            packet_buf[cmdlen + j] = buf[offset + j];
+        }
+
+        ret = fts_write(packet_buf, packet_len + cmdlen);
+        if (ret < 0) {
+            FTS_ERROR("app write fail");
+            return ret;
+        }
+        mdelay(delay);
+
+        /* read status */
+        wr_ok = FTS_CMD_FLASH_STATUS_WRITE_OK + addr / packet_len;
+        for (j = 0; j < FTS_RETRIES_WRITE; j++) {
+            cmd = FTS_CMD_FLASH_STATUS;
+            ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN);
+            read_status = (((u16)val[0]) << 8) + val[1];
+            /*  FTS_INFO("%x %x", wr_ok, read_status); */
+            if (wr_ok == read_status) {
+                break;
+            }
+            mdelay(FTS_RETRIES_DELAY_WRITE);
+        }
+    }
+
+    ecc_in_host = (int)fts_ft5008_crc16_calc_host(buf, len);
+    return ecc_in_host;
+}
+
+
+static void fts_communication_recovery_spi(void)
+{
+    u8 cmd_0[] = {0x70, 0x55, 0xaa};
+    u8 cmd_1[] = {0x70, 0x07, 0xf8, 0x81, 0xca, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00};
+    u8 cmd_2[] = {0x70, 0x07, 0xf8, 0x81, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    u8 cmd_3[] = {0x70, 0x07, 0xf8, 0x81, 0xc6, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00};
+    u8 cmd_4[] = {0x70, 0x07, 0xf8, 0x81, 0xc2, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00};
+    u8 cmd_5[] = {0x70, 0x07, 0xf8, 0x81, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    u8 cmd_6[] = {0x70, 0x07, 0xf8, 0x81, 0xc0, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00};
+    u8 cmd_7[] = {0x70, 0x07, 0xf8, 0x81, 0xc1, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00};
+    u8 cmd_8[] = {0x70, 0x07, 0xf8, 0x81, 0xc8, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00};
+    u8 cmd_9[] = {0x70, 0x07, 0xf8, 0x81, 0xc8, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00};
+    u8 cmd_10[] = {0x70, 0x07, 0xf8, 0x81, 0xc8, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00};
+    u8 cmd_11[] = {0x70, 0x07, 0xf8, 0x81, 0xc4, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00};
+    u8 cmd_12[] = {0x70, 0x06, 0xf9, 0x81, 0xc4, 0x00, 0x00};
+    u8 cmd = 0x71;
+    u8 val[5] = {0};
+    int i = 0;
+
+    FTS_INFO("enter debug mode!!!!");
+    fts_bus_set_speed(fts_data, 2000000);
+    for (i = 0; i < 3; i++) {
+        fts_reset_proc(0);
+        mdelay(2);
+
+        fts_spi_transfer_direct(cmd_0, sizeof(cmd_0), NULL, 0);
+        fts_spi_transfer_direct(cmd_1, sizeof(cmd_1), NULL, 0);
+        fts_spi_transfer_direct(cmd_2, sizeof(cmd_2), NULL, 0);
+        fts_spi_transfer_direct(cmd_3, sizeof(cmd_3), NULL, 0);
+        fts_spi_transfer_direct(cmd_4, sizeof(cmd_4), NULL, 0);
+        fts_spi_transfer_direct(cmd_5, sizeof(cmd_5), NULL, 0);
+        fts_spi_transfer_direct(cmd_6, sizeof(cmd_6), NULL, 0);
+        fts_spi_transfer_direct(cmd_7, sizeof(cmd_7), NULL, 0);
+        fts_spi_transfer_direct(cmd_8, sizeof(cmd_8), NULL, 0);
+        fts_spi_transfer_direct(cmd_9, sizeof(cmd_9), NULL, 0);
+        fts_spi_transfer_direct(cmd_10, sizeof(cmd_10), NULL, 0);
+        fts_spi_transfer_direct(cmd_11, sizeof(cmd_11), NULL, 0);
+        mdelay(8);
+        fts_spi_transfer_direct(cmd_12, sizeof(cmd_12), NULL, 0);
+        fts_spi_transfer_direct(&cmd, sizeof(cmd), val, sizeof(val));
+
+        if (val[1] == 0x50 && val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) {
+            FTS_INFO("debug mode success exit!!!");
+            break;
+        }
+        FTS_INFO("debug mode exit read : 0x%x %x %x %x", val[1], val[2], val[3], val[4]);
+        mdelay(10);
+    }
+    fts_reset_proc(100);
+    fts_bus_set_speed(fts_data, fts_data->spi_speed);
+}
+
+
+/************************************************************************
+* Name: fts_ft5008_upgrade
+* Brief:
+* Input:
+* Output:
+* Return: return 0 if success, otherwise return error code
+***********************************************************************/
+static int fts_ft5008_upgrade(u8 *buf, u32 len)
+{
+    int ret = 0;
+    u32 start_addr = 0;
+    u8 cmd[4] = { 0 };
+    u32 delay = 0;
+    int ecc_in_host = 0;
+    int ecc_in_tp = 0;
+
+    if ((NULL == buf) || (len < FTS_MIN_LEN)) {
+        FTS_ERROR("buffer/len(%x) is invalid", len);
+        return -EINVAL;
+    }
+
+    /* enter into upgrade environment */
+    ret = fts_fwupg_enter_into_boot();
+    if (ret < 0) {
+        FTS_ERROR("enter into pramboot/bootloader fail,ret=%d", ret);
+        goto fw_reset;
+    }
+
+    cmd[0] = FTS_CMD_APP_DATA_LEN_INCELL;
+    cmd[1] = BYTE_OFF_16(len);
+    cmd[2] = BYTE_OFF_8(len);
+    cmd[3] = BYTE_OFF_0(len);
+    ret = fts_write(cmd, FTS_CMD_DATA_LEN_LEN);
+    if (ret < 0) {
+        FTS_ERROR("data len cmd write fail");
+        goto fw_reset;
+    }
+
+    cmd[0] = FTS_CMD_FLASH_MODE;
+    cmd[1] = FLASH_MODE_UPGRADE_VALUE;
+    ret = fts_write(cmd, 2);
+    if (ret < 0) {
+        FTS_ERROR("upgrade mode(09) cmd write fail");
+        goto fw_reset;
+    }
+
+    delay = FTS_DELAY_ERASE_PAGE * (len / FTS_SIZE_PAGE);
+    ret = fts_fwupg_erase(delay);
+    if (ret < 0) {
+        FTS_ERROR("erase cmd write fail");
+        goto fw_reset;
+    }
+
+    /* write app */
+    start_addr = upgrade_func_ft5008.appoff;
+    delay = (FTS_FLASH_PACKET_SIZE / FTS_SIZE_PAGE) * 2;
+    ecc_in_host = fts_ft5008_flash_write_buf(start_addr, buf, len, delay);
+    if (ecc_in_host < 0 ) {
+        FTS_ERROR("flash write fail");
+        goto fw_reset;
+    }
+
+    /* ecc */
+    ecc_in_tp = fts_fwupg_ecc_cal(start_addr, len);
+    if (ecc_in_tp < 0 ) {
+        FTS_ERROR("ecc read fail");
+        goto fw_reset;
+    }
+
+    FTS_INFO("ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host);
+    if (ecc_in_tp != ecc_in_host) {
+        FTS_ERROR("ecc check fail");
+        goto fw_reset;
+    }
+
+    FTS_INFO("upgrade success, reset to normal boot");
+    ret = fts_fwupg_reset_in_boot();
+    if (ret < 0) {
+        FTS_ERROR("reset to normal boot fail");
+    }
+
+    msleep(200);
+    return 0;
+
+fw_reset:
+    FTS_INFO("upgrade fail, reset to normal boot");
+    fts_communication_recovery_spi();
+    return -EIO;
+}
+
+
+struct upgrade_func upgrade_func_ft5008 = {
+    .ctype = {0x90},
+    .fwveroff = 0x010E,
+    .fwcfgoff = 0x1F80,
+    .appoff = 0x0000,
+    .upgspec_version = UPGRADE_SPEC_V_1_0,
+    .pramboot_supported = false,
+    .hid_supported = true,
+    .upgrade = fts_ft5008_upgrade,
+};
diff --git a/ft3683u/focaltech_gesture.c b/ft3683u/focaltech_gesture.c
new file mode 100644
index 0000000..45bc872
--- /dev/null
+++ b/ft3683u/focaltech_gesture.c
@@ -0,0 +1,231 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, Focaltech Ltd. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_gestrue.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-08
+*
+* Abstract:
+*
+* Reference:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* 1.Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/******************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+static ssize_t fts_gesture_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    u8 val = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    mutex_lock(&ts_data->input_dev->mutex);
+    fts_read_reg(FTS_REG_GESTURE_EN, &val);
+    count = snprintf(buf, PAGE_SIZE, "Gesture Mode:%s\n",
+                     ts_data->gesture_mode ? "On" : "Off");
+    count += snprintf(buf + count, PAGE_SIZE, "Reg(0xD0)=%d\n", val);
+    mutex_unlock(&ts_data->input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_gesture_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct fts_ts_data *ts_data = fts_data;
+
+    mutex_lock(&ts_data->input_dev->mutex);
+    if (FTS_SYSFS_ECHO_ON(buf)) {
+        FTS_DEBUG("enable gesture");
+        ts_data->gesture_mode = ENABLE;
+    } else if (FTS_SYSFS_ECHO_OFF(buf)) {
+        FTS_DEBUG("disable gesture");
+        ts_data->gesture_mode = DISABLE;
+    }
+    mutex_unlock(&ts_data->input_dev->mutex);
+
+    return count;
+}
+
+static inline u32 get_gesture_coordinate(u8 msb, u8 lsb)
+{
+  return FTS_TOUCH_HIRES(((msb & 0xFF) << 8) + (lsb & 0xFF));
+}
+
+
+static ssize_t fts_gesture_buf_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    int count = 0;
+    struct input_dev *input_dev = fts_data->input_dev;
+    struct fts_gesture_st *gesture = &fts_data->fts_gesture_data;
+
+    mutex_lock(&input_dev->mutex);
+    count = snprintf(buf, PAGE_SIZE, "Gesture ID:%d\n", gesture->gesture_id);
+    count += snprintf(buf + count, PAGE_SIZE, "Gesture PointNum:%d\n",
+                      gesture->point_num);
+    count += snprintf(buf + count, PAGE_SIZE, "Gesture Points Buffer:\n");
+
+    count += snprintf(buf + count, PAGE_SIZE, "(%4d,%4d) ",
+                      get_gesture_coordinate(gesture->coordinate_x_msb,
+                                             gesture->coordinate_x_lsb),
+                      get_gesture_coordinate(gesture->coordinate_y_msb,
+                                             gesture->coordinate_y_lsb));
+    count += snprintf(buf + count, PAGE_SIZE, "\n");
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+static ssize_t fts_gesture_buf_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    return -EPERM;
+}
+
+
+/* sysfs gesture node
+ *   read example: cat  fts_gesture_mode       ---read gesture mode
+ *   write example:echo 1 > fts_gesture_mode   --- write gesture mode to 1
+ *
+ */
+static DEVICE_ATTR(fts_gesture_mode, S_IRUGO | S_IWUSR, fts_gesture_show,
+                   fts_gesture_store);
+/*
+ *   read example: cat fts_gesture_buf        --- read gesture buf
+ */
+static DEVICE_ATTR(fts_gesture_buf, S_IRUGO | S_IWUSR,
+                   fts_gesture_buf_show, fts_gesture_buf_store);
+
+static struct attribute *fts_gesture_mode_attrs[] = {
+    &dev_attr_fts_gesture_mode.attr,
+    &dev_attr_fts_gesture_buf.attr,
+    NULL,
+};
+
+static struct attribute_group fts_gesture_group = {
+    .attrs = fts_gesture_mode_attrs,
+};
+
+static int fts_create_gesture_sysfs(struct device *dev)
+{
+    int ret = 0;
+
+    ret = sysfs_create_group(&dev->kobj, &fts_gesture_group);
+    if (ret) {
+        FTS_ERROR("gesture sys node create fail");
+        sysfs_remove_group(&dev->kobj, &fts_gesture_group);
+        return ret;
+    }
+
+    return 0;
+}
+
+/*****************************************************************************
+* Name: fts_gesture_readdata
+* Brief: Read information about gesture: enable flag/gesture points..., if ges-
+*        ture enable, save gesture points' information, and report to OS.
+*        It will be called this function every intrrupt when FTS_GESTURE_EN = 1
+*
+*        gesture data length: 1(enable) + 1(reserve) + 2(header) + 6 * 4
+* Input: ts_data   - global struct data
+*        is_report - whether report gesture event or not.
+* Output:
+* Return: 0 - read gesture data successfully, the report data is gesture data
+*         1 - tp not in suspend/gesture not enable in TP FW
+*         -Exx - error
+*****************************************************************************/
+int fts_gesture_readdata(struct fts_ts_data *ts_data)
+{
+    struct fts_gesture_st *gesture = &fts_data->fts_gesture_data;
+    u8 cmd[2] = { 0 };
+
+    cmd[0] = FTS_GESTURE_MAJOR_MINOR;
+
+    /*if (!ts_data->suspended) {
+        return -EINVAL;
+    }*/
+    fts_read(cmd, 1, gesture->data, sizeof(struct fts_gesture_st));
+
+    if (gesture->gesture_enable != ENABLE) {
+        FTS_DEBUG("gesture not enable in fw, don't process gesture");
+        return -EINVAL;
+    }
+
+    if (ts_data->log_level >= 1) {
+        FTS_ERROR("gesture_id=0x%x, point_num=%d, x=%d, y=%d,"
+                  "major=%d, minor=%d, orientation=%d\n",
+            gesture->gesture_id, gesture->point_num,
+            get_gesture_coordinate(gesture->coordinate_x_msb,
+                                   gesture->coordinate_x_lsb),
+            get_gesture_coordinate(gesture->coordinate_y_msb,
+                                   gesture->coordinate_y_lsb),
+            gesture->major, gesture->minor,
+            gesture->orientation);
+   }
+
+    return 0;
+}
+
+int fts_gesture_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    fts_create_gesture_sysfs(ts_data->dev);
+
+    memset(&ts_data->fts_gesture_data, 0, sizeof(struct fts_gesture_st));
+    ts_data->gesture_mode = FTS_GESTURE_EN;
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_gesture_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_gesture_group);
+    FTS_FUNC_EXIT();
+    return 0;
+}
diff --git a/ft3683u/focaltech_goog.c b/ft3683u/focaltech_goog.c
new file mode 100644
index 0000000..0934058
--- /dev/null
+++ b/ft3683u/focaltech_goog.c
@@ -0,0 +1,920 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *
+ * Copyright (c) 2021 Google LLC
+ *    Author: TsoHsien(Blackbear) Chou <[email protected]>
+ */
+
+#include "focaltech_core.h"
+#include "focaltech_common.h"
+#include "focaltech_test/focaltech_test.h"
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+
+#include <goog_touch_interface.h>
+
+static irqreturn_t goog_fts_irq_ts(int irq, void *data)
+{
+    struct fts_ts_data *ts_data = data;
+
+    ts_data->isr_timestamp = ktime_get();
+    return IRQ_WAKE_THREAD;
+}
+
+extern int int_test_has_interrupt;
+static irqreturn_t goog_fts_irq_handler(int irq, void *data)
+{
+    int_test_has_interrupt++;
+    fts_data->coords_timestamp = fts_data->isr_timestamp;
+    fts_irq_read_report();
+
+    return IRQ_HANDLED;
+}
+
+static int google_enter_normal_sensing(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+    int i = 0;
+    u8 gesture_mode = 0;
+    u8 power_mode = 0;
+    mutex_lock(&ts_data->reg_lock);
+
+    for (i = 0; i < 200; i++) {
+        ret = fts_write_reg(FTS_REG_WAKEUP, FTS_WAKEUP_VALUE);
+        if (ret < 0) {
+          FTS_ERROR("Write reg(%x) = %x fail", FTS_REG_WAKEUP, FTS_WAKEUP_VALUE);
+          goto exit;
+        }
+
+        ret = fts_read_reg(FTS_REG_POWER_MODE, &power_mode);
+        if (ret < 0) {
+          FTS_ERROR("read reg0xA5 fails");
+          goto exit;
+        }
+
+        if (power_mode != 3)
+            break;
+
+        usleep_range(1000, 1000);
+    }
+
+    if (i >= 200) {
+        FTS_ERROR("Enter normal mode failed");
+        goto exit;
+    } else {
+        FTS_INFO("Enter normal mode (%d ms)", i);
+    }
+
+
+    ret = fts_read_reg(FTS_REG_GESTURE_EN, &gesture_mode);
+    if (ret < 0) {
+        FTS_ERROR("Read reg(%x) fails", FTS_REG_GESTURE_EN);
+        goto exit;
+    }
+    if (gesture_mode) {
+      FTS_INFO("Exit gesture mode");
+      gesture_mode = 0;
+      ret = fts_write_reg(FTS_REG_GESTURE_EN, gesture_mode);
+      if (ret < 0) {
+        FTS_ERROR("Write reg(%x) = %x fail", FTS_REG_GESTURE_EN, gesture_mode);
+        goto exit;
+      }
+    }
+
+exit:
+    mutex_unlock(&ts_data->reg_lock);
+    return ret;
+}
+
+static int goog_fts_ts_suspend(struct device *dev)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+
+    FTS_FUNC_ENTER();
+
+    if (ts_data->fw_loading) {
+        FTS_INFO("fw upgrade in process, can't suspend");
+        return 0;
+    }
+
+    FTS_INFO("Prepare to suspend device");
+    /* Disable irq */
+    fts_irq_disable();
+
+    FTS_INFO("Do reset on suspend");
+    fts_reset_proc(FTS_RESET_INTERVAL);
+
+    ret = fts_wait_tp_to_valid();
+    if (ret != 0) {
+        FTS_ERROR("Suspend has been cancelled by wake up timeout");
+        return ret;
+    }
+    FTS_INFO("Device has been reset");
+
+    FTS_DEBUG("make TP enter into sleep mode");
+    mutex_lock(&ts_data->reg_lock);
+    ret = fts_write_reg(FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP);
+    ts_data->is_deepsleep = true;
+    mutex_unlock(&ts_data->reg_lock);
+    if (ret < 0)
+      FTS_ERROR("set TP to sleep mode fail, ret=%d", ret);
+
+    ret = fts_pinctrl_select_suspend(ts_data);
+    if (ret < 0)
+      FTS_ERROR("set pinctrl suspend fail, ret=%d", ret);
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+static int goog_fts_ts_resume(struct device *dev)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+
+    FTS_FUNC_ENTER();
+    FTS_INFO("Prepare to resume device\n");
+
+    ret = fts_pinctrl_select_normal(ts_data);
+    if (ret < 0)
+      FTS_ERROR("set pinctrl normal fail, ret=%d", ret);
+
+    ret = google_enter_normal_sensing(ts_data);
+    if (ret < 0) {
+      FTS_ERROR("Fail to enter normal power mode, trigger reset to recover\n");
+      fts_reset_proc(FTS_RESET_INTERVAL);
+
+      ret = fts_wait_tp_to_valid();
+      if (ret != 0) {
+        FTS_ERROR("Resume has been cancelled by wake up timeout");
+        return ret;
+      }
+    }
+
+    fts_update_feature_setting(ts_data);
+
+    ts_data->is_deepsleep = false;
+    fts_irq_enable();
+
+    FTS_FUNC_EXIT();
+    FTS_INFO("Device resumed");
+    return 0;
+};
+
+static const struct dev_pm_ops goog_fts_dev_pm_ops = {
+    .suspend = goog_fts_ts_suspend,
+    .resume = goog_fts_ts_resume,
+};
+
+extern int fts_test_get_raw(int *raw, u8 tx, u8 rx);
+extern int fts_test_get_short(int *short_data, u8 tx, u8 rx);
+extern int fts_test_get_short_ch_to_gnd(int *res, u8 *ab_ch, u8 tx, u8 rx);
+extern int fts_test_get_short_ch_to_ch(int *res, u8 *ab_ch, u8 tx, u8 rx);
+extern size_t google_internal_sttw_setting_read(char *buf, size_t buf_size);
+
+// Reference: proc_test_raw_show
+static int goog_selfttest_test_raw(void)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *raw = NULL;
+    bool result = 0;
+    char print_buf[512];
+    int count = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+    /* get Tx channel number */
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    /* get Rx channel number */
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    raw = fts_malloc(node_num * sizeof(int));
+    if (!raw) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_raw(raw, tx, rx);
+    result = compare_array(raw,
+                           thr->rawdata_h_min,
+                           thr->rawdata_h_max,
+                           false);
+
+    /* output raw data */
+    count += scnprintf(print_buf + count, 512 - count, "     ");
+    for (i = 0; i < rx; i++)
+        count += scnprintf(print_buf + count, 512 - count, " RX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        if ((i % rx) == 0) {
+            FTS_INFO("%s\n", &print_buf[0]);
+            count = 0;
+            count += scnprintf(print_buf + count, 512 - count, "TX%02d:%5d,",
+                               (i / rx  + 1), raw[i]);
+        } else
+            count += scnprintf(print_buf + count, 512 - count, "%5d,", raw[i]);
+    }
+
+    FTS_INFO("%s\n", &print_buf[0]);
+    count = 0;
+
+    FTS_INFO("\n\n");
+    FTS_INFO("Rawdata Test %s\n", result? "PASS" : "NG");
+
+    if (!result)
+      ret = -1;
+
+exit:
+    if (raw)
+        fts_free(raw);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    return ret;
+}
+
+// Reference: proc_test_short_show
+static int goog_selfttest_test_short(void)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *short_data = NULL;
+    int *short_data_cg = NULL;
+    int *short_data_cc = NULL;
+    bool result = 1;
+    bool cg_result = 1;
+    bool cc_result = 1;
+    int code = 0;
+    struct fts_test *tdata = fts_ftest;
+    u8 ab_ch[SC_NUM_MAX + 1] = { 0 };
+    u8 ab_ch_num = 0;
+    int temp = 0;
+    int j = 0;
+    int adc_cnt = 0;
+    bool is_cc_short = false;
+    bool is_cg_short = false;
+    int tmp_num = 0;
+    char print_buf[512];
+    int count = 0;
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+    /* get Tx channel number */
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    /* get Rx channel number */
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    short_data = fts_malloc(node_num * sizeof(int));
+    short_data_cg = fts_malloc(node_num * sizeof(int));
+    short_data_cc = fts_malloc(node_num * sizeof(int));
+    if (!short_data || !short_data_cg || !short_data_cc) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get short all data */
+    fts_test_get_short(short_data, tx, rx);
+
+    for (i = 0; i < node_num; i++) {
+        code = short_data[i];
+
+        if (code > 1500) {
+            FTS_INFO("adc(%d) > 1500 fail", code);
+            result = false;
+            continue;
+        }
+
+        if ((212 - ((code * 250 / 2047) + 40)) == 0) {
+            short_data[i] = 50000;
+            continue;
+        }
+        short_data[i] = fts_abs(((code * 25 / 2047 + 4) * 2005) /
+                                (212 - ((code * 250 / 2047) + 40)));
+        if (short_data[i] < tdata->ic.mc_sc.thr.basic.short_cg) {
+            ab_ch_num++;
+            ab_ch[ab_ch_num] = i;
+            result = false;
+        }
+    }
+    /* output short data */
+    count += scnprintf(print_buf + count, 512 - count, "TX:");
+    for (i = 0; i < tx; i++) {
+        count += scnprintf(print_buf + count, 512 - count, "%d,", short_data[i]);
+    }
+    FTS_INFO("%s\n", &print_buf[0]);
+    count = 0;
+
+    count += scnprintf(print_buf + count, 512 - count, "RX:");
+    for (i = tx; i < node_num; i++) {
+        count += scnprintf(print_buf + count, 512 - count,"%d,", short_data[i]);
+    }
+    FTS_INFO("%s\n", &print_buf[0]);
+    count = 0;
+
+    if (result == true) goto short_end;
+
+    ab_ch[0] = ab_ch_num;
+    if (ab_ch_num) {
+        FTS_INFO("\nabnormal ch:[%*ph]\n", ab_ch_num, ab_ch);
+    }
+    /********************get short cg********************/
+    fts_test_get_short_ch_to_gnd(short_data_cg, ab_ch, tx, rx);
+    for (i = 0; i < ab_ch_num; i++) {
+        temp = short_data_cg[i];
+        if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+            short_data_cg[i] = 50000;
+            continue;
+        }
+        short_data_cg[i] = fts_abs(((temp * 25 / 2047 + 4) * 2005) /
+                                   (212 - ((temp * 250 / 2047) + 40)));
+        if (short_data_cg[i] < tdata->ic.mc_sc.thr.basic.short_cg) {
+            cg_result = false;
+            if (!is_cg_short) {
+                FTS_INFO("\nGND Short:\n");
+                is_cg_short = true;
+            }
+
+            if (ab_ch[i + 1] <= tx) {
+                count += scnprintf(print_buf + count, 512 - count,
+                    "Tx%d with GND:", ab_ch[i + 1]);
+            } else {
+                count += scnprintf(print_buf + count, 512 - count,
+                    "Rx%d with GND:", (ab_ch[i + 1] - tx));
+            }
+            count += scnprintf(print_buf + count, 512 - count,
+                "%d(K)", short_data_cg[i]);
+            FTS_INFO("%s\n", &print_buf[0]);
+            count = 0;
+        }
+    }
+
+
+    /********************get short cc********************/
+    tmp_num = ab_ch_num * (ab_ch_num - 1) / 2;
+    tmp_num = (tmp_num > node_num) ? node_num : tmp_num;
+    fts_test_get_short_ch_to_ch(short_data_cc, ab_ch, tx, rx);
+
+    for (i = 0; i < ab_ch_num; i++) {
+        for (j = i + 1; j < ab_ch_num; j++) {
+            if (adc_cnt >= tmp_num)
+                break;
+
+            temp = short_data_cc[adc_cnt];
+            if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+                short_data_cc[adc_cnt] = 50000;
+                continue;
+            }
+            short_data_cc[adc_cnt] = fts_abs(((temp * 25 / 2047 + 4) * 2005) /
+                                           (212 - ((temp * 250 / 2047) + 40)));
+            if (short_data_cc[adc_cnt] < tdata->ic.mc_sc.thr.basic.short_cc) {
+                cc_result = false;
+                if (!is_cc_short) {
+                    FTS_INFO("\nMutual Short:\n");
+                    is_cc_short = true;
+                }
+
+                if (ab_ch[i + 1] <= tx) {
+                    count += scnprintf(print_buf + count, 512 - count,
+                        "Tx%d with", (ab_ch[i + 1]));
+                } else {
+                    count += scnprintf(print_buf + count, 512 - count,
+                        "Rx%d with", (ab_ch[i + 1] - tx));
+                }
+
+                if (ab_ch[j + 1] <= tx) {
+                    count += scnprintf(print_buf + count, 512 - count,
+                        " Tx%d", (ab_ch[j + 1] ) );
+                } else {
+                    count += scnprintf(print_buf + count, 512 - count,
+                        " Rx%d", (ab_ch[j + 1] - tx));
+                }
+                count += scnprintf(print_buf + count, 512 - count,
+                    ":%d(K)\n", short_data_cc[adc_cnt]);
+                FTS_INFO("%s\n", &print_buf[0]);
+                count = 0;
+            }
+            adc_cnt++;
+        }
+    }
+
+short_end:
+    FTS_INFO("\n\n");
+    FTS_INFO("Short Test %s\n", result? "PASS" : "NG");
+    if (!result)
+      ret = -1;
+
+exit:
+    if (short_data)
+        fts_free(short_data);
+    fts_free(short_data_cg);
+    fts_free(short_data_cc);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    return ret;
+}
+
+static int gti_selftest(void *private_data, struct gti_selftest_cmd *cmd)
+{
+    int ret = 0;
+    cmd->result = GTI_SELFTEST_RESULT_FAIL;
+
+    ret = goog_selfttest_test_raw();
+    if (ret < 0) {
+        FTS_ERROR("goog_selfttest_test_raw failed,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = goog_selfttest_test_short();
+    if (ret < 0) {
+        FTS_ERROR("goog_selfttest_test_short failed,ret=%d\n", ret);
+        return ret;
+    }
+
+    cmd->result = GTI_SELFTEST_RESULT_PASS;
+    return 0;
+}
+
+// Reference: proc_test_fwver_show
+static int goog_internel_get_fw_version(u8 *fw_major_ver,
+        u8 *fw_minor_ver, u8 *vendor_id)
+{
+    int ret = 0;
+
+    ret = fts_read_reg(FTS_REG_FW_MAJOR_VER, fw_major_ver);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read major version fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_read_reg(FTS_REG_FW_MINOR_VER, fw_minor_ver);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read minor version fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_read_reg(FTS_REG_VENDOR_ID, vendor_id);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read vendor id fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    FTS_INFO("Vendor ID:%#02x, Firmware Ver:%02x.%02x\n",
+             *vendor_id, *fw_major_ver, *fw_minor_ver);
+exit:
+    return ret;
+}
+
+// Reference: fts_driverinfo_show
+static int gti_get_fw_version(void *private_data,
+                              struct gti_fw_version_cmd *cmd)
+{
+    int ret = 0;
+    int count = 0;
+    u8 fw_major_ver = 0;
+    u8 fw_minor_ver = 0;
+    u8 vendor_id = 0;
+    struct fts_ts_data *ts_data = private_data;
+    struct fts_ts_platform_data *pdata = ts_data->pdata;
+    char *buf;
+    size_t buf_size = 0;
+
+    ret = goog_internel_get_fw_version(&fw_major_ver,
+        &fw_minor_ver, &vendor_id);
+    if (ret < 0)
+      goto exit;
+
+    buf = cmd->buffer;
+    buf_size = sizeof(cmd->buffer);
+
+    count += snprintf(buf + count, buf_size - count, "\n");
+
+    count += snprintf(buf + count, buf_size - count, "Firmware Ver:%02x.%02x\n",
+                      fw_major_ver, fw_minor_ver);
+
+    count += snprintf(buf + count, buf_size - count, "Vendor ID:%#02x\n",
+                      vendor_id);
+
+    count += snprintf(buf + count, buf_size - count, "Driver Ver:%s\n",
+                      FTS_DRIVER_VERSION);
+
+    count += snprintf(buf + count, buf_size - count, "Resolution:(%d,%d)~(%d,%d)\n",
+                      pdata->x_min, pdata->y_min, pdata->x_max, pdata->y_max);
+
+    count += snprintf(buf + count, buf_size - count, "Max Touches:%d\n",
+                      pdata->max_touch_number);
+
+    count += snprintf(buf + count, buf_size - count,
+                      "reset gpio:%d,int gpio:%d,irq:%d\n",
+                      pdata->reset_gpio, pdata->irq_gpio, ts_data->irq);
+
+    count += snprintf(buf + count, buf_size - count, "IC ID:0x%02x%02x\n",
+                      ts_data->ic_info.ids.chip_idh,
+                      ts_data->ic_info.ids.chip_idl);
+
+    count += snprintf(buf + count, buf_size - count,
+                      "BUS:%s,mode:%d,max_freq:%d\n", "SPI",
+                      ts_data->spi->mode, ts_data->spi->max_speed_hz);
+
+    count += google_internal_sttw_setting_read(buf + count, buf_size - count);
+exit:
+
+    return ret;
+}
+
+// Reference: fts_irq_store
+static int gti_set_irq_mode(void *private_data,
+                              struct gti_irq_cmd *cmd)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+
+    mutex_lock(&input_dev->mutex);
+    if (cmd->setting == GTI_IRQ_MODE_ENABLE) {
+        FTS_INFO("enable irq");
+        fts_irq_enable();
+    } else {
+        FTS_INFO("disable irq");
+        fts_irq_disable();
+    }
+    mutex_unlock(&input_dev->mutex);
+    return 0;
+}
+
+// Reference: fts_irq_show
+static int gti_get_irq_mode(void *private_data,
+                              struct gti_irq_cmd *cmd)
+{
+    cmd->setting = fts_data->irq_disabled ? GTI_IRQ_MODE_DISABLE
+        : GTI_IRQ_MODE_ENABLE;
+
+    return 0;
+}
+
+// Reference: fts_hw_reset_show
+static int gti_reset(void *private_data, struct gti_reset_cmd *cmd)
+{
+    struct input_dev *input_dev = fts_data->input_dev;
+    struct fts_ts_data *ts_data = private_data;
+    int ret = 0;
+
+    mutex_lock(&input_dev->mutex);
+    if (cmd->setting == GTI_RESET_MODE_SW) {
+      ret = fts_write_reg(FTS_TMP_REG_SOFT_RESET, 0xAA);
+      if (ret < 0) {
+        FTS_ERROR("write 0xAA to reg 0xFC fails");
+        goto exit;
+      }
+
+      ret = fts_write_reg(FTS_TMP_REG_SOFT_RESET, 0x66);
+      if (ret < 0) {
+        FTS_ERROR("write 0x66 to reg 0xFC fails");
+        goto exit;
+      }
+    } else if (cmd->setting == GTI_RESET_MODE_HW || cmd->setting == GTI_RESET_MODE_AUTO) {
+      fts_reset_proc(0);
+      fts_update_feature_setting(ts_data);
+    } else {
+      ret = -EOPNOTSUPP;
+    }
+
+exit:
+    mutex_unlock(&input_dev->mutex);
+
+    return ret;
+}
+
+// Reference: proc_grip_read
+static int gti_get_grip_mode(void *private_data, struct gti_grip_cmd *cmd)
+{
+    struct fts_ts_data *ts_data = fts_data;
+
+    cmd->setting = (ts_data->enable_fw_grip % 2) ?
+        GTI_GRIP_ENABLE : GTI_GRIP_DISABLE;
+
+    return 0;
+}
+
+// Reference: proc_grip_write
+static int gti_set_grip_mode(void *private_data, struct gti_grip_cmd *cmd)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+
+    ts_data->enable_fw_grip = (cmd->setting == GTI_GRIP_ENABLE) ?
+        FW_GRIP_ENABLE : FW_GRIP_DISABLE;
+
+    FTS_INFO("switch fw_grip to %u\n", ts_data->enable_fw_grip);
+
+    ret = fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
+// Reference:
+static int gti_ping(void *private_data, struct gti_ping_cmd *cmd)
+{
+    int ret = 0;
+    u8 fw_major_ver = 0;
+    u8 fw_minor_ver = 0;
+    u8 vendor_id = 0;
+
+    ret = goog_internel_get_fw_version(&fw_major_ver,
+        &fw_minor_ver, &vendor_id);
+
+    return ret;
+}
+
+// Reference:
+static int gti_get_sensing_mode(void *private_data, struct gti_sensing_cmd *cmd)
+{
+    struct fts_ts_data *ts_data = fts_data;
+
+    cmd->setting = (!ts_data->is_deepsleep) ?
+        GTI_SENSING_MODE_ENABLE : GTI_SENSING_MODE_DISABLE;
+    return 0;
+}
+
+// Reference:
+static int gti_set_sensing_mode(void *private_data, struct gti_sensing_cmd *cmd)
+{
+    if (cmd->setting == GTI_SENSING_MODE_ENABLE) {
+        goog_fts_ts_resume(NULL);
+    } else {
+        goog_fts_ts_suspend(NULL);
+    }
+
+    return 0;
+}
+
+static void goog_register_options(struct gti_optional_configuration *options,
+    struct fts_ts_data *ts)
+{
+    //options->calibrate = gti_calibrate;
+    //options->get_context_driver = gti_get_context_driver;
+    //options->get_coord_filter_enabled = get_coord_filter_enabled;
+    options->get_fw_version = gti_get_fw_version;
+    options->get_grip_mode = gti_get_grip_mode;
+    options->get_irq_mode = gti_get_irq_mode;
+    //options->get_mutual_sensor_data = get_mutual_sensor_data;
+    options->get_palm_mode = gti_get_palm_mode;
+    options->get_scan_mode = gti_get_scan_mode;
+    options->get_screen_protector_mode = gti_get_screen_protector_mode;
+    //options->get_self_sensor_data = get_self_sensor_data;
+    options->get_sensing_mode = gti_get_sensing_mode;
+    options->ping = gti_ping;
+    //options->post_irq_thread_fn = goodix_ts_post_threadirq_func;
+    options->reset = gti_reset;
+    options->selftest = gti_selftest;
+    //options->set_continuous_report = set_continuous_report;
+    //options->set_coord_filter_enabled = set_coord_filter_enabled;
+    //options->set_gesture_config = syna_set_gesture_config;
+    options->set_grip_mode = gti_set_grip_mode;
+    options->set_irq_mode = gti_set_irq_mode;
+    options->set_palm_mode = gti_set_palm_mode;
+    //options->set_panel_speed_mode = gti_set_panel_speed_mode;
+    //options->set_report_rate = gti_set_report_rate;
+    options->set_scan_mode = gti_set_scan_mode;
+    options->set_screen_protector_mode = gti_set_screen_protector_mode;
+    options->set_sensing_mode = gti_set_sensing_mode;
+}
+
+static int gti_default_handler(void *private_data, enum gti_cmd_type cmd_type,
+    struct gti_union_cmd_data *cmd)
+{
+    int ret = -EOPNOTSUPP;
+
+    switch (cmd_type) {
+    case GTI_CMD_NOTIFY_DISPLAY_STATE:
+    case GTI_CMD_NOTIFY_DISPLAY_VREFRESH:
+    case GTI_CMD_SET_HEATMAP_ENABLED:
+        ret = 0;
+        break;
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+
+void goog_fts_input_report_b(struct fts_ts_data *data)
+{
+    int i = 0;
+    int touchs = 0;
+    bool va_reported = false;
+    u32 max_touch_num = data->pdata->max_touch_number;
+    struct ts_event *events = data->events;
+    struct goog_touch_interface *gti = data->gti;
+    struct input_dev *input_dev = data->input_dev;
+
+    goog_input_lock(gti);
+
+    goog_input_set_timestamp(gti, input_dev, data->coords_timestamp);
+
+    for (i = 0; i < data->touch_point; i++) {
+        if (EVENT_DOWN(events[i].flag)) {
+            goog_input_mt_slot(gti, input_dev, events[i].id);
+            goog_input_mt_report_slot_state(gti, input_dev, MT_TOOL_FINGER, true);
+
+            goog_input_report_abs(gti, input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
+            goog_input_report_abs(gti, input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
+            goog_input_report_abs(gti, input_dev, ABS_MT_POSITION_X, events[i].x);
+            goog_input_report_abs(gti, input_dev, ABS_MT_POSITION_Y, events[i].y);
+            goog_input_report_abs(gti, input_dev, ABS_MT_ORIENTATION,
+                                  (s16) (((s8) events[i].orientation) * 2048 / 45));
+
+            touchs |= BIT(events[i].id);
+            data->touchs |= BIT(events[i].id);
+            if ((data->log_level >= 2) ||
+                ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
+              FTS_DEBUG("[B]P%d(%d, %d)[ma:%d(%d),mi:%d(%d),p:%d,o:%d] DOWN!",
+                        events[i].id,
+                        events[i].x,
+                        events[i].y,
+                        events[i].major, events[i].major / data->pdata->mm2px,
+                        events[i].minor, events[i].minor / data->pdata->mm2px,
+                        events[i].p,
+                        events[i].orientation);
+            }
+        } else {  //EVENT_UP
+            goog_input_mt_slot(gti, input_dev, events[i].id);
+            goog_input_mt_report_slot_state(gti, input_dev, MT_TOOL_FINGER, false);
+
+            data->touchs &= ~BIT(events[i].id);
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[B1]P%d UP!", events[i].id);
+            }
+        }
+    }
+
+    if (unlikely(data->touchs ^ touchs)) {
+        for (i = 0; i < max_touch_num; i++)  {
+            if (BIT(i) & (data->touchs ^ touchs)) {
+                if (data->log_level >= 1) {
+                    FTS_DEBUG("[B2]P%d UP!", i);
+                }
+                va_reported = true;
+                goog_input_mt_slot(gti, input_dev, i);
+                goog_input_mt_report_slot_state(gti, input_dev, MT_TOOL_FINGER, false);
+            }
+        }
+    }
+    data->touchs = touchs;
+
+    if (va_reported) {
+        /* touchs==0, there's no point but key */
+        if (EVENT_NO_DOWN(data) || (!touchs)) {
+            if (data->log_level >= 1) {
+                FTS_DEBUG("[B]Points All Up!");
+            }
+            goog_input_report_key(gti, input_dev, BTN_TOUCH, 0);
+        } else {
+            goog_input_report_key(gti, input_dev, BTN_TOUCH, 1);
+        }
+    }
+    goog_input_sync(gti, input_dev);
+
+    goog_input_unlock(gti);
+}
+
+int goog_parse_dt(struct device_node *np, struct fts_ts_platform_data *pdata)
+{
+    int panel_id = -1;
+
+    if (!np || !pdata)
+      return -EPROBE_DEFER;
+
+    panel_id = goog_get_panel_id(np);
+    if (panel_id < 0) {
+        FTS_ERROR("Unable to get panel");
+        return -EPROBE_DEFER;
+    }
+    pdata->panel_id = panel_id;
+
+    goog_get_firmware_name(np, panel_id, pdata->fw_name,
+            sizeof(pdata->fw_name));
+    goog_get_test_limits_name(np, panel_id, pdata->test_limits_name,
+            sizeof(pdata->test_limits_name));
+
+    return 0;
+}
+
+void goog_gti_probe(struct fts_ts_data *ts_data)
+{
+    struct gti_optional_configuration *options = NULL;
+    struct fts_ts_platform_data *pdata = NULL;
+    int retval = 0;
+    int irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+
+    if (!ts_data || !ts_data->dev || !ts_data->pdata) {
+        FTS_ERROR("ts_data / ts_data->dev / ts_data->pdata is null");
+        goto err;
+    }
+
+    /* release the interrupt and register the gti irq later. */
+    free_irq(ts_data->irq, ts_data);
+
+    options = devm_kzalloc(ts_data->dev,
+        sizeof(struct gti_optional_configuration), GFP_KERNEL);
+    if (!options) {
+        FTS_ERROR("options devm_kzalloc fail");
+        goto err;
+    }
+    goog_register_options(options, ts_data);
+
+    ts_data->gti = goog_touch_interface_probe(
+        ts_data, ts_data->dev, ts_data->input_dev,
+        gti_default_handler, options);
+    if (!ts_data->gti) {
+        FTS_ERROR("Failed to initialize GTI");
+        goto err;
+    }
+
+    retval = goog_pm_register_notification(ts_data->gti, &goog_fts_dev_pm_ops);
+    if (retval < 0)
+        FTS_ERROR("Failed to register GTI pm");
+
+
+    FTS_INFO("Register IRQ by GTI.");
+    pdata = ts_data->pdata;
+    ts_data->irq = gpio_to_irq(pdata->irq_gpio);
+
+    FTS_INFO("gti register irq:%d, flag:%x", ts_data->irq, irq_flags);
+    retval = goog_devm_request_threaded_irq(ts_data->gti, ts_data->dev,
+        ts_data->irq, goog_fts_irq_ts, goog_fts_irq_handler,
+        irq_flags, "fts_ts", ts_data);
+
+    if (retval < 0)
+        FTS_ERROR("Failed to request GTI IRQ");
+
+err:
+    if (options)
+      devm_kfree(ts_data->dev, options);
+}
+
+void goog_gti_remove(struct fts_ts_data *ts_data)
+{
+    if (!ts_data->gti)
+      return;
+
+    goog_pm_unregister_notification(ts_data->gti);
+
+    goog_devm_free_irq(ts_data->gti, ts_data->dev, ts_data->irq);
+    goog_touch_interface_remove(ts_data->gti);
+    ts_data->gti = NULL;
+}
+
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+
diff --git a/ft3683u/focaltech_point_report_check.c b/ft3683u/focaltech_point_report_check.c
new file mode 100644
index 0000000..1d1ee51
--- /dev/null
+++ b/ft3683u/focaltech_point_report_check.c
@@ -0,0 +1,129 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*****************************************************************************
+*
+* File Name: focaltech_point_report_check.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-11-16
+*
+* Abstract: point report check function
+*
+* Version: v1.0
+*
+* Revision History:
+*
+*****************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+#if FTS_POINT_REPORT_CHECK_EN
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define POINT_REPORT_CHECK_WAIT_TIME                200    /* unit:ms */
+#define PRC_INTR_INTERVALS                          100    /* unit:ms */
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+/*****************************************************************************
+*  Name: fts_prc_func
+*  Brief: fts point report check work func, report whole up of points
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+static void fts_prc_func(struct work_struct *work)
+{
+    struct fts_ts_data *ts_data = container_of(work,
+                                  struct fts_ts_data, prc_work.work);
+    unsigned long cur_jiffies = jiffies;
+    unsigned long intr_timeout = msecs_to_jiffies(PRC_INTR_INTERVALS);
+
+    intr_timeout += ts_data->intr_jiffies;
+    if (time_after(cur_jiffies, intr_timeout)) {
+        fts_release_all_finger();
+        ts_data->prc_mode = 0;
+        //FTS_DEBUG("interval:%lu", (cur_jiffies - ts_data->intr_jiffies) * 1000 / HZ);
+    } else {
+        queue_delayed_work(ts_data->ts_workqueue, &ts_data->prc_work,
+                           msecs_to_jiffies(POINT_REPORT_CHECK_WAIT_TIME));
+        ts_data->prc_mode = 1;
+    }
+}
+
+/*****************************************************************************
+*  Name: fts_prc_queue_work
+*  Brief: fts point report check queue work, call it when interrupt comes
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+void fts_prc_queue_work(struct fts_ts_data *ts_data)
+{
+    ts_data->intr_jiffies = jiffies;
+    if (!ts_data->prc_mode) {
+        queue_delayed_work(ts_data->ts_workqueue, &ts_data->prc_work,
+                           msecs_to_jiffies(POINT_REPORT_CHECK_WAIT_TIME));
+        ts_data->prc_mode = 1;
+    }
+}
+
+/*****************************************************************************
+*  Name: fts_point_report_check_init
+*  Brief:
+*  Input:
+*  Output:
+*  Return: < 0: Fail to create esd check queue
+*****************************************************************************/
+int fts_point_report_check_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    if (ts_data->ts_workqueue) {
+        INIT_DELAYED_WORK(&ts_data->prc_work, fts_prc_func);
+    } else {
+        FTS_ERROR("fts workqueue is NULL, can't run point report check function");
+        return -EINVAL;
+    }
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+/*****************************************************************************
+*  Name: fts_point_report_check_exit
+*  Brief:
+*  Input:
+*  Output:
+*  Return:
+*****************************************************************************/
+int fts_point_report_check_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+
+    FTS_FUNC_EXIT();
+    return 0;
+}
+#endif /* FTS_POINT_REPORT_CHECK_EN */
+
diff --git a/ft3683u/focaltech_spi.c b/ft3683u/focaltech_spi.c
new file mode 100644
index 0000000..2373d2a
--- /dev/null
+++ b/ft3683u/focaltech_spi.c
@@ -0,0 +1,525 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/************************************************************************
+*
+* File Name: focaltech_spi.c
+*
+*    Author: FocalTech Driver Team
+*
+*   Created: 2019-03-21
+*
+*  Abstract: new spi protocol communication with TP
+*
+*   Version: v1.0
+*
+* Revision History:
+*
+************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_core.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define SPI_RETRY_NUMBER            3
+#define CS_HIGH_DELAY               150 /* unit: us */
+
+/* The touch data size is 1451 bytes = (cap header(91 bytes) + MS(1088 bytes) +
+ * water_SS(136 bytes) + normal-SS(136 bytes)), so set SPI_BUF_LENGTH larger
+ * than 1451 to prevent SPI buff from being allocated and freed on every touch
+ * data transfer.
+ */
+#define SPI_BUF_LENGTH              1536 /* ALIGN(1451, 256) */
+
+#define DATA_CRC_EN                 0x20
+#define WRITE_CMD                   0x00
+#define READ_CMD                    (0x80 | DATA_CRC_EN)
+
+#define SPI_DUMMY_BYTE              3
+#define SPI_HEADER_LENGTH           6   /*CRC*/
+/*****************************************************************************
+* Private enumerations, structures and unions using typedef
+*****************************************************************************/
+
+/*****************************************************************************
+* Static variables
+*****************************************************************************/
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+/* spi interface */
+static int fts_spi_transfer(u8 *tx_buf, u8 *rx_buf, u32 len)
+{
+    int ret = 0;
+    struct spi_device *spi = fts_data->spi;
+    struct spi_message msg;
+    struct spi_transfer xfer = {
+        .tx_buf = tx_buf,
+        .rx_buf = rx_buf,
+        .len    = len,
+        .bits_per_word = len >= 64 ? 32 : 8,
+    };
+    spi_message_init(&msg);
+    spi_message_add_tail(&xfer, &msg);
+
+    ret = spi_sync(spi, &msg);
+    if (ret) {
+        FTS_ERROR("spi_sync fail,ret:%d", ret);
+        return ret;
+    }
+
+    return ret;
+}
+
+static void fts_spi_buf_show(u8 *data, int datalen)
+{
+    int i = 0;
+    int last_print_index = 0;
+    int count = 0;
+    int size = 0;
+    int max_cnt = 256;
+    int tmpbuf_size = 0;
+    char *tmpbuf = NULL;
+
+    if (!data || (datalen <= 0)) {
+        FTS_ERROR("data/datalen is invalid");
+        return;
+    }
+
+    size = (datalen > max_cnt) ? max_cnt : datalen;
+    tmpbuf_size = size * 3;
+    tmpbuf = kzalloc(tmpbuf_size, GFP_KERNEL);
+    if (!tmpbuf) {
+        FTS_ERROR("tmpbuf zalloc fail");
+        return;
+    }
+
+    for (i = 0; i < size; i++) {
+        count += scnprintf(tmpbuf + count, tmpbuf_size - count, "%02X ", data[i]);
+        if (i % 16 == 15) {
+          FTS_DEBUG("%03d, %s", last_print_index, tmpbuf + last_print_index);
+          last_print_index = count;
+        }
+    }
+    if (last_print_index != count)
+        FTS_DEBUG("%03d, %s", last_print_index, tmpbuf + last_print_index);
+
+    if (tmpbuf) {
+        kfree(tmpbuf);
+        tmpbuf = NULL;
+    }
+}
+
+static void crckermit(u8 *data, u32 len, u16 *crc_out)
+{
+    u32 i = 0;
+    u32 j = 0;
+    u16 crc = 0xFFFF;
+
+    for ( i = 0; i < len; i++) {
+        crc ^= data[i];
+        for (j = 0; j < 8; j++) {
+            if (crc & 0x01)
+                crc = (crc >> 1) ^ 0x8408;
+            else
+                crc = (crc >> 1);
+        }
+    }
+
+    *crc_out = crc;
+}
+
+static int rdata_check(u8 *rdata, u32 rlen)
+{
+    u16 crc_calc = 0;
+    u16 crc_read = 0;
+
+    crckermit(rdata, rlen - 2, &crc_calc);
+    crc_read = (u16)(rdata[rlen - 1] << 8) + rdata[rlen - 2];
+    if (crc_calc != crc_read) {
+        FTS_ERROR("crc_calc = 0x%X, crc_read=0x%X",crc_calc, crc_read);
+        fts_spi_buf_show(rdata, rlen);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+int fts_write(u8 *writebuf, u32 writelen)
+{
+    int ret = 0;
+    int i = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 *txbuf = NULL;
+    u8 *rxbuf = NULL;
+    u32 txlen = 0;
+    u32 txlen_need = writelen + SPI_HEADER_LENGTH + ts_data->dummy_byte;
+    u32 datalen = writelen - 1;
+
+    if (!writebuf || !writelen) {
+        FTS_ERROR("writebuf/len is invalid");
+        return -EINVAL;
+    }
+    /* 4 bytes alignment for DMA mode. */
+    if (txlen_need > 64) {
+        txlen_need = ALIGN(txlen_need, 4);
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+    if (txlen_need > SPI_BUF_LENGTH) {
+        txbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == txbuf) {
+            FTS_ERROR("txbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_write;
+        }
+
+        rxbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == rxbuf) {
+            FTS_ERROR("rxbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_write;
+        }
+    } else {
+        txbuf = ts_data->bus_tx_buf;
+        rxbuf = ts_data->bus_rx_buf;
+        memset(txbuf, 0x0, SPI_BUF_LENGTH);
+        memset(rxbuf, 0x0, SPI_BUF_LENGTH);
+    }
+
+    txbuf[txlen++] = writebuf[0];
+    txbuf[txlen++] = WRITE_CMD;
+    txbuf[txlen++] = (datalen >> 8) & 0xFF;
+    txbuf[txlen++] = datalen & 0xFF;
+    if (datalen > 0) {
+        txlen = txlen + SPI_DUMMY_BYTE;
+        memcpy(&txbuf[txlen], &writebuf[1], datalen);
+        txlen = txlen + datalen;
+    }
+    /* 4 bytes alignment for DMA mode. */
+    if (txlen > 64) {
+        txlen = ALIGN(txlen, 4);
+    }
+
+    for (i = 0; i < SPI_RETRY_NUMBER; i++) {
+        ret = fts_spi_transfer(txbuf, rxbuf, txlen);
+        if ((0 == ret) && ((rxbuf[3] & 0xA0) == 0)) {
+            break;
+        } else {
+            FTS_DEBUG("data write(addr:%x),status:%x,retry:%d,ret:%d",
+                      writebuf[0], rxbuf[3], i, ret);
+            if (ret == -EACCES)
+                break;
+            ret = -EIO;
+            udelay(CS_HIGH_DELAY);
+        }
+    }
+    if (ret < 0) {
+        FTS_ERROR("data write(addr:%x) fail,status:%x,ret:%d",
+                  writebuf[0], rxbuf[3], ret);
+    }
+
+err_write:
+    if (txlen_need > SPI_BUF_LENGTH) {
+        if (txbuf) {
+            kfree(txbuf);
+            txbuf = NULL;
+        }
+
+        if (rxbuf) {
+            kfree(rxbuf);
+            rxbuf = NULL;
+        }
+    }
+
+    udelay(CS_HIGH_DELAY);
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+int fts_write_reg(u8 addr, u8 value)
+{
+    u8 writebuf[2] = { 0 };
+
+    writebuf[0] = addr;
+    writebuf[1] = value;
+    return fts_write(writebuf, 2);
+}
+
+int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen)
+{
+    int ret = 0;
+    int i = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 *txbuf = NULL;
+    u8 *rxbuf = NULL;
+    u32 txlen = 0;
+    u32 aligned_txlen = 0;
+    u32 aligned_datalen = 0;
+    u32 txlen_need = datalen + SPI_HEADER_LENGTH + ts_data->dummy_byte;
+    u8 ctrl = READ_CMD;
+    u32 dp = 0;
+
+    if (!cmd || !cmdlen || !data || !datalen) {
+        FTS_ERROR("cmd/cmdlen/data/datalen is invalid");
+        return -EINVAL;
+    }
+    /* 4 bytes alignment for DMA mode */
+    if (txlen_need > 64) {
+        txlen_need = ALIGN(txlen_need, 4);
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+    if (txlen_need > SPI_BUF_LENGTH) {
+        txbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == txbuf) {
+            FTS_ERROR("txbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_read;
+        }
+
+        rxbuf = kzalloc(txlen_need, GFP_KERNEL);
+        if (NULL == rxbuf) {
+            FTS_ERROR("rxbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_read;
+        }
+    } else {
+        txbuf = ts_data->bus_tx_buf;
+        rxbuf = ts_data->bus_rx_buf;
+        memset(txbuf, 0x0, SPI_BUF_LENGTH);
+        memset(rxbuf, 0x0, SPI_BUF_LENGTH);
+    }
+
+    txbuf[txlen++] = cmd[0];
+    txbuf[txlen++] = ctrl;
+    txbuf[txlen++] = (datalen >> 8) & 0xFF;
+    txbuf[txlen++] = datalen & 0xFF;
+    dp = txlen + SPI_DUMMY_BYTE;
+    txlen = dp + datalen;
+    if (ctrl & DATA_CRC_EN) {
+        txlen = txlen + 2;
+    }
+    aligned_txlen = txlen;
+    aligned_datalen = datalen;
+    /* 4 bytes alignment for DMA mode */
+    if (aligned_txlen > 64) {
+        aligned_txlen = ALIGN(aligned_txlen, 4);
+        /* Calculate new datalen for CRC checking code. */
+        aligned_datalen += aligned_txlen - txlen;
+        txbuf[2] = (aligned_datalen >> 8) & 0xFF;
+        txbuf[3] = aligned_datalen & 0xFF;
+    }
+
+    for (i = 0; i < SPI_RETRY_NUMBER; i++) {
+        ret = fts_spi_transfer(txbuf, rxbuf, aligned_txlen);
+        if ((0 == ret) && ((rxbuf[3] & 0xA0) == 0)) {
+            memcpy(data, &rxbuf[dp], datalen);
+            /* crc check */
+            if (ctrl & DATA_CRC_EN) {
+                ret = rdata_check(&rxbuf[dp], aligned_txlen - dp);
+                if (ret < 0) {
+                    FTS_DEBUG("data read(addr:%x) crc abnormal,retry:%d",
+                              cmd[0], i);
+                    udelay(CS_HIGH_DELAY);
+                    continue;
+                }
+            }
+            break;
+        } else {
+            FTS_DEBUG("data read(addr:%x) status:%x,retry:%d,ret:%d",
+                      cmd[0], rxbuf[3], i, ret);
+            if (ret == -EACCES)
+                break;
+            ret = -EIO;
+            udelay(CS_HIGH_DELAY);
+        }
+    }
+
+    if (ret < 0) {
+        FTS_ERROR("data read(addr:%x) %s,status:%x,ret:%d", cmd[0],
+                  (i >= SPI_RETRY_NUMBER) ? "crc abnormal" : "fail",
+                  rxbuf[3], ret);
+    }
+
+err_read:
+    if (txlen_need > SPI_BUF_LENGTH) {
+        if (txbuf) {
+            kfree(txbuf);
+            txbuf = NULL;
+        }
+
+        if (rxbuf) {
+            kfree(rxbuf);
+            rxbuf = NULL;
+        }
+    }
+
+    udelay(CS_HIGH_DELAY);
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+int fts_read_reg(u8 addr, u8 *value)
+{
+    return fts_read(&addr, 1, value, 1);
+}
+
+
+int fts_spi_transfer_direct(u8 *writebuf, u32 writelen, u8 *readbuf, u32 readlen)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    u8 *txbuf = NULL;
+    u8 *rxbuf = NULL;
+    bool read_cmd = (readbuf && readlen) ? 1 : 0;
+    u32 txlen = (read_cmd) ? (writelen + readlen) : writelen;
+
+    if (!writebuf || !writelen) {
+        FTS_ERROR("writebuf/len is invalid");
+        return -EINVAL;
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+    if (txlen > SPI_BUF_LENGTH) {
+        txbuf = kzalloc(txlen, GFP_KERNEL);
+        if (NULL == txbuf) {
+            FTS_ERROR("txbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_spi_dir;
+        }
+
+        rxbuf = kzalloc(txlen, GFP_KERNEL);
+        if (NULL == rxbuf) {
+            FTS_ERROR("rxbuf malloc fail");
+            ret = -ENOMEM;
+            goto err_spi_dir;
+        }
+    } else {
+        txbuf = ts_data->bus_tx_buf;
+        rxbuf = ts_data->bus_rx_buf;
+        memset(txbuf, 0x0, SPI_BUF_LENGTH);
+        memset(rxbuf, 0x0, SPI_BUF_LENGTH);
+    }
+
+    memcpy(txbuf, writebuf, writelen);
+    ret = fts_spi_transfer(txbuf, rxbuf, txlen);
+    if (ret < 0) {
+        FTS_ERROR("data read(addr:%x) fail,status:%x,ret:%d", txbuf[0], rxbuf[3], ret);
+        goto err_spi_dir;
+    }
+
+    if (read_cmd) {
+        memcpy(readbuf, rxbuf, txlen);
+    }
+
+    ret = 0;
+err_spi_dir:
+    if (txlen > SPI_BUF_LENGTH) {
+        if (txbuf) {
+            kfree(txbuf);
+            txbuf = NULL;
+        }
+
+        if (rxbuf) {
+            kfree(rxbuf);
+            rxbuf = NULL;
+        }
+    }
+
+    udelay(CS_HIGH_DELAY);
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+
+int fts_bus_set_speed(struct fts_ts_data *ts_data, u32 speed)
+{
+    int ret = 0;
+
+    if (!ts_data) {
+        FTS_ERROR("ts_data is null");
+        return -EINVAL;
+    }
+
+    mutex_lock(&ts_data->bus_lock);
+
+    if (speed > 0) {
+        ts_data->spi->max_speed_hz = speed;
+    } else {
+        ts_data->spi->max_speed_hz = ts_data->spi_speed;
+    }
+
+    ret = spi_setup(ts_data->spi);
+    if (ret < 0) {
+        FTS_ERROR("spi speed set fail,ret:%d", ret);
+    }
+
+    mutex_unlock(&ts_data->bus_lock);
+    return ret;
+}
+
+
+int fts_bus_init(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    ts_data->bus_tx_buf = kzalloc(SPI_BUF_LENGTH, GFP_KERNEL);
+    if (NULL == ts_data->bus_tx_buf) {
+        FTS_ERROR("failed to allocate memory for bus_tx_buf");
+        return -ENOMEM;
+    }
+
+    ts_data->bus_rx_buf = kzalloc(SPI_BUF_LENGTH, GFP_KERNEL);
+    if (NULL == ts_data->bus_rx_buf) {
+        FTS_ERROR("failed to allocate memory for bus_rx_buf");
+        return -ENOMEM;
+    }
+
+    ts_data->dummy_byte = SPI_DUMMY_BYTE;
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
+int fts_bus_exit(struct fts_ts_data *ts_data)
+{
+    FTS_FUNC_ENTER();
+    if (ts_data && ts_data->bus_tx_buf) {
+        kfree(ts_data->bus_tx_buf);
+        ts_data->bus_tx_buf = NULL;
+    }
+
+    if (ts_data && ts_data->bus_rx_buf) {
+        kfree(ts_data->bus_rx_buf);
+        ts_data->bus_rx_buf = NULL;
+    }
+    FTS_FUNC_EXIT();
+    return 0;
+}
+
diff --git a/ft3683u/focaltech_test/Makefile b/ft3683u/focaltech_test/Makefile
new file mode 100644
index 0000000..062610e
--- /dev/null
+++ b/ft3683u/focaltech_test/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_test.o
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_test_ini.o
+obj-$(CONFIG_TOUCHSCREEN_FTS) += supported_ic/
diff --git a/ft3683u/focaltech_test/focaltech_test.c b/ft3683u/focaltech_test/focaltech_test.c
new file mode 100644
index 0000000..968416e
--- /dev/null
+++ b/ft3683u/focaltech_test/focaltech_test.c
@@ -0,0 +1,4692 @@
+/*
+ *
+ * FocalTech TouchScreen driver.
+ *
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/************************************************************************
+*
+* File Name: focaltech_test.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Modify:
+*
+* Abstract: create char device and proc node for  the comm between APK and TP
+*
+************************************************************************/
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+#include "focaltech_test.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+struct fts_test *fts_ftest;
+
+struct test_funcs *test_func_list[] = {
+    &test_func_ft5672,
+};
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+
+/*****************************************************************************
+* functions body
+*****************************************************************************/
+void sys_delay(int ms)
+{
+    msleep(ms);
+}
+
+int fts_abs(int value)
+{
+    if (value < 0)
+        value = 0 - value;
+
+    return value;
+}
+
+void *fts_malloc(size_t size)
+{
+    return kzalloc(size, GFP_KERNEL);
+}
+
+void fts_free_proc(void *p)
+{
+    return kfree(p);
+}
+
+void print_buffer(int *buffer, int length, int line_num)
+{
+    int i = 0;
+    int j = 0;
+    int tmpline = 0;
+    char *tmpbuf = NULL;
+    int tmplen = 0;
+    int cnt = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata && tdata->ts_data && (tdata->ts_data->log_level < 3)) {
+        return;
+    }
+
+    if ((NULL == buffer) || (length <= 0)) {
+        FTS_TEST_DBG("buffer/length(%d) fail", length);
+        return;
+    }
+
+    tmpline = line_num ? line_num : length;
+    tmplen = tmpline * 6 + 128;
+    tmpbuf = kzalloc(tmplen, GFP_KERNEL);
+
+    for (i = 0; i < length; i = i + tmpline) {
+        cnt = 0;
+        for (j = 0; j < tmpline; j++) {
+            cnt += snprintf(tmpbuf + cnt, tmplen - cnt, "%5d ", buffer[i + j]);
+            if ((cnt >= tmplen) || ((i + j + 1) >= length))
+                break;
+        }
+        FTS_TEST_DBG("%s", tmpbuf);
+    }
+
+    if (tmpbuf) {
+        kfree(tmpbuf);
+        tmpbuf = NULL;
+    }
+}
+
+/********************************************************************
+ * test read/write interface
+ *******************************************************************/
+static int fts_test_bus_read(
+    u8 *cmd, int cmdlen, u8 *data, int datalen)
+{
+    int ret = 0;
+
+    ret = fts_read(cmd, cmdlen, data, datalen);
+    if (ret < 0)
+        return ret;
+    else
+        return 0;
+}
+
+static int fts_test_bus_write(u8 *writebuf, int writelen)
+{
+    int ret = 0;
+
+    ret = fts_write(writebuf, writelen);
+    if (ret < 0)
+        return ret;
+    else
+        return 0;
+}
+
+int fts_test_read_reg(u8 addr, u8 *val)
+{
+    return fts_test_bus_read(&addr, 1, val, 1);
+}
+
+int fts_test_write_reg(u8 addr, u8 val)
+{
+    int ret;
+    u8 cmd[2] = {0};
+
+    cmd[0] = addr;
+    cmd[1] = val;
+    ret = fts_test_bus_write(cmd, 2);
+
+    return ret;
+}
+
+int fts_test_read(u8 addr, u8 *readbuf, int readlen)
+{
+    int ret = 0;
+    int i = 0;
+    int packet_length = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    int byte_num = readlen;
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+
+    if (byte_num < BYTES_PER_TIME) {
+        packet_length = byte_num;
+    } else {
+        packet_length = BYTES_PER_TIME;
+    }
+    /* FTS_TEST_DBG("packet num:%d, remainder:%d", packet_num, packet_remainder); */
+
+    ret = fts_test_bus_read(&addr, 1, &readbuf[offset], packet_length);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read buffer fail");
+        return ret;
+    }
+    for (i = 1; i < packet_num; i++) {
+        offset += packet_length;
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            packet_length = packet_remainder;
+        }
+
+        ret = fts_test_bus_read(&addr, 1, &readbuf[offset],
+                                packet_length);
+
+        if (ret < 0) {
+            FTS_TEST_ERROR("read buffer fail");
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+int fts_test_write(u8 addr, u8 *writebuf, int writelen)
+{
+    int ret = 0;
+    int i = 0;
+    u8 *data = NULL;
+    int packet_length = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    int byte_num = writelen;
+
+    data = fts_malloc(BYTES_PER_TIME + 1);
+    if (!data) {
+        FTS_TEST_ERROR("malloc memory for bus write data fail");
+        return -ENOMEM;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+
+    if (byte_num < BYTES_PER_TIME) {
+        packet_length = byte_num;
+    } else {
+        packet_length = BYTES_PER_TIME;
+    }
+    /* FTS_TEST_DBG("packet num:%d, remainder:%d", packet_num, packet_remainder); */
+
+    data[0] = addr;
+    for (i = 0; i < packet_num; i++) {
+        if (i != 0) {
+            data[0] = addr + 1;
+        }
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            packet_length = packet_remainder;
+        }
+        memcpy(&data[1], &writebuf[offset], packet_length);
+
+        ret = fts_test_bus_write(data, packet_length + 1);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write buffer fail");
+            fts_free(data);
+            return ret;
+        }
+
+        offset += packet_length;
+    }
+
+    fts_free(data);
+    return 0;
+}
+
+/********************************************************************
+ * test global function enter work/factory mode
+ *******************************************************************/
+int enter_work_mode(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+    u8 mode = 0;
+    int i = 0;
+    int j = 0;
+
+    FTS_TEST_FUNC_ENTER();
+
+    ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+    if ((ret >= 0) && (0x00 == mode))
+        return 0;
+
+    for (i = 0; i < ENTER_WORK_FACTORY_RETRIES; i++) {
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0x00);
+        if (ret >= 0) {
+            sys_delay(FACTORY_TEST_DELAY);
+            for (j = 0; j < 20; j++) {
+                ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+                if ((ret >= 0) && (mode == FTS_REG_WORKMODE_WORK_VALUE)) {
+                    FTS_TEST_INFO("enter work mode success");
+                    ts_data->work_mode = mode;
+                    return 0;
+                } else {
+                    sys_delay(FACTORY_TEST_DELAY);
+                }
+            }
+        }
+
+        sys_delay(50);
+    }
+
+    if (i >= ENTER_WORK_FACTORY_RETRIES) {
+        FTS_TEST_ERROR("Enter work mode fail");
+        return -EIO;
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+int enter_factory_mode(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    int ret = 0;
+    u8 mode = 0;
+    int i = 0;
+    int j = 0;
+
+    ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+    if ((ret >= 0) && (0x40 == mode))
+        return 0;
+
+    for (i = 0; i < ENTER_WORK_FACTORY_RETRIES; i++) {
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0x40);
+        if (ret >= 0) {
+            sys_delay(FACTORY_TEST_DELAY);
+            for (j = 0; j < 20; j++) {
+                ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &mode);
+                if ((ret >= 0) && (mode == FTS_REG_WORKMODE_FACTORY_VALUE)) {
+                    FTS_TEST_INFO("enter factory mode success");
+                    sys_delay(10);
+                    ts_data->work_mode = mode;
+                    return 0;
+                } else {
+                    sys_delay(FACTORY_TEST_DELAY);
+                }
+            }
+        }
+
+        sys_delay(50);
+    }
+
+    if (i >= ENTER_WORK_FACTORY_RETRIES) {
+        FTS_TEST_ERROR("Enter factory mode fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * read_mass_data - read rawdata/short test data
+ * addr - register addr which read data from
+ * byte_num - read data length, unit:byte
+ * buf - save data
+ *
+ * return 0 if read data succuss, otherwise return error code
+ */
+int read_mass_data(u8 addr, int byte_num, int *buf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 *data = NULL;
+
+    data = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == data) {
+        FTS_TEST_SAVE_ERR("mass data buffer malloc fail\n");
+        return -ENOMEM;
+    }
+
+    /* read rawdata buffer */
+    FTS_TEST_INFO("mass data len:%d", byte_num);
+    ret = fts_test_read(addr, data, byte_num);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read mass data fail\n");
+        goto read_massdata_err;
+    }
+
+    for (i = 0; i < byte_num; i = i + 2) {
+        buf[i >> 1] = (int)(short)((data[i] << 8) + data[i + 1]);
+    }
+
+    ret = 0;
+read_massdata_err:
+    fts_free(data);
+    return ret;
+}
+
+int read_mass_data_u16(u8 addr, int byte_num, int *buf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 *data = NULL;
+
+    data = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == data) {
+        FTS_TEST_SAVE_ERR("mass data buffer malloc fail\n");
+        return -ENOMEM;
+    }
+
+    /* read rawdata buffer */
+    FTS_TEST_INFO("mass data len:%d", byte_num);
+    ret = fts_test_read(addr, data, byte_num);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read mass data fail\n");
+        goto read_massdata_err;
+    }
+
+    for (i = 0; i < byte_num; i = i + 2) {
+        buf[i >> 1] = (int)(u16)((data[i] << 8) + data[i + 1]);
+    }
+
+    ret = 0;
+read_massdata_err:
+    fts_free(data);
+    return ret;
+}
+
+int short_get_adcdata_incell(u8 retval, u8 ch_num, int byte_num, int *adc_buf)
+{
+    int ret = 0;
+    int times = 0;
+    u8 short_state = 0;
+
+    FTS_TEST_FUNC_ENTER();
+
+    /* Start ADC sample */
+    ret = fts_test_write_reg(FACTORY_REG_SHORT_TEST_EN, 0x01);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("start short test fail\n");
+        goto adc_err;
+    }
+
+    sys_delay(ch_num * FACTORY_TEST_DELAY);
+    for (times = 0; times < FACTORY_TEST_RETRY; times++) {
+        ret = fts_test_read_reg(FACTORY_REG_SHORT_TEST_STATE, &short_state);
+        if ((ret >= 0) && (retval == short_state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d",
+                         FACTORY_REG_SHORT_TEST_STATE, short_state, times);
+
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+    }
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("short test timeout, ADC data not OK\n");
+        ret = -EIO;
+        goto adc_err;
+    }
+
+    ret = read_mass_data(FACTORY_REG_SHORT_ADDR, byte_num, adc_buf);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("get short(adc) data fail\n");
+    }
+
+adc_err:
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+/*
+ * wait_state_update - wait fw status update
+ */
+int wait_state_update(u8 retval)
+{
+    int ret = 0;
+    int times = 0;
+    u8 addr = 0;
+    u8 state = 0xFF;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    if (IC_HW_INCELL == tdata->func->hwtype) {
+        addr = FACTORY_REG_PARAM_UPDATE_STATE;
+    } else {
+        addr = FACTORY_REG_PARAM_UPDATE_STATE_TOUCH;
+    }
+
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+        /* Wait register status update */
+        state = 0xFF;
+        ret = fts_test_read_reg(addr, &state);
+        if ((ret >= 0) && (retval == state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", addr, state, times);
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("Wait State Update fail,reg%x=%x\n", addr, state);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * start_scan - start to scan a frame
+ */
+int start_scan(void)
+{
+    int ret = 0;
+    u8 addr = 0;
+    u8 val = 0;
+    u8 finish_val = 0;
+    int times = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    if (SCAN_SC == tdata->func->startscan_mode) {
+        /* sc ic */
+        addr = FACTORY_REG_SCAN_ADDR2;
+        val = 0x01;
+        finish_val = 0x00;
+    } else {
+        addr = DIVIDE_MODE_ADDR;
+        val = 0xC0;
+        finish_val = 0x40;
+    }
+
+    /* write register to start scan */
+    ret = fts_test_write_reg(addr, val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write start scan mode fail\n");
+        return ret;
+    }
+
+    /* Wait for the scan to complete */
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+
+        ret = fts_test_read_reg(addr, &val);
+        if ((ret >= 0) && (val == finish_val)) {
+            break;
+        } else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", addr, val, times);
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("scan timeout\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int read_rawdata(
+    struct fts_test *tdata,
+    u8 off_addr,
+    u8 off_val,
+    u8 rawdata_addr,
+    int byte_num,
+    int *data)
+{
+    int ret = 0;
+
+    /* set line addr or rawdata start addr */
+    ret = fts_test_write_reg(off_addr, off_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write line/start addr fail\n");
+        return ret;
+    }
+
+    if (tdata->func->raw_u16)
+        ret = read_mass_data_u16(rawdata_addr, byte_num, data);
+    else
+        ret = read_mass_data(rawdata_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+int get_rawdata(int *data)
+{
+    int ret = 0;
+    u8 val = 0;
+    u8 addr = 0;
+    u8 rawdata_addr = 0;
+    int byte_num = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    /* enter factory mode */
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* start scanning */
+    ret = start_scan();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scan fail\n");
+        return ret;
+    }
+
+    /* read rawdata */
+    if (IC_HW_INCELL == tdata->func->hwtype) {
+        val = 0xAD;
+        addr = FACTORY_REG_LINE_ADDR;
+        rawdata_addr = FACTORY_REG_RAWDATA_ADDR;
+    } else if (IC_HW_MC_SC == tdata->func->hwtype) {
+        val = 0xAA;
+        addr = FACTORY_REG_LINE_ADDR;
+        rawdata_addr = FACTORY_REG_RAWDATA_ADDR_MC_SC;
+    } else {
+        val = 0x0;
+        addr = FACTORY_REG_RAWDATA_SADDR_SC;
+        rawdata_addr = FACTORY_REG_RAWDATA_ADDR_SC;
+    }
+
+    byte_num = tdata->node.node_num * 2;
+    ret = read_rawdata(tdata, addr, val, rawdata_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+/*
+ * chip_clb - auto clb
+ */
+int chip_clb(void)
+{
+    int ret = 0;
+    u8 val = 0;
+    int times = 0;
+
+    /* start clb */
+    ret = fts_test_write_reg(FACTORY_REG_CLB, 0x04);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("write start clb fail\n");
+        return ret;
+    }
+
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+        ret = fts_test_read_reg(FACTORY_REG_CLB, &val);
+        if ((0 == ret) && (0x02 == val)) {
+            /* clb ok */
+            break;
+        } else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", FACTORY_REG_CLB, val, times);
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("chip clb timeout\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+/*
+ * get_cb_incell - get cb data for incell IC
+ */
+int get_cb_incell(u16 saddr, int byte_num, int *cb_buf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cb_addr = 0;
+    u8 addr_h = 0;
+    u8 addr_l = 0;
+    int read_num = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    int addr = 0;
+    u8 *data = NULL;
+
+    data = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == data) {
+        FTS_TEST_SAVE_ERR("cb buffer malloc fail\n");
+        return -ENOMEM;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+    read_num = BYTES_PER_TIME;
+
+    FTS_TEST_INFO("cb packet:%d,remainder:%d", packet_num, packet_remainder);
+    cb_addr = FACTORY_REG_CB_ADDR;
+    for (i = 0; i < packet_num; i++) {
+        offset = read_num * i;
+        addr = saddr + offset;
+        addr_h = (addr >> 8) & 0xFF;
+        addr_l = addr & 0xFF;
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            read_num = packet_remainder;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_CB_ADDR_H, addr_h);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("write cb addr high fail\n");
+            goto TEST_CB_ERR;
+        }
+        ret = fts_test_write_reg(FACTORY_REG_CB_ADDR_L, addr_l);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("write cb addr low fail\n");
+            goto TEST_CB_ERR;
+        }
+
+        ret = fts_test_read(cb_addr, data + offset, read_num);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read cb fail\n");
+            goto TEST_CB_ERR;
+        }
+    }
+
+    for (i = 0; i < byte_num; i++) {
+        cb_buf[i] = data[i];
+    }
+
+TEST_CB_ERR:
+    fts_free(data);
+    return ret;
+}
+
+int get_cb_sc(int byte_num, int *cb_buf, enum byte_mode mode)
+{
+    int ret = 0;
+    int i = 0;
+    int read_num = 0;
+    int packet_num = 0;
+    int packet_remainder = 0;
+    int offset = 0;
+    u8 cb_addr = 0;
+    u8 off_addr = 0;
+    u8 off_h_addr = 0;
+    struct fts_test *tdata = fts_ftest;
+    u8 *cb = NULL;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    cb = (u8 *)fts_malloc(byte_num * sizeof(u8));
+    if (NULL == cb) {
+        FTS_TEST_SAVE_ERR("malloc memory for cb buffer fail\n");
+        return -ENOMEM;
+    }
+
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        cb_addr = FACTORY_REG_MC_SC_CB_ADDR;
+        off_addr = FACTORY_REG_MC_SC_CB_ADDR_OFF;
+        off_h_addr = FACTORY_REG_MC_SC_CB_H_ADDR_OFF;
+    } else if (IC_HW_SC == tdata->func->hwtype) {
+        cb_addr = FACTORY_REG_SC_CB_ADDR;
+        off_addr = FACTORY_REG_SC_CB_ADDR_OFF;
+    }
+
+    packet_num = byte_num / BYTES_PER_TIME;
+    packet_remainder = byte_num % BYTES_PER_TIME;
+    if (packet_remainder)
+        packet_num++;
+    read_num = BYTES_PER_TIME;
+    offset = 0;
+
+    FTS_TEST_INFO("cb packet:%d,remainder:%d", packet_num, packet_remainder);
+    for (i = 0; i < packet_num; i++) {
+        if ((i == (packet_num - 1)) && packet_remainder) {
+            read_num = packet_remainder;
+        }
+
+        ret = fts_test_write_reg(off_addr, offset);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("write cb addr offset fail\n");
+            goto cb_err;
+        }
+
+        if (tdata->func->cb_high_support) {
+            ret = fts_test_write_reg(off_h_addr, offset >> 8);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("write cb_h addr offset fail\n");
+                goto cb_err;
+            }
+        }
+
+        ret = fts_test_read(cb_addr, cb + offset, read_num);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read cb fail\n");
+            goto cb_err;
+        }
+
+        offset += read_num;
+    }
+
+    if (DATA_ONE_BYTE == mode) {
+        for (i = 0; i < byte_num; i++) {
+            cb_buf[i] = cb[i];
+        }
+    } else if (DATA_TWO_BYTE == mode) {
+        for (i = 0; i < byte_num; i = i + 2) {
+            cb_buf[i >> 1] = (int)(((int)(cb[i]) << 8) + cb[i + 1]);
+        }
+    }
+
+    ret = 0;
+cb_err:
+    fts_free(cb);
+    return ret;
+}
+
+bool compare_data(int *data, int min, int max, int min_vk, int max_vk, bool key)
+{
+    int i = 0;
+    bool result = true;
+    struct fts_test *tdata = fts_ftest;
+    int rx = tdata->node.rx_num;
+    int node_va = tdata->node.node_num - tdata->node.key_num;
+
+    if (!data || !tdata->node_valid) {
+        FTS_TEST_SAVE_ERR("data/node_valid is null\n");
+        return false;
+    }
+
+    for (i = 0; i < node_va; i++) {
+        if (0 == tdata->node_valid[i])
+            continue;
+
+        if ((data[i] < min) || (data[i] > max)) {
+            FTS_TEST_SAVE_ERR("test fail,node(%4d,%4d)=%5d,range=(%5d,%5d)\n",
+                              i / rx + 1, i % rx + 1, data[i], min, max);
+            result = false;
+        }
+    }
+
+    if (key) {
+        for (i = node_va; i < tdata->node.node_num; i++) {
+            if (0 == tdata->node_valid[i])
+                continue;
+
+            if ((data[i] < min_vk) || (data[i] > max_vk)) {
+                FTS_TEST_SAVE_ERR("test fail,node(%4d,%4d)=%5d,range=(%5d,%5d)\n",
+                                  i / rx + 1, i % rx + 1,
+                                  data[i], min_vk, max_vk);
+                result = false;
+            }
+        }
+    }
+
+    return result;
+}
+
+bool compare_array(int *data, int *min, int *max, bool key)
+{
+    int i = 0;
+    bool result = true;
+    struct fts_test *tdata = fts_ftest;
+    int rx = tdata->node.rx_num;
+    int node_num = tdata->node.node_num;
+
+    if (!data || !min || !max || !tdata->node_valid) {
+        FTS_TEST_SAVE_ERR("data/min/max/node_valid is null\n");
+        return false;
+    }
+
+    if (!key) {
+        node_num -= tdata->node.key_num;
+    }
+    for (i = 0; i < node_num; i++) {
+        if (0 == tdata->node_valid[i])
+            continue;
+
+        if ((data[i] < min[i]) || (data[i] > max[i])) {
+            FTS_TEST_SAVE_ERR("test fail,node(%4d,%4d)=%5d,range=(%5d,%5d)\n",
+                              i / rx + 1, i % rx + 1, data[i], min[i], max[i]);
+            result = false;
+        }
+    }
+
+    return result;
+}
+
+/*
+ * show_data - show and save test data to testresult.txt
+ */
+void show_data(int *data, bool key)
+{
+#if TXT_SUPPORT
+    int i = 0;
+    int j = 0;
+    struct fts_test *tdata = fts_ftest;
+    int node_num = tdata->node.node_num;
+    int tx_num = tdata->node.tx_num;
+    int rx_num = tdata->node.rx_num;
+
+    FTS_TEST_FUNC_ENTER();
+    for (i = 0; i < tx_num; i++) {
+        FTS_TEST_SAVE_INFO("Ch/Tx_%02d:  ", i + 1);
+        for (j = 0; j < rx_num; j++) {
+            FTS_TEST_SAVE_INFO("%5d, ", data[i * rx_num + j]);
+        }
+        FTS_TEST_SAVE_INFO("\n");
+    }
+
+    if (key) {
+        FTS_TEST_SAVE_INFO("Ch/Tx_%02d:  ", tx_num + 1);
+        for (i = tx_num * rx_num; i < node_num; i++) {
+            FTS_TEST_SAVE_INFO("%5d, ",  data[i]);
+        }
+        FTS_TEST_SAVE_INFO("\n");
+    }
+    FTS_TEST_FUNC_EXIT();
+#endif
+}
+
+/* mc_sc only */
+/* Only V3 Pattern has mapping & no-mapping */
+int mapping_switch(u8 mapping)
+{
+    int ret = 0;
+    u8 val = 0xFF;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata->v3_pattern) {
+        ret = fts_test_read_reg(FACTORY_REG_NOMAPPING, &val);
+        if (ret < 0) {
+            FTS_TEST_ERROR("read 0x54 register fail");
+            return ret;
+        }
+
+        if (val != mapping) {
+            ret = fts_test_write_reg(FACTORY_REG_NOMAPPING, mapping);
+            if (ret < 0) {
+                FTS_TEST_ERROR("write 0x54 register fail");
+                return ret;
+            }
+            sys_delay(FACTORY_TEST_DELAY);
+        }
+    }
+
+    return 0;
+}
+
+bool get_fw_wp(u8 wp_ch_sel, enum wp_type water_proof_type)
+{
+    bool fw_wp_state = false;
+
+    switch (water_proof_type) {
+    case WATER_PROOF_ON:
+        /* bit5: 0-check in wp on, 1-not check */
+        fw_wp_state = !(wp_ch_sel & 0x20);
+        break;
+    case WATER_PROOF_ON_TX:
+        /* Bit6:  0-check Rx+Tx in wp mode  1-check one channel
+           Bit2:  0-check Tx in wp mode;  1-check Rx in wp mode
+        */
+        fw_wp_state = (!(wp_ch_sel & 0x40) || !(wp_ch_sel & 0x04));
+        break;
+    case WATER_PROOF_ON_RX:
+        fw_wp_state = (!(wp_ch_sel & 0x40) || (wp_ch_sel & 0x04));
+        break;
+    case WATER_PROOF_OFF:
+        /* bit7: 0-check in wp off, 1-not check */
+        fw_wp_state = !(wp_ch_sel & 0x80);
+        break;
+    case WATER_PROOF_OFF_TX:
+        /* Bit1-0:  00-check Tx in non-wp mode
+                    01-check Rx in non-wp mode
+                    10:check Rx+Tx in non-wp mode
+        */
+        fw_wp_state = ((0x0 == (wp_ch_sel & 0x03)) || (0x02 == (wp_ch_sel & 0x03)));
+        break;
+    case WATER_PROOF_OFF_RX:
+        fw_wp_state = ((0x01 == (wp_ch_sel & 0x03)) || (0x02 == (wp_ch_sel & 0x03)));
+        break;
+    default:
+        break;
+    }
+
+    return fw_wp_state;
+}
+
+int get_cb_mc_sc(u8 wp, int byte_num, int *cb_buf, enum byte_mode mode)
+{
+    int ret = 0;
+
+    /* 1:waterproof 0:non-waterproof */
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, wp);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set mc_sc mode fail\n");
+        return ret;
+    }
+
+    if (fts_ftest->func->param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            return ret;
+        }
+    }
+
+    /* read cb */
+    ret = get_cb_sc(byte_num, cb_buf, mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get sc cb fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+int get_rawdata_mc_sc(enum wp_type wp, int *data)
+{
+    int ret = 0;
+    u8 val = 0;
+    u8 addr = 0;
+    u8 rawdata_addr = 0;
+    int byte_num = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    byte_num = tdata->sc_node.node_num * 2;
+    addr = FACTORY_REG_LINE_ADDR;
+    rawdata_addr = FACTORY_REG_RAWDATA_ADDR_MC_SC;
+    if (WATER_PROOF_ON == wp) {
+        val = 0xAC;
+    } else if (WATER_PROOF_OFF == wp) {
+        val = 0xAB;
+    } else if (HIGH_SENSITIVITY == wp) {
+        val = 0xA0;
+    } else if (HOV == wp) {
+        val = 0xA1;
+        byte_num = 4 * 2;
+    }
+
+    ret = read_rawdata(tdata, addr, val, rawdata_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+int get_rawdata_mc(u8 fre, u8 fir, int *rawdata)
+{
+    int ret = 0;
+    int i = 0;
+
+    if (NULL == rawdata ) {
+        FTS_TEST_SAVE_ERR("rawdata buffer is null\n");
+        return -EINVAL;
+    }
+
+    /* set frequency high/low */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* fir enable/disable */
+    ret = fts_test_write_reg(FACTORY_REG_FIR, fir);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* get rawdata */
+    for (i = 0; i < 3; i++) {
+        /* lost 3 frames, in order to obtain stable data */
+        ret = get_rawdata(rawdata);
+    }
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    return 0;
+}
+
+int short_get_adc_data_mc(u8 retval, int byte_num, int *adc_buf, u8 mode)
+{
+    int ret = 0;
+    int i = 0;
+    u8 short_state = 0;
+    u8 short_state_reg = 0;
+    u8 short_en_reg = 0;
+    u8 short_data_reg = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+    if (tdata->func->mc_sc_short_v2) {
+        short_en_reg = FACTROY_REG_SHORT2_TEST_EN;
+        short_state_reg = FACTROY_REG_SHORT2_TEST_STATE;
+        short_data_reg = FACTORY_REG_SHORT2_ADDR_MC;
+    } else {
+        short_en_reg = FACTROY_REG_SHORT_TEST_EN;
+        short_state_reg = FACTROY_REG_SHORT_TEST_EN;
+        short_data_reg = FACTORY_REG_SHORT_ADDR_MC;
+    }
+
+    /* select short test mode & start test */
+    ret = fts_test_write_reg(short_en_reg, mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short test mode fail\n");
+        goto test_err;
+    }
+
+    for (i = 0; i < FACTORY_TEST_RETRY; i++) {
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+
+        ret = fts_test_read_reg(short_state_reg, &short_state);
+        if ((ret >= 0) && (retval == short_state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", short_state_reg, short_state, i);
+    }
+    if (i >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("short test timeout, ADC data not OK\n");
+        ret = -EIO;
+        goto test_err;
+    }
+
+    ret = read_mass_data(short_data_reg, byte_num, adc_buf);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get short(adc) data fail\n");
+    }
+
+    FTS_TEST_DBG("adc data:\n");
+    print_buffer(adc_buf, byte_num / 2, 0);
+test_err:
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+bool compare_mc_sc(bool tx_check, bool rx_check, int *data, int *min, int *max)
+{
+    int i = 0;
+    bool result = true;
+    struct fts_test *tdata = fts_ftest;
+
+    if (rx_check) {
+        for (i = 0; i < tdata->sc_node.rx_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((data[i] < min[i]) || (data[i] > max[i])) {
+                FTS_TEST_SAVE_ERR("test fail,rx%d=%5d,range=(%5d,%5d)\n",
+                                  i + 1, data[i], min[i], max[i]);
+                result = false;
+            }
+        }
+    }
+
+    if (tx_check) {
+        for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((data[i] < min[i]) || (data[i] > max[i])) {
+                FTS_TEST_SAVE_INFO("test fail,tx%d=%5d,range=(%5d,%5d)\n",
+                                   i - tdata->sc_node.rx_num + 1,
+                                   data[i], min[i], max[i]);
+                result = false;
+            }
+        }
+    }
+
+    return result;
+}
+
+void show_data_mc_sc(int *data)
+{
+    int i = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_SAVE_INFO("SCap Rx: ");
+    for (i = 0; i < tdata->sc_node.rx_num; i++) {
+        FTS_TEST_SAVE_INFO( "%5d, ", data[i]);
+    }
+    FTS_TEST_SAVE_INFO("\n");
+
+    FTS_TEST_SAVE_INFO("SCap Tx: ");
+    for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++) {
+        FTS_TEST_SAVE_INFO( "%5d, ", data[i]);
+    }
+    FTS_TEST_SAVE_INFO("\n");
+}
+/* mc_sc end*/
+
+#if CSV_SUPPORT || TXT_SUPPORT
+static int fts_test_save_test_data(char *file_name, char *data_buf, int len)
+{
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0))
+    struct file *pfile = NULL;
+    char filepath[FILE_NAME_LENGTH] = { 0 };
+    loff_t pos;
+    mm_segment_t old_fs;
+
+    FTS_TEST_FUNC_ENTER();
+    memset(filepath, 0, sizeof(filepath));
+    snprintf(filepath, FILE_NAME_LENGTH, "%s%s", FTS_INI_FILE_PATH, file_name);
+    FTS_INFO("save test data to %s", filepath);
+    if (NULL == pfile) {
+        pfile = filp_open(filepath, O_TRUNC | O_CREAT | O_RDWR, 0);
+    }
+    if (IS_ERR(pfile)) {
+        FTS_TEST_ERROR("error occured while opening file %s.",  filepath);
+        return -EIO;
+    }
+
+    old_fs = get_fs();
+    set_fs(KERNEL_DS);
+    pos = 0;
+    vfs_write(pfile, data_buf, len, &pos);
+    filp_close(pfile, NULL);
+    set_fs(old_fs);
+
+    FTS_TEST_FUNC_EXIT();
+#endif
+    return 0;
+}
+#endif
+
+#if CSV_SUPPORT
+static int fts_test_malloc_csv(struct fts_test *tdata)
+{
+    if (!tdata->csv_file_buf) {
+        FTS_TEST_INFO("csv_file_buf is null, need malloc memory");
+        tdata->csv_file_buf = vmalloc(CSV_BUFFER_LEN);
+        if (!tdata->csv_file_buf) {
+            FTS_TEST_ERROR("csv_file_buf malloc fail");
+            return -ENOMEM;
+        }
+    }
+
+    memset(tdata->csv_file_buf, 0, CSV_BUFFER_LEN);
+    return 0;
+}
+
+static int fts_test_get_item_count_scap_csv(int index)
+{
+    int ret = 0;
+    int i = 0;
+    int select = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 scap_select[4] = { 0 };
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        return index;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        return index;
+    }
+
+    scap_select[0] = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    scap_select[1] = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    scap_select[2] = (hc_sel & 0x03) ? 1 : 0;
+    scap_select[3] = (hc_sel & 0x04) ? 1 : 0;
+
+    for (i = 0; i < 4; i++) {
+        if (scap_select[i])
+            select++;
+        if (select == index)
+            break;
+    }
+
+    return (i + 1);
+}
+
+static void fts_test_save_data_csv(struct fts_test *tdata)
+{
+    int i = 0;
+    int j = 0;
+    int index = 0;
+    int k = 0;
+    int tx = 0;
+    int rx = 0;
+    int node_num = 0;
+    int offset = 0;
+    int start_line = 11;
+    int data_count = 0;
+    char *csv_buffer = tdata->csv_file_buf;
+    char *line2_buffer = NULL;
+    int csv_length = 0;
+    int line2_length = 0;
+    int csv_item_count = 0;
+    struct fts_test_data *td = &tdata->testdata;
+    struct item_info *info = NULL;
+
+    FTS_TEST_INFO("save data in csv format");
+    if (!tdata || !tdata->csv_file_buf) {
+        FTS_TEST_ERROR("tdata/csv_file_buf is null");
+        return ;
+    }
+    memset(csv_buffer, 0, CSV_BUFFER_LEN);
+
+    line2_buffer = vmalloc(CSV_LINE2_BUFFER_LEN);
+    if (!line2_buffer) {
+        FTS_TEST_ERROR("line2_buffer malloc fail\n");
+        goto csv_save_err;
+    }
+
+    FTS_TEST_INFO("test item count:%d", td->item_count);
+    /* line 1 */
+    csv_length += snprintf(csv_buffer + csv_length, \
+                           CSV_BUFFER_LEN - csv_length, \
+                           "ECC, 85, 170, IC Name, %s, IC Code, %x\n", \
+                           tdata->ini.ic_name, \
+                           (tdata->ini.ic_code >> IC_CODE_OFFSET));
+
+    /* line 2 */
+    for (i = 0; i < td->item_count; i++) {
+        info = &td->info[i];
+        if (info->mc_sc) {
+            node_num = tdata->sc_node.node_num;
+            /* set max len of tx/rx to column */
+            rx = (tdata->sc_node.tx_num > tdata->sc_node.rx_num)
+                 ? tdata->sc_node.tx_num : tdata->sc_node.rx_num;
+        } else {
+            if (info->key_support && (tdata->node.key_num > 0))
+                node_num = (tdata->node.tx_num + 1) * tdata->node.rx_num;
+            else
+                node_num = tdata->node.tx_num * tdata->node.rx_num;
+            rx = tdata->node.rx_num;
+        }
+
+        if (info->datalen > node_num) {
+            data_count = (info->datalen - 1 ) / node_num + 1;
+            tx = (node_num - 1 ) / rx + 1;
+        } else {
+            data_count = 1;
+            tx = ((info->datalen - 1) / rx) + 1;
+        }
+
+        for (j = 1; j <= data_count; j++) {
+            index = j;
+
+            if (tdata->func->hwtype == IC_HW_MC_SC) {
+                /*MC_SC, rawdata index will be 2*/
+                if ((info->code == CODE_M_RAWDATA_TEST) && (data_count == 1)) {
+                    index = 2;
+                }
+
+                /*MC_SC, SCAP index will be 1~4*/
+                if ((info->code == CODE_M_SCAP_CB_TEST)
+                    || (info->code == CODE_M_SCAP_RAWDATA_TEST)) {
+                    index = fts_test_get_item_count_scap_csv(j);
+                }
+            }
+
+            line2_length += snprintf(line2_buffer + line2_length, \
+                                     CSV_LINE2_BUFFER_LEN - line2_length, \
+                                     "%s, %d, %d, %d, %d, %d, ", \
+                                     info->name, info->code, tx, rx,
+                                     start_line, index);
+            start_line += tx;
+            csv_item_count++;
+        }
+    }
+
+    csv_length += snprintf(csv_buffer + csv_length, \
+                           CSV_BUFFER_LEN - csv_length, \
+                           "TestItem Num, %d, ", \
+                           csv_item_count);
+
+    if (line2_length > 0) {
+        csv_length += snprintf(csv_buffer + csv_length, \
+                               CSV_BUFFER_LEN - csv_length, \
+                               "%s", line2_buffer);
+    }
+
+    /* line 3 ~ 10  "\n" */
+    csv_length += snprintf(csv_buffer + csv_length, \
+                           CSV_BUFFER_LEN - csv_length, \
+                           "\n\n\n\n\n\n\n\n\n");
+
+    /* line 11 ~ data area */
+    for (i = 0; i < td->item_count; i++) {
+        info = &td->info[i];
+        if (!info->data) {
+            FTS_TEST_ERROR("test item data is null");
+            goto csv_save_err;
+        }
+
+        if (info->mc_sc) {
+            offset = 0;
+            for (j = 0; j < info->datalen;) {
+                for (k = 0; k < tdata->sc_node.node_num; k++) {
+                    csv_length += snprintf(csv_buffer + csv_length, \
+                                           CSV_BUFFER_LEN - csv_length, \
+                                           "%d, ", info->data[offset + k]);
+                    if ((k + 1) == tdata->sc_node.rx_num) {
+                        csv_length += snprintf(csv_buffer + csv_length, \
+                                               CSV_BUFFER_LEN - csv_length, \
+                                               "\n");
+                    }
+                }
+                csv_length += snprintf(csv_buffer + csv_length, \
+                                       CSV_BUFFER_LEN - csv_length, \
+                                       "\n");
+                offset += k;
+                j += k;
+            }
+        } else {
+            for (j = 0; j < info->datalen; j++) {
+                csv_length += snprintf(csv_buffer + csv_length, \
+                                       CSV_BUFFER_LEN - csv_length, \
+                                       "%d, ", info->data[j]);
+                if (((j + 1) % tdata->node.rx_num) == 0) {
+                    csv_length += snprintf(csv_buffer + csv_length, \
+                                           CSV_BUFFER_LEN - csv_length,
+                                           "\n");
+                }
+            }
+        }
+    }
+    FTS_TEST_INFO("csv length:%d", csv_length);
+    fts_test_save_test_data(FTS_CSV_FILE_NAME, csv_buffer, csv_length);
+
+csv_save_err:
+    if (line2_buffer) {
+        vfree(line2_buffer);
+        line2_buffer = NULL;
+    }
+}
+
+static void fts_test_save_data_csv_private(struct fts_test *tdata)
+{
+    char *csv_buffer = tdata->csv_file_buf;
+    int csv_length = 0;
+
+    FTS_TEST_INFO("save data in csv format");
+    if (!tdata || !tdata->csv_file_buf) {
+        FTS_TEST_ERROR("tdata/csv_file_buf is null");
+        return ;
+    }
+    memset(csv_buffer, 0, CSV_BUFFER_LEN);
+
+    if (tdata->func && tdata->func->save_data_private)
+        tdata->func->save_data_private(csv_buffer, &csv_length);
+
+    FTS_TEST_INFO("csv length:%d", csv_length);
+    fts_test_save_test_data(FTS_CSV_FILE_NAME, csv_buffer, csv_length);
+}
+#endif
+
+#if TXT_SUPPORT
+static int fts_test_malloc_txt(struct fts_test *tdata)
+{
+    if (!tdata->testresult) {
+        FTS_TEST_INFO("testresult is null, need malloc memory");
+        tdata->testresult = vmalloc(TXT_BUFFER_LEN);
+        if (!tdata->testresult) {
+            FTS_TEST_ERROR("tdata->testresult malloc fail\n");
+            return -ENOMEM;
+        }
+    }
+    memset(tdata->testresult, 0, TXT_BUFFER_LEN);
+    tdata->testresult_len = 0;
+    FTS_TEST_SAVE_INFO("FW version:0x%02x\n", tdata->fw_major_ver);
+    FTS_TEST_SAVE_INFO("tx_num:%d, rx_num:%d, key_num:%d\n", tdata->node.tx_num,
+                       tdata->node.rx_num, tdata->node.key_num);
+
+    return 0;
+}
+
+static void fts_test_save_result_txt(struct fts_test *tdata)
+{
+    if (!tdata || !tdata->testresult) {
+        FTS_TEST_ERROR("test result is null");
+        return;
+    }
+
+    FTS_TEST_INFO("test result length in txt:%d", tdata->testresult_len);
+    fts_test_save_test_data(FTS_TXT_FILE_NAME, tdata->testresult,
+                            tdata->testresult_len);
+}
+#endif
+
+
+/*****************************************************************************
+* Name: fts_test_save_data
+* Brief: Save test data.
+*        If multi-data of MC, length of data package must be tx*rx,(tx+1)*rx
+*        If multi-data of MC-SC, length of data package should be (tx+rx)*2
+*        Need fill 0 when no actual data
+* Input:
+* Output:
+* Return:
+*****************************************************************************/
+void fts_test_save_data(char *name, int code, int *data, int datacnt,
+                        bool mc_sc, bool key, bool result)
+{
+    int datalen = datacnt;
+    struct fts_test *tdata = fts_ftest;
+    struct fts_test_data *td = &tdata->testdata;
+    struct item_info *info = &td->info[td->item_count];
+
+    if (!name || !data) {
+        FTS_TEST_ERROR("name/data is null");
+        return ;
+    }
+
+    strlcpy(info->name, name, TEST_ITEM_NAME_MAX - 1);
+    info->code = code;
+    info->mc_sc = mc_sc;
+    info->key_support = key;
+    info->result = result;
+    if (datalen <= 0) {
+        if (mc_sc) {
+            datalen = tdata->sc_node.node_num * 2;
+        } else {
+            if (key && (tdata->node.key_num > 0))
+                datalen = (tdata->node.tx_num + 1) * tdata->node.rx_num;
+            else
+                datalen = tdata->node.tx_num * tdata->node.rx_num;
+
+        }
+    }
+
+    FTS_TEST_DBG("name:%s,len:%d", name, datalen);
+    info->data = fts_malloc(datalen * sizeof(int));
+    if (!info->data) {
+        FTS_TEST_ERROR("malloc memory for item(%d) data fail", td->item_count);
+        info->datalen = 0;
+        return ;
+    }
+    memcpy(info->data, data, datalen * sizeof(int));
+    info->datalen = datalen;
+
+    td->item_count++;
+}
+
+static void fts_test_free_data(struct fts_test *tdata)
+{
+    int i = 0;
+    struct fts_test_data *td = &tdata->testdata;
+
+    for (i = 0; i < td->item_count; i++) {
+        if (td->info[i].data) {
+            fts_free(td->info[i].data);
+        }
+    }
+}
+
+static int fts_test_malloc_free_incell(struct fts_test *tdata, bool allocate)
+{
+    struct incell_threshold *thr = &tdata->ic.incell.thr;
+    int buflen = tdata->node.node_num * sizeof(int);
+
+    if (true == allocate) {
+        FTS_TEST_INFO("buflen:%d", buflen);
+        fts_malloc_r(thr->rawdata_min, buflen);
+        fts_malloc_r(thr->rawdata_max, buflen);
+        if (tdata->func->rawdata2_support) {
+            fts_malloc_r(thr->rawdata2_min, buflen);
+            fts_malloc_r(thr->rawdata2_max, buflen);
+        }
+        fts_malloc_r(thr->cb_min, buflen);
+        fts_malloc_r(thr->cb_max, buflen);
+    } else {
+        fts_free(thr->rawdata_min);
+        fts_free(thr->rawdata_max);
+        if (tdata->func->rawdata2_support) {
+            fts_free(thr->rawdata2_min);
+            fts_free(thr->rawdata2_max);
+        }
+        fts_free(thr->cb_min);
+        fts_free(thr->cb_max);
+    }
+
+    return 0;
+}
+
+static int fts_test_malloc_free_mc_sc(struct fts_test *tdata, bool allocate)
+{
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+    int buflen = tdata->node.node_num * sizeof(int);
+    int buflen_sc = tdata->sc_node.node_num * sizeof(int);
+
+    if (true == allocate) {
+        fts_malloc_r(thr->rawdata_h_min, buflen);
+        fts_malloc_r(thr->rawdata_h_max, buflen);
+        if (tdata->func->rawdata2_support) {
+            fts_malloc_r(thr->rawdata_l_min, buflen);
+            fts_malloc_r(thr->rawdata_l_max, buflen);
+        }
+        fts_malloc_r(thr->tx_linearity_max, buflen);
+        fts_malloc_r(thr->tx_linearity_min, buflen);
+        fts_malloc_r(thr->rx_linearity_max, buflen);
+        fts_malloc_r(thr->rx_linearity_min, buflen);
+
+        fts_malloc_r(thr->scap_cb_off_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_off_max, buflen_sc);
+        fts_malloc_r(thr->scap_cb_on_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_on_max, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hi_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hi_max, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hov_min, buflen_sc);
+        fts_malloc_r(thr->scap_cb_hov_max, buflen_sc);
+
+        fts_malloc_r(thr->scap_rawdata_off_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_off_max, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_on_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_on_max, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hi_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hi_max, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hov_min, buflen_sc);
+        fts_malloc_r(thr->scap_rawdata_hov_max, buflen_sc);
+
+        fts_malloc_r(thr->panel_differ_min, buflen);
+        fts_malloc_r(thr->panel_differ_max, buflen);
+        fts_malloc_r(thr->noise_min, buflen);
+        fts_malloc_r(thr->noise_max, buflen);
+
+        fts_malloc_r(thr->low_freq_rawdata_min, buflen);
+        fts_malloc_r(thr->low_freq_rawdata_max, buflen);
+        fts_malloc_r(thr->high_freq_rawdata_min, buflen);
+        fts_malloc_r(thr->high_freq_rawdata_max, buflen);
+
+        fts_malloc_r(thr->low_freq_rawdata_tx_linearity_max, buflen);
+        fts_malloc_r(thr->low_freq_rawdata_rx_linearity_max, buflen);
+        fts_malloc_r(thr->low_freq_rawdata_tx_linearity_min, buflen);
+        fts_malloc_r(thr->low_freq_rawdata_rx_linearity_min, buflen);
+
+        fts_malloc_r(thr->high_freq_rawdata_tx_linearity_max, buflen);
+        fts_malloc_r(thr->high_freq_rawdata_rx_linearity_max, buflen);
+        fts_malloc_r(thr->high_freq_rawdata_tx_linearity_min, buflen);
+        fts_malloc_r(thr->high_freq_rawdata_rx_linearity_min, buflen);
+    } else {
+        fts_free(thr->rawdata_h_min);
+        fts_free(thr->rawdata_h_max);
+        if (tdata->func->rawdata2_support) {
+            fts_free(thr->rawdata_l_min);
+            fts_free(thr->rawdata_l_max);
+        }
+        fts_free(thr->tx_linearity_max);
+        fts_free(thr->tx_linearity_min);
+        fts_free(thr->rx_linearity_max);
+        fts_free(thr->rx_linearity_min);
+
+        fts_free(thr->scap_cb_off_min);
+        fts_free(thr->scap_cb_off_max);
+        fts_free(thr->scap_cb_on_min);
+        fts_free(thr->scap_cb_on_max);
+        fts_free(thr->scap_cb_hi_min);
+        fts_free(thr->scap_cb_hi_max);
+        fts_free(thr->scap_cb_hov_min);
+        fts_free(thr->scap_cb_hov_max);
+
+        fts_free(thr->scap_rawdata_off_min);
+        fts_free(thr->scap_rawdata_off_max);
+        fts_free(thr->scap_rawdata_on_min);
+        fts_free(thr->scap_rawdata_on_max);
+        fts_free(thr->scap_rawdata_hi_min);
+        fts_free(thr->scap_rawdata_hi_max);
+        fts_free(thr->scap_rawdata_hov_min);
+        fts_free(thr->scap_rawdata_hov_max);
+
+        fts_free(thr->panel_differ_min);
+        fts_free(thr->panel_differ_max);
+
+        fts_free(thr->noise_min);
+        fts_free(thr->noise_max);
+
+        fts_free(thr->low_freq_rawdata_min);
+        fts_free(thr->low_freq_rawdata_max);
+        fts_free(thr->high_freq_rawdata_min);
+        fts_free(thr->high_freq_rawdata_max);
+
+        fts_free(thr->low_freq_rawdata_tx_linearity_max);
+        fts_free(thr->low_freq_rawdata_rx_linearity_max);
+        fts_free(thr->low_freq_rawdata_tx_linearity_min);
+        fts_free(thr->low_freq_rawdata_rx_linearity_min);
+
+        fts_free(thr->high_freq_rawdata_tx_linearity_max);
+        fts_free(thr->high_freq_rawdata_rx_linearity_max);
+        fts_free(thr->high_freq_rawdata_tx_linearity_min);
+        fts_free(thr->high_freq_rawdata_rx_linearity_min);
+    }
+
+    return 0;
+}
+
+static int fts_test_malloc_free_sc(struct fts_test *tdata, bool allocate)
+{
+    struct sc_threshold *thr = &tdata->ic.sc.thr;
+    int buflen = tdata->node.node_num * sizeof(int);
+
+    if (true == allocate) {
+        fts_malloc_r(thr->rawdata_min, buflen);
+        fts_malloc_r(thr->rawdata_max, buflen);
+        fts_malloc_r(thr->cb_min, buflen);
+        fts_malloc_r(thr->cb_max, buflen);
+        fts_malloc_r(thr->dcb_sort, buflen);
+        fts_malloc_r(thr->dcb_base, buflen);
+    } else {
+        fts_free(thr->rawdata_min);
+        fts_free(thr->rawdata_max);
+        fts_free(thr->cb_min);
+        fts_free(thr->cb_max);
+        fts_free(thr->dcb_sort);
+        fts_free(thr->dcb_base);
+    }
+
+    return 0;
+}
+
+static int fts_test_malloc_free_thr(struct fts_test *tdata, bool allocate)
+{
+    int ret = 0;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("tdata/func is NULL\n");
+        return -EINVAL;
+    }
+
+    if (true == allocate) {
+        fts_malloc_r(tdata->node_valid, tdata->node.node_num * sizeof(int));
+        fts_malloc_r(tdata->node_valid_sc, tdata->sc_node.node_num * sizeof(int));
+    } else {
+        fts_free(tdata->node_valid);
+        fts_free(tdata->node_valid_sc);
+    }
+
+    switch (tdata->func->hwtype) {
+    case IC_HW_INCELL:
+        ret = fts_test_malloc_free_incell(tdata, allocate);
+        break;
+    case IC_HW_MC_SC:
+        ret = fts_test_malloc_free_mc_sc(tdata, allocate);
+        break;
+    case IC_HW_SC:
+        ret = fts_test_malloc_free_sc(tdata, allocate);
+        break;
+    default:
+        FTS_TEST_SAVE_ERR("test ic type(%d) fail\n", tdata->func->hwtype);
+        ret = -EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+/* default enable all test item */
+static void fts_test_init_item(struct fts_test *tdata)
+{
+    switch (tdata->func->hwtype) {
+    case IC_HW_INCELL:
+        tdata->ic.incell.u.tmp = 0xFFFFFFFF;
+        break;
+    case IC_HW_MC_SC:
+        tdata->ic.mc_sc.u.tmp = 0xFFFFFFFF;
+        break;
+    case IC_HW_SC:
+        tdata->ic.sc.u.tmp = 0xFFFFFFFF;
+        break;
+    }
+}
+
+static int get_tx_rx_num(u8 tx_rx_reg, u8 *ch_num, u8 ch_num_max)
+{
+    int ret = 0;
+    int i = 0;
+
+    for (i = 0; i < 3; i++) {
+        ret = fts_test_read_reg(tx_rx_reg, ch_num);
+        if ((ret < 0) || (*ch_num > ch_num_max)) {
+            sys_delay(50);
+        } else
+            break;
+    }
+
+    if (i >= 3) {
+        FTS_TEST_ERROR("get channel num fail");
+        return -EIO;
+    }
+
+    return 0;
+}
+static int get_key_num(int *key_num_en, int max_key_num)
+{
+    int ret = 0;
+    u8 key_en = 0;
+
+    if (!max_key_num) {
+        FTS_TEST_DBG("not support key, don't read key num register");
+        return 0;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_LEFT_KEY, &key_en);
+    if (ret >= 0) {
+        if (key_en & 0x01) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x02) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x04) {
+            (*key_num_en)++;
+        }
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_RIGHT_KEY, &key_en);
+    if (ret >= 0) {
+        if (key_en & 0x01) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x02) {
+            (*key_num_en)++;
+        }
+
+        if (key_en & 0x04) {
+            (*key_num_en)++;
+        }
+    }
+
+    if (*key_num_en > max_key_num) {
+        FTS_TEST_ERROR("get key num, fw:%d > max:%d", *key_num_en, max_key_num);
+        return -EIO;
+    }
+
+    return ret;
+}
+
+static int get_channel_num(struct fts_test *tdata)
+{
+    int ret = 0;
+    u8 tx_num = 0;
+    u8 rx_num = 0;
+    int key_num = 0;
+    ktime_t start_time = ktime_get();
+
+    /* node structure */
+    if (IC_HW_SC == tdata->func->hwtype) {
+        ret = get_tx_rx_num(FACTORY_REG_CH_NUM_SC, &tx_num, NUM_MAX_SC);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get channel number fail");
+            return ret;
+        }
+
+        ret = get_tx_rx_num(FACTORY_REG_KEY_NUM_SC, &rx_num, KEY_NUM_MAX);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get key number fail");
+            return ret;
+        }
+
+        tdata->node.tx_num = 1;
+        tdata->node.rx_num = tx_num;
+        tdata->node.channel_num = tx_num;
+        tdata->node.node_num = tx_num;
+        key_num = rx_num;
+    } else {
+        ret = get_tx_rx_num(FACTORY_REG_CHX_NUM, &tx_num, TX_NUM_MAX);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get tx_num fail");
+            return ret;
+        }
+
+        ret = get_tx_rx_num(FACTORY_REG_CHY_NUM, &rx_num, RX_NUM_MAX);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get rx_num fail");
+            return ret;
+        }
+
+        if (IC_HW_INCELL == tdata->func->hwtype) {
+            ret = get_key_num(&key_num, tdata->func->key_num_total);
+            if (ret < 0) {
+                FTS_TEST_ERROR("get key_num fail");
+                return ret;
+            }
+        } else if (IC_HW_MC_SC == tdata->func->hwtype) {
+            key_num = tdata->func->key_num_total;
+        }
+        tdata->node.tx_num = tx_num;
+        tdata->node.rx_num = rx_num;
+        if (IC_HW_INCELL == tdata->func->hwtype)
+            tdata->node.channel_num = tx_num * rx_num;
+        else if (IC_HW_MC_SC == tdata->func->hwtype)
+            tdata->node.channel_num = tx_num + rx_num;
+        tdata->node.node_num = tx_num * rx_num;
+    }
+
+    /* key */
+    tdata->node.key_num = key_num;
+    tdata->node.node_num += tdata->node.key_num;
+
+    /* sc node structure */
+    tdata->sc_node = tdata->node;
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        if (tdata->v3_pattern) {
+            ret = get_tx_rx_num(FACTORY_REG_CHX_NUM_NOMAP, &tx_num, TX_NUM_MAX);
+            if (ret < 0) {
+                FTS_TEST_ERROR("get no-mappint tx_num fail");
+                return ret;
+            }
+
+            ret = get_tx_rx_num(FACTORY_REG_CHY_NUM_NOMAP, &rx_num, TX_NUM_MAX);
+            if (ret < 0) {
+                FTS_TEST_ERROR("get no-mapping rx_num fail");
+                return ret;
+            }
+
+            tdata->sc_node.tx_num = tx_num;
+            tdata->sc_node.rx_num = rx_num;
+        }
+        tdata->sc_node.channel_num = tx_num + rx_num;
+        tdata->sc_node.node_num = tx_num + rx_num;
+    }
+
+    if (tdata->node.tx_num > TX_NUM_MAX) {
+        FTS_TEST_ERROR("tx num(%d) fail", tdata->node.tx_num);
+        return -EIO;
+    }
+
+    if (tdata->node.rx_num > RX_NUM_MAX) {
+        FTS_TEST_ERROR("rx num(%d) fail", tdata->node.rx_num);
+        return -EIO;
+    }
+
+    FTS_TEST_INFO("node_num:%d, tx:%d, rx:%d, key:%d",
+                  tdata->node.node_num, tdata->node.tx_num,
+                  tdata->node.rx_num, tdata->node.key_num);
+
+    FTS_INFO("channel Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return 0;
+}
+
+static int fts_test_init_basicinfo(struct fts_test *tdata)
+{
+    int ret = 0;
+    u8 val = 0;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("tdata/func is NULL\n");
+        return -EINVAL;
+    }
+
+    fts_test_read_reg(REG_FW_MAJOR_VER, &val);
+    if (ret < 0) {
+        FTS_ERROR("read fw major version fail,ret=%d\n", ret);
+        return ret;
+    }
+    tdata->fw_major_ver = val;
+
+    fts_test_read_reg(REG_FW_MINOR_VER, &val);
+    if (ret < 0) {
+        FTS_ERROR("read fw minor version fail,ret=%d\n", ret);
+        return ret;
+    }
+    tdata->fw_minor_ver = val;
+
+    if (IC_HW_INCELL == tdata->func->hwtype) {
+        fts_test_read_reg(REG_VA_TOUCH_THR, &val);
+        tdata->va_touch_thr = val;
+        fts_test_read_reg(REG_VKEY_TOUCH_THR, &val);
+        tdata->vk_touch_thr = val;
+    }
+
+    /* enter factory mode */
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail\n");
+        return ret;
+    }
+
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        fts_test_read_reg(FACTORY_REG_PATTERN, &val);
+        tdata->v3_pattern = (1 == val) ? true : false;
+        fts_test_read_reg(FACTORY_REG_NOMAPPING, &val);
+        tdata->mapping = val;
+    }
+
+    /* enter into factory mode and read tx/rx num */
+    ret = get_channel_num(tdata);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get channel number fail\n");
+        return ret;
+    }
+
+    return ret;
+}
+
+static int fts_test_main_init(void)
+{
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+    /* Init fts_test_data to 0 before test,  */
+    memset(&tdata->testdata, 0, sizeof(struct fts_test_data));
+
+    /* get basic information: tx/rx num ... */
+    ret = fts_test_init_basicinfo(tdata);
+    if (ret < 0) {
+        FTS_TEST_ERROR("test init basicinfo fail");
+        return ret;
+    }
+
+    /* allocate memory for test threshold */
+    ret = fts_test_malloc_free_thr(tdata, true);
+    if (ret < 0) {
+        FTS_TEST_ERROR("test malloc for threshold fail");
+        return ret;
+    }
+
+    /* default enable all test item */
+    fts_test_init_item(tdata);
+    
+#if CSV_SUPPORT
+        ret = fts_test_malloc_csv(tdata);
+        if (ret < 0) {
+            FTS_TEST_ERROR("allocate memory for test data(csv) fail");
+            return ret;
+        }
+#endif
+    
+#if TXT_SUPPORT
+        ret = fts_test_malloc_txt(tdata);
+        if (ret < 0) {
+            FTS_TEST_ERROR("allocate memory for test data(txt) fail");
+            return ret;
+        }
+#endif
+
+    /* allocate test data buffer */
+    tdata->buffer_length = (tdata->node.tx_num + 1) * tdata->node.rx_num;
+    tdata->buffer_length *= sizeof(int) * 2;
+    FTS_TEST_INFO("test buffer length:%d", tdata->buffer_length);
+    tdata->buffer = (int *)fts_malloc(tdata->buffer_length);
+    if (NULL == tdata->buffer) {
+        FTS_TEST_ERROR("test buffer(%d) malloc fail", tdata->buffer_length);
+        return -ENOMEM;
+    }
+    memset(tdata->buffer, 0, tdata->buffer_length);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int fts_test_main_exit(void)
+{
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+#if CSV_SUPPORT
+        if (tdata->func && tdata->func->save_data_private)
+            fts_test_save_data_csv_private(tdata);
+        else
+            fts_test_save_data_csv(tdata);
+#endif
+#if TXT_SUPPORT
+    fts_test_save_result_txt(tdata);
+#endif
+
+    /* free memory */
+    fts_test_malloc_free_thr(tdata, false);
+
+    /* free test data */
+    fts_test_free_data(tdata);
+
+    /*free test data buffer*/
+    fts_free(tdata->buffer);
+
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+
+/*
+ * fts_test_get_testparams - get test parameter from ini
+ */
+static int fts_test_get_testparams(char *config_name)
+{
+    int ret = 0;
+
+    ret = fts_test_get_testparam_from_ini(config_name);
+
+    return ret;
+}
+
+static int fts_test_start(void)
+{
+    int testresult = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata && tdata->func && tdata->func->start_test) {
+        tdata->testdata.item_count = 0;
+        testresult = tdata->func->start_test();
+    } else {
+        FTS_TEST_ERROR("test func/start_test func is null");
+    }
+
+    return testresult;
+}
+
+/*
+ * fts_test_entry - test main entry
+ *
+ * warning - need disable irq & esdcheck before call this function
+ *
+ */
+static int fts_test_entry(char *ini_file_name)
+{
+    int ret = 0;
+
+    /* test initialize */
+    ret = fts_test_main_init();
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto test_err;
+    }
+
+    /*Read parse configuration file*/
+    FTS_TEST_SAVE_INFO("ini_file_name:%s\n", ini_file_name);
+    ret = fts_test_get_testparams(ini_file_name);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get testparam fail");
+        goto test_err;
+    }
+
+    /* Start testing according to the test configuration */
+    if (true == fts_test_start()) {
+        FTS_TEST_SAVE_INFO("=======Tp test pass.");
+        if (fts_ftest->s) seq_printf(fts_ftest->s, "=======Tp test pass.\n");
+        fts_ftest->result = true;
+    } else {
+        FTS_TEST_SAVE_INFO("=======Tp test failure.");
+        if (fts_ftest->s) seq_printf(fts_ftest->s, "=======Tp test failure.\n");
+        fts_ftest->result = false;
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+        do_gettimeofday(&(fts_ftest->tv));
+#endif
+    }
+
+test_err:
+    fts_test_main_exit();
+    enter_work_mode();
+    return ret;
+}
+
+
+/*
+ * fts_test_entry - test main entry
+ *
+ * warning - need disable irq & esdcheck before call this function
+ *
+ */
+int fts_proc_test_entry(char *ini_file_name)
+{
+    int ret = 0;
+
+    /* test initialize */
+    ret = fts_test_main_init();
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        return ret;
+    }
+
+    /*Read parse configuration file*/
+    FTS_TEST_SAVE_INFO("ini_file_name:%s\n", ini_file_name);
+    ret = fts_test_get_testparams(ini_file_name);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get testparam fail");
+        return ret;
+    }
+
+    return 0;
+}
+
+int fts_proc_test_exit(void)
+{
+    struct fts_test *tdata = fts_ftest;
+
+    FTS_TEST_FUNC_ENTER();
+
+    /* free memory */
+    fts_test_malloc_free_thr(tdata, false);
+
+    /* free test data */
+    fts_test_free_data(tdata);
+
+    /*free test data buffer*/
+    fts_free(tdata->buffer);
+
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+
+
+static ssize_t fts_test_show(
+    struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev = ts_data->input_dev;
+    ssize_t size = 0;
+
+    mutex_lock(&input_dev->mutex);
+    size += snprintf(buf + size, PAGE_SIZE, "FTS_INI_FILE_PATH:%s\n",
+                     FTS_INI_FILE_PATH);
+    size += snprintf(buf + size, PAGE_SIZE, "FTS_CSV_FILE_NAME:%s\n",
+                     FTS_CSV_FILE_NAME);
+    size += snprintf(buf + size, PAGE_SIZE, "FTS_TXT_FILE_NAME:%s\n",
+                     FTS_TXT_FILE_NAME);
+    mutex_unlock(&input_dev->mutex);
+
+    return size;
+}
+
+static ssize_t fts_test_store(
+    struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+    int ret = 0;
+    char fwname[FILE_NAME_LENGTH] = { 0 };
+    struct fts_ts_data *ts_data = fts_data;
+    struct input_dev *input_dev;
+
+    if (ts_data->suspended) {
+        FTS_INFO("In suspend, no test, return now");
+        return -EINVAL;
+    }
+
+    input_dev = ts_data->input_dev;
+    memset(fwname, 0, sizeof(fwname));
+    snprintf(fwname, FILE_NAME_LENGTH, "%s", buf);
+    fwname[count - 1] = '\0';
+    FTS_TEST_DBG("fwname:%s.", fwname);
+
+    mutex_lock(&input_dev->mutex);
+    fts_irq_disable();
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    fts_ftest->s = NULL;
+    ret = fts_enter_test_environment(1);
+    if (ret < 0) {
+        FTS_ERROR("enter test environment fail");
+    } else {
+        fts_test_entry(fwname);
+    }
+    ret = fts_enter_test_environment(0);
+    if (ret < 0) {
+        FTS_ERROR("enter normal environment fail");
+    }
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    fts_irq_enable();
+    mutex_unlock(&input_dev->mutex);
+
+    return count;
+}
+
+/*  test from test.ini
+ *  example:echo "***.ini" > fts_test
+ */
+static DEVICE_ATTR(fts_test, S_IRUGO | S_IWUSR, fts_test_show, fts_test_store);
+
+static struct attribute *fts_test_attributes[] = {
+    &dev_attr_fts_test.attr,
+    NULL
+};
+
+static struct attribute_group fts_test_attribute_group = {
+    .attrs = fts_test_attributes
+};
+
+#if CSV_SUPPORT
+/* /proc/fts_test_csv */
+static int fts_csv_show(struct seq_file *s, void *v)
+{
+    struct fts_test *tdata = s->private;
+    int csv_size = 0;
+
+    if (!tdata || !tdata->csv_file_buf) {
+        FTS_TEST_ERROR("tdata/csv_file_buf is null");
+        return -ENODATA;
+    }
+
+    csv_size = (int)strlen(tdata->csv_file_buf);
+    FTS_TEST_INFO("csv_show, size=%d,%d", (int)s->size, csv_size);
+    if (csv_size <= 0) {
+        seq_printf(s, "no csv data, please do test first\n");
+        return 0;
+    }
+
+    if (s->size < csv_size) {
+        s->count = s->size;
+        return 0;
+    }
+
+    seq_printf(s, "%s", tdata->csv_file_buf);
+    return 0;
+}
+
+static int fts_csv_open(struct inode *inode, struct file *file)
+{
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0))
+    return single_open(file, fts_csv_show, pde_data(inode));
+#else
+    return single_open(file, fts_csv_show, PDE_DATA(inode));
+#endif
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops fts_proccsv_fops = {
+    .proc_open = fts_csv_open,
+    .proc_read = seq_read,
+    .proc_lseek = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations fts_proccsv_fops = {
+    .open = fts_csv_open,
+    .read = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+#endif
+
+#if TXT_SUPPORT
+/* /proc/fts_test_txt */
+static int fts_txt_show(struct seq_file *s, void *v)
+{
+    struct fts_test *tdata = s->private;
+    int txt_size = 0;
+
+    if (!tdata || !tdata->testresult) {
+        FTS_TEST_ERROR("tdata/testresult is null");
+        return -ENODATA;
+    }
+
+    txt_size = (int)strlen(tdata->testresult);
+    FTS_TEST_INFO("csv_show, size=%d,%d", (int)s->size, txt_size);
+    if (txt_size <= 0) {
+        seq_printf(s, "no csv data, please do test first\n");
+        return 0;
+    }
+
+    if (s->size < txt_size) {
+        s->count = s->size;
+        return 0;
+    }
+
+    seq_printf(s, "%s", tdata->testresult);
+    return 0;
+}
+
+static int fts_txt_open(struct inode *inode, struct file *file)
+{
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0))
+    return single_open(file, fts_txt_show, pde_data(inode));
+#else
+    return single_open(file, fts_txt_show, PDE_DATA(inode));
+#endif
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops fts_proctxt_fops = {
+    .proc_open = fts_txt_open,
+    .proc_read = seq_read,
+    .proc_lseek = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations fts_proctxt_fops = {
+    .open = fts_txt_open,
+    .read = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+#endif
+
+
+static int fts_test_func_init(struct fts_ts_data *ts_data)
+{
+    int i = 0;
+    int j = 0;
+    u16 ic_stype = ts_data->ic_info.ids.type;
+    struct test_funcs *func = test_func_list[0];
+    int func_count = sizeof(test_func_list) / sizeof(test_func_list[0]);
+
+    FTS_TEST_FUNC_ENTER();
+    if (0 == func_count) {
+        FTS_TEST_SAVE_ERR("test functions list is NULL, fail\n");
+        return -ENODATA;
+    }
+
+    fts_ftest = (struct fts_test *)kzalloc(sizeof(*fts_ftest), GFP_KERNEL);
+    if (NULL == fts_ftest) {
+        FTS_TEST_ERROR("malloc memory for test fail");
+        return -ENOMEM;
+    }
+
+    for (i = 0; i < func_count; i++) {
+        func = test_func_list[i];
+        for (j = 0; j < FTS_MAX_COMPATIBLE_TYPE; j++) {
+            if (0 == func->ctype[j])
+                break;
+            else if (func->ctype[j] == ic_stype) {
+                FTS_TEST_INFO("match test function,type:%x",
+                    (int)func->ctype[j]);
+                fts_ftest->func = func;
+            }
+        }
+    }
+    if (NULL == fts_ftest->func) {
+        FTS_TEST_ERROR("no test function match, can't test");
+        return -ENODATA;
+    }
+
+    fts_ftest->ts_data = ts_data;
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
+
+/*run_os_test*/
+#define RUN_OS_TEST_INI_FILE        "focaltech_testconf.ini"
+char *goog_get_test_limit_name(void)
+{
+    struct fts_ts_data *ts_data = fts_data;
+    if (!ts_data || !ts_data->pdata ||
+        ts_data->pdata->test_limits_name[0] == '\0')
+      return RUN_OS_TEST_INI_FILE;
+
+    return ts_data->pdata->test_limits_name;
+}
+
+static int proc_run_os_test_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_test *tdata = (struct fts_test *)s->private;
+    struct fts_ts_data *ts_data = NULL;
+    struct input_dev *input_dev = NULL;
+
+    if (s->size <= (PAGE_SIZE * 4)) {
+        s->count = s->size;
+        FTS_TEST_ERROR("Buffer size:%d, return ", (int)s->size);
+        return 0;
+    }
+
+    if (!tdata) {
+        FTS_TEST_ERROR("test data is null, return");
+        return -EINVAL;
+    }
+
+    ts_data = tdata->ts_data;
+    input_dev = ts_data->input_dev;
+    if (ts_data->suspended) {
+        FTS_TEST_ERROR("In suspend, no test, return");
+        return -EINVAL;
+    }
+
+    mutex_lock(&input_dev->mutex);
+    fts_irq_disable();
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(DISABLE);
+#endif
+
+    tdata->s = s;
+    ret = fts_enter_test_environment(1);
+    if (ret < 0) {
+        FTS_ERROR("enter test environment fail");
+    } else {
+        fts_test_entry(goog_get_test_limit_name());
+    }
+    ret = fts_enter_test_environment(0);
+    if (ret < 0) {
+        FTS_ERROR("enter normal environment fail");
+    }
+
+#if defined(FTS_ESDCHECK_EN) && (FTS_ESDCHECK_EN)
+    fts_esdcheck_switch(ENABLE);
+#endif
+
+    fts_irq_enable();
+    mutex_unlock(&input_dev->mutex);
+
+    return 0;
+}
+
+static int proc_run_os_test_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_run_os_test_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_run_os_test_fops = {
+    .proc_open   = proc_run_os_test_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_run_os_test_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_run_os_test_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+static void seq_print_and_log_array(struct seq_file *s, int tx, int rx, int* data)
+{
+    if (s == NULL || data == NULL) {
+        FTS_ERROR("Error: NULL ptr");
+        return ;
+    }
+
+    int i, j, count = 0;
+    char print_buf[512];
+
+    for (i = 0; i < tx; i++) {
+      for (j = 0; j < rx; j++) {
+          seq_printf(s, "%d,", data[i*rx + j]);
+          count += scnprintf(print_buf + count, sizeof(print_buf) - count, "%d,",
+                             data[i*rx + j]);
+      }
+      seq_printf(s, "\n");
+      FTS_INFO("%s", print_buf);
+      count = 0;
+    }
+}
+
+
+#define SEQ_PRINT_AND_LOG(fmt, ...) \
+    do { \
+        seq_printf(s, fmt, ##__VA_ARGS__); \
+        FTS_INFO(fmt, ##__VA_ARGS__); \
+    } while (0)
+
+/*FW Version test*/
+static int proc_test_fwver_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    u8 fw_major_ver = 0;
+    u8 fw_minor_ver = 0;
+    u8 vendor_id = 0;
+
+    ret = fts_read_reg(REG_FW_MAJOR_VER, &fw_major_ver);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read major version fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_read_reg(REG_FW_MINOR_VER, &fw_minor_ver);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read minor version fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_read_reg(FTS_REG_VENDOR_ID, &vendor_id);
+    if (ret < 0) {
+        FTS_ERROR("FWVER read vendor id fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    SEQ_PRINT_AND_LOG("Vendor ID:%02x, FWVER:V%02x_D%02x\n", vendor_id, fw_major_ver, fw_minor_ver);
+
+exit:
+    return ret;
+}
+
+static int proc_test_fwver_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_fwver_show, inode->i_private);
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_fwver_fops = {
+    .proc_open   = proc_test_fwver_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_fwver_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_fwver_open,
+    .read  = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/*Channel Num test*/
+static int proc_test_chnum_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    ktime_t start_time = ktime_get();
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    SEQ_PRINT_AND_LOG("TX:%02d, RX:%02d\n", tx, rx);
+
+exit:
+    enter_work_mode();
+
+    FTS_INFO("chnum Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_chnum_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_chnum_show, inode->i_private);
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_chnum_fops = {
+    .proc_open   = proc_test_chnum_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_chnum_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_chnum_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* HW Reset_Pin Test */
+static int proc_test_hw_reset_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    u8 reg88_val = 0xFF;
+    u8 tmp_val = 0;
+
+    ret = fts_read_reg(FTS_TMP_REG_AD, &reg88_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails");
+        goto exit;
+    }
+
+    tmp_val = reg88_val + 1;
+    ret = fts_write_reg(FTS_TMP_REG_AD, tmp_val);
+    if (ret < 0) {
+        FTS_ERROR("write reg88 fails");
+        goto exit;
+    }
+
+    fts_reset_proc(200);
+
+    ret = fts_read_reg(FTS_TMP_REG_AD, &tmp_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails");
+        goto exit;
+    }
+
+    if (tmp_val == reg88_val)
+        SEQ_PRINT_AND_LOG("Reset Pin test PASS.\n");
+    else
+        SEQ_PRINT_AND_LOG("Reset Pin test FAIL.\n");
+
+exit:
+    return ret;
+}
+
+static int proc_test_hw_reset_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_hw_reset_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_hw_reset_fops = {
+    .proc_open   = proc_test_hw_reset_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_hw_reset_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_hw_reset_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* SW Reset Test */
+static int proc_test_sw_reset_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    u8 reg88_val = 0;
+    u8 tmp_val = 0;
+
+    ret = fts_read_reg(FTS_TMP_REG_88, &reg88_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails");
+        goto exit;
+    }
+
+    ret = fts_write_reg(FTS_TMP_REG_88, 0x22);
+    if (ret < 0) {
+        FTS_ERROR("write reg88 fails for SW reset");
+        goto exit;
+    }
+
+    ret = fts_write_reg(FTS_TMP_REG_SOFT_RESET, 0xAA);
+    if (ret < 0) {
+        FTS_ERROR("write 0xAA to reg 0xFC fails");
+        goto exit;
+    }
+
+    ret = fts_write_reg(FTS_TMP_REG_SOFT_RESET, 0x66);
+    if (ret < 0) {
+        FTS_ERROR("write 0x66 to reg 0xFC fails");
+        goto exit;
+    }
+    sys_delay(40);
+    ret = fts_read_reg(FTS_TMP_REG_88, &tmp_val);
+    if (ret < 0) {
+        FTS_ERROR("read reg88 fails for SW reset");
+        goto exit;
+    }
+
+    if (tmp_val == reg88_val)
+        SEQ_PRINT_AND_LOG("SW Reset test PASS.\n");
+    else
+        SEQ_PRINT_AND_LOG("SW Reset test FAIL.\n");
+
+exit:
+    return ret;
+}
+
+static int proc_test_sw_reset_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_sw_reset_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_sw_reset_fops = {
+    .proc_open   = proc_test_sw_reset_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_sw_reset_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_sw_reset_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Lot Code */
+static int goog_get_lot_code(u8* data, size_t datalen)
+{
+    int ret = 0;
+    u8 cmd = FACTORY_REG_LOT_CODE;
+
+    /* enter factory mode */
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_read(&cmd, 1, data, datalen);
+
+    enter_work_mode();
+    return ret;
+}
+
+static int proc_test_lot_code_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    u8 data[15];
+
+    ret = goog_get_lot_code(data, sizeof(data));
+    if (ret != 0) {
+        FTS_ERROR("get lot code faile %d", ret);
+        return ret;
+    }
+
+    SEQ_PRINT_AND_LOG("Lot Code:");
+    SEQ_PRINT_AND_LOG("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X "
+                      "%02X %02X %02X %02X\n",
+      data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],
+      data[8],data[9],data[10],data[11],data[12],data[13],data[14]);
+
+    return 0;
+}
+
+static int proc_test_lot_code_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_lot_code_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_lot_code_fops = {
+    .proc_open   = proc_test_lot_code_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_lot_code_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_lot_code_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* INT_Pin Test */
+int int_test_has_interrupt = 0;
+static int proc_test_int_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int i = 0;
+    ktime_t start_time = ktime_get();
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    fts_irq_enable();
+    int_test_has_interrupt = 0;
+    ret = fts_write_reg(FACTORY_REG_SCAN_ADDR2, 0x01);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    sys_delay(10);
+    for (i = 0; i < 100; i++) {
+        if (int_test_has_interrupt) {
+            SEQ_PRINT_AND_LOG("INT Pin test PASS.\n");
+            goto exit;
+        } else {
+            sys_delay(10);
+        }
+    }
+    SEQ_PRINT_AND_LOG("INT Pin test FAIL.\n");
+
+exit:
+    enter_work_mode();
+
+    FTS_INFO("INT Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_int_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_int_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_int_fops = {
+    .proc_open   = proc_test_int_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_int_fops = {
+    .open   = proc_test_int_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+
+extern int fts_test_get_raw(int *raw, u8 tx, u8 rx);
+extern int fts_test_get_baseline(int *raw,int *base_raw, u8 tx, u8 rx);
+extern int fts_test_get_strength(u8 *base_raw, u16 base_raw_size);
+extern int fts_test_get_uniformity_data(int *raw, int *rawdata_linearity, u8 tx, u8 rx);
+extern int fts_test_get_scap_raw(int *scap_raw, u8 tx, u8 rx, int *fwcheck);
+extern int fts_test_get_scap_cb(int *scap_cb, u8 tx, u8 rx, int *fwcheck);
+extern int fts_test_get_short(int *short_data, u8 tx, u8 rx);
+extern int fts_test_get_noise(int *noise, u8 tx, u8 rx);
+extern int fts_test_get_panel_differ(int *panel_differ, u8 tx, u8 rx);
+extern int fts_get_low_high_freq_rawdata(struct fts_test *tdata, int *data, bool only_high);
+extern int fts_test_get_scap_noise(int *scap_noise_data, u8 tx, u8 rx, int *fwcheck);
+extern int fts_test_get_short_ch_to_gnd(int *res, u8 *ab_ch, u8 tx, u8 rx);
+extern int fts_test_get_short_ch_to_ch(int *res, u8 *ab_ch, u8 tx, u8 rx);
+
+/* Rawdata test */
+static int proc_test_raw_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    bool result = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    ktime_t start_time = ktime_get();
+    struct fts_test *tdata = fts_ftest;
+    int *raw = tdata->raw_data;
+
+    if (!raw) {
+        FTS_ERROR("raw mem is null,can not test rawdata");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+    /* get Tx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    /* get Rx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+
+    /* get raw data */
+    fts_test_get_raw(raw, tx, rx);
+    result = compare_array(raw,
+                           thr->rawdata_h_min,
+                           thr->rawdata_h_max,
+                           false);
+    tdata->pretest_raw = true;
+
+    /* output raw data */
+    seq_print_and_log_array(s, tx, rx, raw);
+
+    SEQ_PRINT_AND_LOG("Rawdata Test %s\n", result? "PASS" : "NG");
+
+exit:
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Rawdata test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_raw_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_raw_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_raw_fops = {
+    .proc_open   = proc_test_raw_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_raw_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_raw_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Baseline test */
+static int proc_test_baseline_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *raw = NULL;
+    int *base_raw = NULL;
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    raw = fts_malloc(node_num * sizeof(int));
+    if (!raw) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    base_raw = fts_malloc(node_num * sizeof(int));
+    if (!base_raw) {
+        FTS_ERROR("malloc memory for base_raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get baseline data */
+    fts_test_get_baseline(raw, base_raw, tx, rx);
+
+    /* output baseline data */
+    SEQ_PRINT_AND_LOG("     ");
+    for (i = 0; i < rx; i++)
+        SEQ_PRINT_AND_LOG(" RX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        if ((i % rx) == 0)
+            SEQ_PRINT_AND_LOG("\nTX%02d:%5d,", (i / rx  + 1), (raw[i]-base_raw[i]));
+        else
+            SEQ_PRINT_AND_LOG("%5d,", (raw[i]-base_raw[i]));
+    }
+
+    SEQ_PRINT_AND_LOG("\n\n");
+
+exit:
+
+    if (base_raw)
+        fts_free(base_raw);
+
+    if (raw)
+        fts_free(raw);
+
+    enter_work_mode();
+
+    return 0;
+}
+
+static int proc_test_baseline_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_baseline_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_baseline_fops = {
+    .proc_open   = proc_test_baseline_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_baseline_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_baseline_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Strength test for full size */
+/* Transpose raw */
+void transpose_raw(u8 *src, u8 *dist, int tx, int rx, bool big_endian) {
+    int i = 0;
+    int j = 0;
+
+    for (i = 0; i < tx; i++) {
+        for (j = 0; j < rx; j++) {
+            if (big_endian) {
+                /* keep big_endian. */
+                dist[(j * tx + i) * 2] = src[(i * rx + j) * 2];
+                dist[(j * tx + i) * 2 + 1] = src[(i * rx + j) * 2 + 1];
+           } else {
+                /* transfer to big_endian. */
+                dist[(j * tx + i) * 2] = src[(i * rx + j) * 2 + 1];
+                dist[(j * tx + i) * 2 + 1] = src[(i * rx + j) * 2];
+           }
+        }
+    }
+}
+
+static int proc_test_strength_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    struct fts_ts_data *ts_data = fts_data;
+    int i = 0;
+    int node_num = 0;
+    int self_node = 0;
+    u8 tx = ts_data->pdata->tx_ch_num;
+    u8 rx = ts_data->pdata->rx_ch_num;
+
+    int ms_cap_idx = FTS_CAP_DATA_LEN + 28 + 1;
+    int ss_cap_on_idx = ms_cap_idx +  tx*rx* sizeof(u16);
+    int ss_cap_off_idx = ss_cap_on_idx + FTS_SELF_DATA_LEN * sizeof(u16);
+    short base_result = 0;
+
+    u8 *base_raw = NULL;
+    u8 *trans_raw = NULL;
+    int base_raw_size = 0;
+    int base = 0;
+    u8 tp_finger_cnt = 0;
+    int tp_events_x = 0;
+    int tp_events_y = 0;
+    u8 tp_events_id = 0;
+
+    ret = enter_work_mode();
+    if (ret < 0) {
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    self_node = tx + rx;
+
+    base_raw_size = FTS_FULL_TOUCH_RAW_SIZE(tx, rx);
+    FTS_DEBUG("base_raw size = %d", base_raw_size);
+    base_raw = fts_malloc(base_raw_size);
+    if (!base_raw) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    trans_raw = fts_malloc(node_num * sizeof(u16));
+    if (!trans_raw) {
+        FTS_ERROR("malloc memory for transpose raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get strength data. */
+    ret = fts_test_get_strength(base_raw, base_raw_size);
+    if (ret < 0) {
+        FTS_ERROR("get strength fails");
+        goto exit;
+    }
+
+    tp_finger_cnt = base_raw[1] & 0x0F;
+    if (tp_finger_cnt > FTS_MAX_POINTS_SUPPORT) {
+        FTS_ERROR("The finger count(%d) is over than max fingers(%d)",
+            tp_finger_cnt, FTS_MAX_POINTS_SUPPORT);
+        tp_finger_cnt = FTS_MAX_POINTS_SUPPORT;
+    }
+
+    /*---------Output touch point-----------*/
+    for (i = 0; i < tp_finger_cnt; i++) {
+         base = FTS_ONE_TCH_LEN_V2 * i +3;
+         tp_events_x = ((base_raw[FTS_TOUCH_OFF_E_XH + base] & 0x0F) << 12) \
+                        + ((base_raw[FTS_TOUCH_OFF_XL + base] & 0xFF) << 4) \
+                        + ((base_raw[FTS_TOUCH_OFF_PRE + base] >> 4) & 0x0F);
+         tp_events_y = ((base_raw[FTS_TOUCH_OFF_ID_YH + base] & 0x0F) << 12) \
+                        + ((base_raw[FTS_TOUCH_OFF_YL + base] & 0xFF) << 4) \
+                        + (base_raw[FTS_TOUCH_OFF_PRE + base] & 0x0F);
+         tp_events_id = (base_raw[FTS_TOUCH_OFF_ID_YH + base]) >> 4;
+         seq_printf(s, "Finger ID = %d, x = %d, y = %d\n", tp_events_id,
+                    FTS_TOUCH_HIRES(tp_events_x), FTS_TOUCH_HIRES(tp_events_y));
+    }
+
+    seq_printf(s, "     ");
+    /* transpose data buffer. */
+    FTS_DEBUG("index of MS = %d", ms_cap_idx);
+    transpose_raw(base_raw + ms_cap_idx, trans_raw, tx, rx, true);
+    for (i = 0; i < tx; i++)
+        seq_printf(s, " TX%02d ", (i + 1));
+
+    for (i = 0; i < node_num; i++) {
+        base_result = (int)(trans_raw[(i * 2)] << 8) +
+                      (int)trans_raw[(i * 2) + 1];
+        if ((i % tx) == 0)
+            seq_printf(s, "\nRX%02d:%5d,", (i / tx + 1), base_result);
+        else
+            seq_printf(s, "%5d,", base_result);
+    }
+    /*---------END touch point-----------*/
+
+    /*---------output self of strength data-----------*/
+    seq_printf(s, "\n");
+    seq_printf(s, "Scap raw(proof on):\n");
+    FTS_DEBUG("index of SS_ON = %d", ss_cap_on_idx);
+    for (i = 0; i < self_node; i++) {
+        base_result = (int)(base_raw[(i * 2) + ss_cap_on_idx] << 8) +
+                      (int)base_raw[(i * 2) + ss_cap_on_idx + 1];
+
+        if (i == 0)
+            seq_printf(s, "RX:");
+
+        if (i == rx) {
+            FTS_DEBUG("index(tx) = %d", (ss_cap_on_idx + (i * 2)));
+            seq_printf(s, "\n");
+            seq_printf(s, "TX:");
+        }
+        seq_printf(s, "%d,", base_result);
+    }
+    seq_printf(s, "\n\n");
+    seq_printf(s, "Scap raw(proof off):\n");
+    FTS_DEBUG("index of SS_OFF = %d", ss_cap_off_idx);
+    for (i = 0; i < self_node; i++) {
+        base_result = (int)(base_raw[(i * 2) + ss_cap_off_idx] << 8) +
+                      (int)base_raw[(i * 2) + ss_cap_off_idx + 1];
+
+        if (i == 0)
+            seq_printf(s, "RX:");
+
+        if (i == rx){
+            seq_printf(s, "\n");
+            seq_printf(s, "TX:");
+        }
+        seq_printf(s, "%d,", base_result);
+    }
+
+    seq_printf(s, "\n\n");
+    /*---------END self of strength data-----------*/
+
+exit:
+    if (trans_raw)
+        fts_free(trans_raw);
+
+    if (base_raw)
+        fts_free(base_raw);
+
+    return ret;
+}
+
+static int proc_test_strength_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_strength_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_strength_fops = {
+    .proc_open    = proc_test_strength_open,
+    .proc_read    = seq_read,
+    .proc_lseek   = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_strength_fops = {
+    .owner   = THIS_MODULE,
+    .open    = proc_test_strength_open,
+    .read    = seq_read,
+    .llseek  = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Rawdata_Uniformity test */
+static int proc_test_uniformity_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *uniformity = NULL;
+    bool result = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    struct fts_test *tdata = fts_ftest;
+
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    if (tdata->pretest_raw == false) {
+        FTS_ERROR("please test raw first!");
+        seq_printf(s, "please test raw first!\n");
+        goto exit;
+    }
+    tdata->pretest_raw = false;
+
+    node_num = tx * rx;
+    uniformity = fts_malloc(node_num * 2 * sizeof(int));
+    if (!uniformity) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_uniformity_data(tdata->raw_data, uniformity, tx, rx);
+
+    /* output raw data */
+    SEQ_PRINT_AND_LOG("Rawdata Uniformity TX:\n");
+
+    seq_print_and_log_array(s, tx, rx, uniformity);
+
+    result = compare_array(uniformity,
+                               thr->tx_linearity_min,
+                               thr->tx_linearity_max,
+                               false);
+    SEQ_PRINT_AND_LOG("Rawdata Uniformity TX %s\n", result? "PASS" : "NG");
+
+    SEQ_PRINT_AND_LOG("Rawdata Uniformity RX:\n");
+    seq_print_and_log_array(s, tx, rx, &uniformity[node_num]);
+    result = compare_array(uniformity + node_num,
+                                thr->rx_linearity_min,
+                                thr->rx_linearity_max,
+                                false);
+    SEQ_PRINT_AND_LOG("Rawdata Uniformity RX %s\n", result? "PASS" : "NG");
+
+exit:
+    if (uniformity)
+        fts_free(uniformity);
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Rawdata Uniformity Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return 0;
+}
+
+static int proc_test_uniformity_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_uniformity_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_uniformity_fops = {
+    .proc_open   = proc_test_uniformity_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_uniformity_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_uniformity_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Low High test */
+static int proc_test_low_high_fre_uniformity(bool only_high)
+{
+    int ret = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *uniformity = NULL;
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    uniformity = fts_malloc(node_num * 6 * sizeof(int));
+    if (!uniformity) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_get_low_high_freq_rawdata(fts_ftest, uniformity, only_high);
+
+exit:
+    if (uniformity)
+        fts_free(uniformity);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("low high frq Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return 0;
+}
+
+static int proc_test_low_high_fre_uniformity_show(struct seq_file *s, void *v)
+{
+    seq_printf(s, "%s", fts_ftest->log_buf);
+    return 0;
+}
+
+
+static int proc_test_low_high_fre_uniformity_open(struct inode *inode, struct file *file)
+{
+    proc_test_low_high_fre_uniformity(false);
+    return single_open(file, proc_test_low_high_fre_uniformity_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_low_high_fre_uniformity_fops = {
+    .proc_open   = proc_test_low_high_fre_uniformity_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_low_high_fre_uniformity_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_low_high_fre_uniformity_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+static int proc_test_high_fre_uniformity_open(struct inode *inode,
+                                              struct file *file)
+{
+    proc_test_low_high_fre_uniformity(true);
+    return single_open(file, proc_test_low_high_fre_uniformity_show,
+                       pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_high_fre_uniformity_fops = {
+    .proc_open   = proc_test_high_fre_uniformity_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_high_fre_uniformity_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_high_fre_uniformity_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Scap Rawdata test */
+static int proc_test_sraw_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    int offset = 0;
+    int *sraw = NULL;
+    int fwcheck = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    bool result = true;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    struct fts_test *tdata = fts_ftest;
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    sraw = fts_malloc(node_num * 3 * sizeof(int));
+    if (!sraw) {
+        FTS_ERROR("malloc memory for sraw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_scap_raw(sraw, tx, rx, &fwcheck);
+    SEQ_PRINT_AND_LOG("Scap raw checked:%X\n", fwcheck);
+
+    /* output raw data */
+    if ((fwcheck & 0x01) || (fwcheck & 0x02)) {
+        SEQ_PRINT_AND_LOG("Scap raw(proof on):\n");
+        SEQ_PRINT_AND_LOG("RX:");
+        seq_print_and_log_array(s, 1, rx, sraw);
+
+        SEQ_PRINT_AND_LOG("TX:");
+
+        seq_print_and_log_array(s, 1, tx, &sraw[rx]);
+
+        result = true;
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (((fwcheck & 0x01) && (i < tdata->sc_node.rx_num)) ||
+                ((fwcheck & 0x02) && (i >= tdata->sc_node.rx_num))) {
+                if ((sraw[i + offset] < thr->scap_rawdata_off_min[i]) ||
+                    (sraw[i + offset] > thr->scap_rawdata_off_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, sraw[i],
+                                      thr->scap_rawdata_off_min[i],
+                                      thr->scap_rawdata_off_max[i]);
+                    result = false;
+                }
+            }
+        }
+        SEQ_PRINT_AND_LOG("Scap raw(proof on) %s\n", result? "PASS" : "NG");
+        offset += node_num;
+    }
+
+    if ((fwcheck & 0x04) || (fwcheck & 0x08)) {
+        SEQ_PRINT_AND_LOG("Scap raw(proof off):\n");
+        SEQ_PRINT_AND_LOG("RX:");
+        seq_print_and_log_array(s, 1, rx, &sraw[node_num]);
+
+        SEQ_PRINT_AND_LOG("TX:");
+        seq_print_and_log_array(s, 1, tx, &sraw[node_num + rx]);
+
+        result = true;
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (((fwcheck & 0x04) && (i < tdata->sc_node.rx_num)) ||
+                ((fwcheck & 0x08) && (i >= tdata->sc_node.rx_num))) {
+                if ((sraw[i + offset] < thr->scap_rawdata_on_min[i]) ||
+                    (sraw[i + offset] > thr->scap_rawdata_on_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, sraw[i],
+                                      thr->scap_rawdata_off_min[i],
+                                      thr->scap_rawdata_off_max[i]);
+                    result = false;
+                }
+            }
+        }
+        SEQ_PRINT_AND_LOG("Scap raw(proof off) %s\n", result? "PASS" : "NG");
+        offset += node_num;
+    }
+
+    if ((fwcheck & 0x10) || (fwcheck & 0x20)) {
+        SEQ_PRINT_AND_LOG("Scap raw(high):\n");
+        SEQ_PRINT_AND_LOG("RX:");
+        for (i = node_num * 2; i < node_num * 2 + rx; i++) {
+            SEQ_PRINT_AND_LOG("%d,", sraw[i]);
+        }
+        SEQ_PRINT_AND_LOG("\n");
+
+        SEQ_PRINT_AND_LOG("TX:");
+        for (i = node_num * 2 + rx; i < node_num * 3; i++) {
+            SEQ_PRINT_AND_LOG("%d,", sraw[i]);
+        }
+        SEQ_PRINT_AND_LOG("\n");
+
+        result = true;
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (((fwcheck & 0x01) && (i < tdata->sc_node.rx_num)) ||
+                ((fwcheck & 0x02) && (i >= tdata->sc_node.rx_num))) {
+                if ((sraw[i + offset] < thr->scap_rawdata_hov_min[i]) ||
+                    (sraw[i + offset] > thr->scap_rawdata_hov_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, sraw[i],
+                                      thr->scap_rawdata_hov_min[i],
+                                      thr->scap_rawdata_hov_max[i]);
+                    result = false;
+                }
+            }
+        }
+        SEQ_PRINT_AND_LOG("Scap raw(high) %s\n", result? "PASS" : "NG");
+    }
+
+exit:
+    if (sraw)
+        fts_free(sraw);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Scap raw Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_sraw_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_sraw_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_sraw_fops = {
+    .proc_open   = proc_test_sraw_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_sraw_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_sraw_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Scap CB test */
+static int proc_test_scb_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    int *scb = NULL;
+    int fwcheck = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    bool result = true;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    struct fts_test *tdata = fts_ftest;
+    int offset = 0;
+
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    scb = fts_malloc(node_num * 6 * sizeof(int));
+    if (!scb) {
+        FTS_ERROR("malloc memory for scb fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get raw data */
+    fts_test_get_scap_cb(scb, tx, rx, &fwcheck);
+    SEQ_PRINT_AND_LOG("Scap cb checked:%X\n", fwcheck);
+    /* output raw data */
+    if ((fwcheck & 0x01) || (fwcheck & 0x02)) {
+        SEQ_PRINT_AND_LOG("Scap CB(proof on):\n");
+        SEQ_PRINT_AND_LOG("RX:");
+        seq_print_and_log_array(s, 1, rx, scb);
+
+        SEQ_PRINT_AND_LOG("TX:");
+        seq_print_and_log_array(s, 1, tx, &scb[rx]);
+
+        result = true;
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (((fwcheck & 0x01) && (i < tdata->sc_node.rx_num)) ||
+                ((fwcheck & 0x02) && (i >= tdata->sc_node.rx_num))) {
+                if ((scb[i + offset] < thr->scap_cb_off_min[i]) ||
+                    (scb[i + offset] > thr->scap_cb_off_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, scb[i + offset],
+                                      thr->scap_cb_off_min[i],
+                                      thr->scap_cb_off_max[i]);
+                    result = false;
+                }
+            }
+        }
+        SEQ_PRINT_AND_LOG("Scap CB(proof on) %s\n", result? "PASS" : "NG");
+        offset += tdata->sc_node.node_num;
+    }
+
+    if ((fwcheck & 0x04) || (fwcheck & 0x08)) {
+        SEQ_PRINT_AND_LOG("Scap CB(proof off):\n");
+        SEQ_PRINT_AND_LOG("RX:");
+        seq_print_and_log_array(s, 1, rx, &scb[node_num]);
+
+        SEQ_PRINT_AND_LOG("TX:");
+        seq_print_and_log_array(s, 1, tx, &scb[node_num + rx]);
+
+        result = true;
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (((fwcheck & 0x04) && (i < tdata->sc_node.rx_num)) ||
+                ((fwcheck & 0x08) && (i >= tdata->sc_node.rx_num))) {
+                if ((scb[i + offset] < thr->scap_cb_on_min[i]) ||
+                    (scb[i + offset] > thr->scap_cb_on_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, scb[i + offset],
+                                      thr->scap_cb_on_min[i],
+                                      thr->scap_cb_on_max[i]);
+                    result = false;
+                }
+            }
+        }
+        SEQ_PRINT_AND_LOG("Scap CB(proof off) %s\n", result? "PASS" : "NG");
+        offset += tdata->sc_node.node_num;
+    }
+
+    if ((fwcheck & 0x10) || (fwcheck & 0x20)) {
+        SEQ_PRINT_AND_LOG("Scap raw(high):\n");
+
+        SEQ_PRINT_AND_LOG("RX:");
+        for (i = node_num * 2; i < node_num * 2 + rx; i++) {
+            SEQ_PRINT_AND_LOG("%d,", scb[i]);
+        }
+        SEQ_PRINT_AND_LOG("\n");
+
+        SEQ_PRINT_AND_LOG("TX:");
+        for (i = node_num * 2 + rx; i < node_num * 3; i++) {
+            SEQ_PRINT_AND_LOG("%d,", scb[i]);
+        }
+        SEQ_PRINT_AND_LOG("\n");
+
+        result = true;
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (((fwcheck & 0x10) && (i < tdata->sc_node.rx_num)) ||
+                ((fwcheck & 0x20) && (i >= tdata->sc_node.rx_num))) {
+                if ((scb[i + offset] < thr->scap_cb_hov_min[i]) ||
+                    (scb[i + offset] > thr->scap_cb_hov_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, scb[i + offset],
+                                      thr->scap_cb_hov_min[i],
+                                      thr->scap_cb_hov_max[i]);
+                    result = false;
+                }
+            }
+        }
+        SEQ_PRINT_AND_LOG("Scap CB(high) %s\n", result? "PASS" : "NG");
+        offset += tdata->sc_node.node_num;
+    }
+
+exit:
+    if (scb)
+        fts_free(scb);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Scap CB Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_scb_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_scb_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_scb_fops = {
+    .proc_open   = proc_test_scb_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_scb_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_scb_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Noise test */
+static int proc_noise_test(void)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    int scap_node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *noise = NULL;
+    int *snoise = NULL;
+    bool result = 0;
+    int fwcheck = 0;
+    int offset = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    struct fts_test *tdata = fts_ftest;
+    char *log_buf = tdata->log_buf;
+    int count = 0;
+    int row_start = 0;
+
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    noise = fts_malloc(node_num * sizeof(int));
+    if (!noise) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /*get raw data*/
+    fts_test_get_noise(noise, tx, rx);
+
+    /*output raw data*/
+
+    FTS_INFO("Noise Test:\n");
+    count += scnprintf(log_buf + count, PAGE_SIZE, "Noise Test:\n");
+    row_start = count;
+
+    for (i = 0; i < node_num; i++) {
+        if ((i + 1) % rx) {
+            count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", noise[i]);
+        } else {
+            count += scnprintf(log_buf + count, PAGE_SIZE, "%d,\n", noise[i]);
+
+            FTS_INFO("%s", &log_buf[row_start]);
+            row_start = count;
+        }
+    }
+
+    count += scnprintf(log_buf + count, PAGE_SIZE, "\n\n");
+
+    result = compare_array(noise, thr->noise_min, thr->noise_max, false);
+    FTS_INFO("Noise Test %s\n", result? "PASS" : "NG");
+    count += scnprintf(log_buf + count, PAGE_SIZE, "Noise Test %s\n", result? "PASS" : "NG");
+
+    /**************scap noise*******************/
+    scap_node_num = tx + rx;
+    snoise = fts_malloc(scap_node_num * 3 * sizeof(int));
+    if (!snoise) {
+        FTS_ERROR("malloc memory for snoise fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+    fts_test_get_scap_noise(snoise, tx, rx, &fwcheck);
+    FTS_INFO("Scap noise checked:%X\n", fwcheck);
+    count += scnprintf(log_buf + count, PAGE_SIZE, "Scap noise checked:%X\n", fwcheck);
+
+        /* output raw data */
+        if ((fwcheck & 0x01) || (fwcheck & 0x02)) {
+            FTS_INFO("Scap noise(proof on):\n");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "Scap noise(proof on):\n");
+            FTS_INFO("RX:");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "RX:");
+
+            row_start = count;
+            for (i = 0; i < rx; i++) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", snoise[i]);
+            }
+            FTS_INFO("%s", &log_buf[row_start]);
+
+            count += scnprintf(log_buf + count, PAGE_SIZE, "\n");
+            FTS_INFO("TX:");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "TX:");
+
+            row_start = count;
+            for (i = rx; i < scap_node_num; i++) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", snoise[i]);
+            }
+            FTS_INFO("%s", &log_buf[row_start]);
+            count += scnprintf(log_buf + count, PAGE_SIZE, "\n");
+
+            result = true;
+            for (i = 0; i < tdata->sc_node.node_num; i++) {
+                if (0 == tdata->node_valid_sc[i])
+                    continue;
+
+                if (((fwcheck & 0x01) && (i < tdata->sc_node.rx_num)) ||
+                    ((fwcheck & 0x02) && (i >= tdata->sc_node.rx_num))) {
+                    if ((snoise[i + offset] < thr->basic.scap_noise_on_min) ||
+                        (snoise[i + offset] > thr->basic.scap_noise_on_max)) {
+                        FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                          i + 1, snoise[i],
+                                          thr->basic.scap_noise_on_min,
+                                          thr->basic.scap_noise_on_max);
+                        result = false;
+                    }
+                }
+            }
+
+            FTS_INFO("Scap noise(proof on) %s\n", result? "PASS" : "NG");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "Scap noise(proof on) %s\n", result? "PASS" : "NG");
+            offset += scap_node_num;
+        }
+
+        if ((fwcheck & 0x04) || (fwcheck & 0x08)) {
+            FTS_INFO("Scap noise(proof off):\n");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "Scap noise(proof off):\n");
+            FTS_INFO("RX:");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "RX:");
+
+            row_start = count;
+            for (i = scap_node_num; i < scap_node_num + rx; i++) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", snoise[i]);
+            }
+            FTS_INFO("%s", &log_buf[row_start]);
+
+            count += scnprintf(log_buf + count, PAGE_SIZE, "\n");
+
+            FTS_INFO("TX:");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "TX:");
+            row_start = count;
+            for (i = scap_node_num + rx; i < scap_node_num * 2; i++) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", snoise[i]);
+            }
+            FTS_INFO("%s", &log_buf[row_start]);
+            count += scnprintf(log_buf + count, PAGE_SIZE, "\n");
+
+            result = true;
+            for (i = 0; i < tdata->sc_node.node_num; i++) {
+                if (0 == tdata->node_valid_sc[i])
+                    continue;
+
+                if (((fwcheck & 0x04) && (i < tdata->sc_node.rx_num)) ||
+                    ((fwcheck & 0x08) && (i >= tdata->sc_node.rx_num))) {
+                    if ((snoise[i + offset] < thr->basic.scap_noise_off_min) ||
+                        (snoise[i + offset] > thr->basic.scap_noise_off_max)) {
+                        FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                          i + 1, snoise[i],
+                                          thr->basic.scap_noise_off_min,
+                                          thr->basic.scap_noise_off_max);
+                        result = false;
+                    }
+                }
+            }
+
+            FTS_INFO("Scap noise(proof off) %s\n", result? "PASS" : "NG");
+            count += scnprintf(log_buf + count, PAGE_SIZE,
+            "Scap noise(proof off) %s\n", result? "PASS" : "NG");
+            offset += scap_node_num;
+        }
+
+        if ((fwcheck & 0x10) || (fwcheck & 0x20)) {
+            count += scnprintf(log_buf + count, PAGE_SIZE, "Scap noise(high):\n");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "RX:");
+            for (i = scap_node_num * 2; i < scap_node_num * 2 + rx; i++) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", snoise[i]);
+            }
+            count += scnprintf(log_buf + count, PAGE_SIZE, "\n");
+
+            count += scnprintf(log_buf + count, PAGE_SIZE, "TX:");
+            for (i = scap_node_num * 2 + rx; i < scap_node_num * 3; i++) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", snoise[i]);
+            }
+
+            count += scnprintf(log_buf + count, PAGE_SIZE, "\n");
+
+            result = true;
+            for (i = 0; i < tdata->sc_node.node_num; i++) {
+                if (0 == tdata->node_valid_sc[i])
+                    continue;
+
+                if (((fwcheck & 0x01) && (i < tdata->sc_node.rx_num)) ||
+                    ((fwcheck & 0x02) && (i >= tdata->sc_node.rx_num))) {
+                    if ((snoise[i + offset] < thr->basic.scap_noise_off_min) ||
+                        (snoise[i + offset] > thr->basic.scap_noise_off_max)) {
+                        FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                          i + 1, snoise[i],
+                                          thr->basic.scap_noise_off_min,
+                                          thr->basic.scap_noise_off_max);
+                        result = false;
+                    }
+                }
+            }
+
+            count += scnprintf(log_buf + count, PAGE_SIZE,
+                "Scap noise(high) %s\n", result? "PASS" : "NG");
+        }
+exit:
+    if (noise)
+        fts_free(noise);
+    if (snoise)
+        fts_free(snoise);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Scap noise Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_noise_show(struct seq_file *s, void *v)
+{
+    seq_printf(s, "%s", fts_ftest->log_buf);
+    return 0;
+}
+
+
+static int proc_test_noise_open(struct inode *inode, struct file *file)
+{
+    proc_noise_test();
+    return single_open(file, proc_test_noise_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_noise_fops = {
+    .proc_open   = proc_test_noise_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release  = single_release,
+};
+#else
+static const struct file_operations proc_test_noise_fops = {
+    .owner  = THIS_MODULE,
+    .open   = proc_test_noise_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Short test */
+static int proc_test_short_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *short_data = NULL;
+    int *short_data_cg = NULL;
+    int *short_data_cc = NULL;
+    bool result = 1;
+    bool cg_result = 1;
+    bool cc_result = 1;
+    int code = 0;
+    struct fts_test *tdata = fts_ftest;
+    u8 ab_ch[SC_NUM_MAX + 1] = { 0 };
+    u8 ab_ch_num = 0;
+    int temp = 0;
+    int j = 0;
+    int adc_cnt = 0;
+    bool is_cc_short = false;
+    bool is_cg_short = false;
+    int tmp_num = 0;
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+    /* get Tx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+    /* get Rx chanel number */
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx + rx;
+    short_data = fts_malloc(node_num * sizeof(int));
+    short_data_cg = fts_malloc(node_num * sizeof(int));
+    short_data_cc = fts_malloc(node_num * sizeof(int));
+    if (!short_data || !short_data_cg || !short_data_cc) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /* get short all data */
+    fts_test_get_short(short_data, tx, rx);
+
+    for (i = 0; i < node_num; i++) {
+        code = short_data[i];
+
+        if (code > 1500) {
+            SEQ_PRINT_AND_LOG("adc(%d) > 1500 fail", code);
+            result = false;
+            continue;
+        }
+
+        if ((212 - ((code * 250 / 2047) + 40)) == 0) {
+            short_data[i] = 50000;
+            continue;
+        }
+        short_data[i] = fts_abs(((code * 25 / 2047 + 4) * 2005) /
+                                (212 - ((code * 250 / 2047) + 40)));
+        if (short_data[i] < tdata->ic.mc_sc.thr.basic.short_cg) {
+            ab_ch_num++;
+            ab_ch[ab_ch_num] = i;
+            result = false;
+        }
+    }
+    /* output short data */
+    SEQ_PRINT_AND_LOG("TX:");
+    seq_print_and_log_array(s, 1, tx, short_data);
+
+    SEQ_PRINT_AND_LOG("RX:");
+    seq_print_and_log_array(s, 1, rx, &short_data[tx]);
+
+    if (result == true) goto short_end;
+
+    ab_ch[0] = ab_ch_num;
+    if (ab_ch_num) {
+        SEQ_PRINT_AND_LOG("\nabnormal ch:[%*ph]\n", ab_ch_num, ab_ch);
+    }
+    /********************get short cg********************/
+    fts_test_get_short_ch_to_gnd(short_data_cg, ab_ch, tx, rx);
+    for (i = 0; i < ab_ch_num; i++) {
+        temp = short_data_cg[i];
+        if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+            short_data_cg[i] = 50000;
+            continue;
+        }
+        short_data_cg[i] = fts_abs(((temp * 25 / 2047 + 4) * 2005) /
+                                   (212 - ((temp * 250 / 2047) + 40)));
+        if (short_data_cg[i] < tdata->ic.mc_sc.thr.basic.short_cg) {
+            cg_result = false;
+            if (!is_cg_short) {
+                SEQ_PRINT_AND_LOG("\nGND Short:\n");
+                is_cg_short = true;
+            }
+
+            if (ab_ch[i + 1] <= tx) {
+                SEQ_PRINT_AND_LOG("Tx%d with GND:", ab_ch[i + 1]);
+            } else {
+                SEQ_PRINT_AND_LOG( "Rx%d with GND:", (ab_ch[i + 1] - tx));
+            }
+            SEQ_PRINT_AND_LOG("%d(K)\n", short_data_cg[i]);
+        }
+    }
+
+
+    /********************get short cc********************/
+    tmp_num = ab_ch_num * (ab_ch_num - 1) / 2;
+    tmp_num = (tmp_num > node_num) ? node_num : tmp_num;
+    fts_test_get_short_ch_to_ch(short_data_cc, ab_ch, tx, rx);
+
+    for (i = 0; i < ab_ch_num; i++) {
+        for (j = i + 1; j < ab_ch_num; j++) {
+            if (adc_cnt >= tmp_num)
+                break;
+
+            temp = short_data_cc[adc_cnt];
+            if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+                short_data_cc[adc_cnt] = 50000;
+                continue;
+            }
+            short_data_cc[adc_cnt] = fts_abs(((temp * 25 / 2047 + 4) * 2005) /
+                                        (212 - ((temp * 250 / 2047) + 40)));
+            if (short_data_cc[adc_cnt] < tdata->ic.mc_sc.thr.basic.short_cc) {
+                cc_result = false;
+                if (!is_cc_short) {
+                    SEQ_PRINT_AND_LOG("\nMutual Short:\n");
+                    is_cc_short = true;
+                }
+
+                if (ab_ch[i + 1] <= tx) {
+                    SEQ_PRINT_AND_LOG("Tx%d with", (ab_ch[i + 1]));
+                } else {
+                    SEQ_PRINT_AND_LOG("Rx%d with", (ab_ch[i + 1] - tx));
+                }
+
+                if (ab_ch[j + 1] <= tx) {
+                    SEQ_PRINT_AND_LOG(" Tx%d", (ab_ch[j + 1] ) );
+                } else {
+                    SEQ_PRINT_AND_LOG(" Rx%d", (ab_ch[j + 1] - tx));
+                }
+                SEQ_PRINT_AND_LOG(":%d(K)\n", short_data_cc[adc_cnt]);
+            }
+            adc_cnt++;
+        }
+    }
+
+short_end:
+    SEQ_PRINT_AND_LOG("Short Test %s\n", result? "PASS" : "NG");
+
+exit:
+    if (short_data)
+        fts_free(short_data);
+    fts_free(short_data_cg);
+    fts_free(short_data_cc);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Short Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_short_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_short_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_short_fops = {
+    .proc_open   = proc_test_short_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_short_fops = {
+    .open   = proc_test_short_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+/* Panel_Differ test */
+static int proc_test_panel_differ_show(struct seq_file *s, void *v)
+{
+    int ret = 0;
+    int node_num = 0;
+    u8 tx = 0;
+    u8 rx = 0;
+    int *panel_differ = NULL;
+    bool result = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    ktime_t start_time = ktime_get();
+
+    ret = fts_proc_test_entry(goog_get_test_limit_name());
+    if (ret < 0) {
+        FTS_TEST_ERROR("fts_test_main_init fail");
+        goto exit;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_ERROR("enter factory mode fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHX_NUM, &tx);
+    if (ret < 0) {
+        FTS_ERROR("read tx fails");
+        goto exit;
+    }
+
+    ret = fts_read_reg(FACTORY_REG_CHY_NUM, &rx);
+    if (ret < 0) {
+        FTS_ERROR("read rx fails");
+        goto exit;
+    }
+
+    node_num = tx * rx;
+    panel_differ = fts_malloc(node_num * sizeof(int));
+    if (!panel_differ) {
+        FTS_ERROR("malloc memory for raw fails");
+        ret = -ENOMEM;
+        goto exit;
+    }
+
+    /*get panel_differ data*/
+    fts_test_get_panel_differ(panel_differ, tx, rx);
+
+    /*output panel_differ data*/
+
+    seq_print_and_log_array(s, tx, rx, panel_differ);
+
+    result = compare_array(panel_differ,
+                               thr->panel_differ_min,
+                               thr->panel_differ_max,
+                               false);
+
+
+exit:
+    SEQ_PRINT_AND_LOG("Panel Differ Test %s\n", result? "PASS" : "NG");
+
+    if (panel_differ)
+        fts_free(panel_differ);
+
+    fts_proc_test_exit();
+    enter_work_mode();
+
+    FTS_INFO("Panel Differ Test %lldms taken",ktime_ms_delta(ktime_get(), start_time));
+    return ret;
+}
+
+static int proc_test_panel_differ_open(struct inode *inode, struct file *file)
+{
+    return single_open(file, proc_test_panel_differ_show, pde_data(inode));
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
+static const struct proc_ops proc_test_panel_differ_fops = {
+    .proc_open   = proc_test_panel_differ_open,
+    .proc_read   = seq_read,
+    .proc_lseek  = seq_lseek,
+    .proc_release = single_release,
+};
+#else
+static const struct file_operations proc_test_panel_differ_fops = {
+    .open   = proc_test_panel_differ_open,
+    .read   = seq_read,
+    .llseek = seq_lseek,
+    .release = single_release,
+};
+#endif
+
+#define FTS_PROC_TEST_DIR       "selftest"
+
+struct proc_dir_entry *fts_proc_test_dir;
+struct proc_dir_entry *proc_run_os_test;
+struct proc_dir_entry *proc_test_fwver;
+struct proc_dir_entry *proc_test_chnum;
+struct proc_dir_entry *proc_test_reset_pin;
+struct proc_dir_entry *proc_test_sw_reset;
+struct proc_dir_entry *proc_test_lot_code;
+
+struct proc_dir_entry *proc_test_int_pin;
+struct proc_dir_entry *proc_test_raw;
+struct proc_dir_entry *proc_test_baseline;
+struct proc_dir_entry *proc_test_strength;
+struct proc_dir_entry *proc_test_uniformity;
+struct proc_dir_entry *proc_test_low_high_rawdata;
+struct proc_dir_entry *proc_test_high_rawdata;
+struct proc_dir_entry *proc_test_sraw;
+struct proc_dir_entry *proc_test_scb;
+struct proc_dir_entry *proc_test_noise;
+struct proc_dir_entry *proc_test_short;
+struct proc_dir_entry *proc_test_panel_differ;
+
+
+static int fts_create_test_procs(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    proc_run_os_test = proc_create_data("run_os_test", S_IRUSR,
+        fts_proc_test_dir, &proc_run_os_test_fops, fts_ftest);
+    if (!proc_run_os_test) {
+        FTS_ERROR("create proc_run_os_test entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_fwver = proc_create("FW_Version", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_fwver_fops);
+    if (!proc_test_fwver) {
+        FTS_ERROR("create proc_test_fwver entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_chnum = proc_create("Channel_Num", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_chnum_fops);
+    if (!proc_test_chnum) {
+        FTS_ERROR("create proc_test_chnum entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_reset_pin = proc_create("Reset_Pin", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_hw_reset_fops);
+    if (!proc_test_reset_pin) {
+        FTS_ERROR("create proc_test_reset_pin entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_sw_reset = proc_create("SW_Reset", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_sw_reset_fops);
+    if (!proc_test_sw_reset) {
+        FTS_ERROR("create proc_test_sw_reset entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_lot_code = proc_create("Lot_Code", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_lot_code_fops);
+    if (!proc_test_lot_code) {
+        FTS_ERROR("create proc_test_lot_code entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_int_pin = proc_create("INT_PIN", S_IRUSR,
+        ts_data->proc_touch_entry, &proc_test_int_fops);
+    if (!proc_test_int_pin) {
+        FTS_ERROR("create proc_test_int_pin entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_raw = proc_create_data("Rawdata", S_IRUSR,
+        fts_proc_test_dir, &proc_test_raw_fops, ts_data);
+    if (!proc_test_raw) {
+        FTS_ERROR("create proc_test_raw entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_baseline = proc_create_data("Baseline", S_IRUSR,
+        fts_proc_test_dir, &proc_test_baseline_fops, ts_data);
+    if (!proc_test_baseline) {
+        FTS_ERROR("create proc_test_baseline entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_strength = proc_create_data("Strength", S_IRUSR,
+        fts_proc_test_dir, &proc_test_strength_fops, ts_data);
+    if (!proc_test_strength) {
+        FTS_ERROR("create proc_test_strength entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_uniformity = proc_create_data("Rawdata_Uniformity", S_IRUSR,
+        fts_proc_test_dir, &proc_test_uniformity_fops, ts_data);
+    if (!proc_test_uniformity) {
+        FTS_ERROR("create proc_test_uniformity entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_low_high_rawdata = proc_create_data("Low_High_Freq_Rawdata_Uniformity", S_IRUSR,
+        fts_proc_test_dir, &proc_test_low_high_fre_uniformity_fops, ts_data);
+    if (!proc_test_low_high_rawdata) {
+        FTS_ERROR("create Low_High_Freq_Rawdata_Uniformity entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_high_rawdata = proc_create_data("High_Freq_Rawdata_Uniformity", S_IRUSR,
+        fts_proc_test_dir, &proc_test_high_fre_uniformity_fops, ts_data);
+    if (!proc_test_high_rawdata) {
+        FTS_ERROR("create High_Freq_Rawdata_Uniformity entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_sraw = proc_create_data("Scap_Rawdata", S_IRUSR,
+        fts_proc_test_dir, &proc_test_sraw_fops, ts_data);
+    if (!proc_test_sraw) {
+        FTS_ERROR("create proc_test_sraw entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_scb = proc_create_data("Scap_CB", S_IRUSR,
+        fts_proc_test_dir, &proc_test_scb_fops, ts_data);
+    if (!proc_test_scb) {
+        FTS_ERROR("create proc_test_scb entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_noise = proc_create_data("Noise", S_IRUSR,
+        fts_proc_test_dir, &proc_test_noise_fops, ts_data);
+    if (!proc_test_noise) {
+        FTS_ERROR("create proc_test_noise entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_short = proc_create_data("Short", S_IRUSR,
+        fts_proc_test_dir, &proc_test_short_fops, ts_data);
+    if (!proc_test_short) {
+        FTS_ERROR("create proc_test_short entry fail");
+        return -ENOMEM;
+    }
+
+    proc_test_panel_differ = proc_create_data("Panel_Differ", S_IRUSR,
+        fts_proc_test_dir, &proc_test_panel_differ_fops, ts_data);
+    if (!proc_test_panel_differ) {
+        FTS_ERROR("create proc_test_panel_differ entry fail");
+        return -ENOMEM;
+    }
+
+    FTS_INFO("create test procs succeeds");
+    return ret;
+}
+
+#define FTS_LOG_SIZE (20 * 1024)
+#define FTS_TX_NUM 16
+#define FTS_RX_NUM 36
+
+int fts_test_init(struct fts_ts_data *ts_data)
+{
+    int ret = 0;
+
+    FTS_TEST_FUNC_ENTER();
+    /* get test function, must be the first step */
+    ret = fts_test_func_init(ts_data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("test functions init fail");
+        return ret;
+    }
+
+    ret = sysfs_create_group(&ts_data->dev->kobj, &fts_test_attribute_group);
+    if (0 != ret) {
+        FTS_TEST_ERROR("sysfs(test) create fail");
+        sysfs_remove_group(&ts_data->dev->kobj, &fts_test_attribute_group);
+    } else {
+        FTS_TEST_DBG("sysfs(test) create successfully");
+    }
+
+    fts_proc_test_dir = proc_mkdir(FTS_PROC_TEST_DIR,
+        ts_data->proc_touch_entry);
+    if (!fts_proc_test_dir) {
+        FTS_ERROR("create %s fails", FTS_PROC_TEST_DIR);
+        return -ENOMEM;
+    }
+
+    ret = fts_create_test_procs(ts_data);
+    if (ret) {
+        FTS_TEST_ERROR("create test procs fail");
+    }
+
+#if CSV_SUPPORT
+    fts_ftest->proc_csv = proc_create_data("fts_test_csv", 0777, NULL, &fts_proccsv_fops, fts_ftest);
+    if (NULL == fts_ftest->proc_csv) {
+        FTS_ERROR("create proc_csv entry fail");
+    }
+#endif
+#if TXT_SUPPORT
+    fts_ftest->proc_txt = proc_create_data("fts_test_txt", 0777, NULL, &fts_proctxt_fops, fts_ftest);
+    if (NULL == fts_ftest->proc_txt) {
+        FTS_ERROR("create proc_txt entry fail");
+    }
+#endif
+    fts_ftest->raw_data = fts_malloc(FTS_TX_NUM * FTS_RX_NUM * sizeof(int));
+    if (!fts_ftest->raw_data) {
+        FTS_ERROR("malloc memory for raw fails");
+    }
+
+    fts_ftest->log_buf = fts_malloc(FTS_LOG_SIZE);
+    if (!fts_ftest->log_buf) {
+        FTS_ERROR("malloc memory for log buf fails");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+
+    return ret;
+}
+
+int fts_test_exit(struct fts_ts_data *ts_data)
+{
+    struct fts_test *tdata = fts_ftest;
+    
+    FTS_TEST_FUNC_ENTER();
+
+    if (fts_proc_test_dir)
+        proc_remove(fts_proc_test_dir);
+    sysfs_remove_group(&ts_data->dev->kobj, &fts_test_attribute_group);
+#if CSV_SUPPORT
+        proc_remove(tdata->proc_csv);
+        if (tdata->csv_file_buf) {
+            vfree(tdata->csv_file_buf);
+            tdata->csv_file_buf = NULL;
+        }
+#endif
+#if TXT_SUPPORT
+        proc_remove(tdata->proc_txt);
+        if (tdata->testresult) {
+            vfree(tdata->testresult);
+            tdata->testresult = NULL;
+        }
+#endif
+    fts_free(fts_ftest->log_buf);
+    fts_free(fts_ftest->raw_data);
+    fts_free(tdata);
+
+    FTS_TEST_FUNC_EXIT();
+    return 0;
+}
diff --git a/ft3683u/focaltech_test/focaltech_test.h b/ft3683u/focaltech_test/focaltech_test.h
new file mode 100644
index 0000000..f322582
--- /dev/null
+++ b/ft3683u/focaltech_test/focaltech_test.h
@@ -0,0 +1,784 @@
+/************************************************************************
+* Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
+*
+* File Name: focaltech_test.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Abstract: test entry for all IC
+*
+************************************************************************/
+#ifndef _TEST_LIB_H
+#define _TEST_LIB_H
+
+/*****************************************************************************
+* Included header files
+*****************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <asm/uaccess.h>
+#include <linux/i2c.h>//iic
+#include <linux/delay.h>//msleep
+#include <linux/string.h>
+#include <asm/unistd.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include "../focaltech_core.h"
+#include "focaltech_test_ini.h"
+
+/*****************************************************************************
+* Macro definitions using #define
+*****************************************************************************/
+#define FTS_INI_FILE_PATH                       "/mnt/sdcard/"
+#define FTS_CSV_FILE_NAME                       "testdata.csv"
+#define FTS_TXT_FILE_NAME                       "testresult.txt"
+#define false 0
+#define true  1
+#define TEST_ICSERIES_LEN                       (8)
+#define TEST_ICSERIES(x)                        ((x) >> TEST_ICSERIES_LEN)
+
+#define TEST_OPEN_MAX_VALUE                     (255)
+#define BYTES_PER_TIME                          (32)  /* max:128 */
+/* CSV & TXT */
+#define CSV_LINE2_BUFFER_LEN                    (1024)
+#define CSV_BUFFER_LEN                          (1024*80*5)
+#define TXT_BUFFER_LEN                          (1024*80*5)
+
+#define TEST_SAVE_FAIL_RESULT                   0
+
+/*-----------------------------------------------------------
+Test Status
+-----------------------------------------------------------*/
+#define RESULT_NULL                             0
+#define RESULT_PASS                             1
+#define RESULT_NG                               2
+
+#define TX_NUM_MAX                              60
+#define RX_NUM_MAX                              100
+#define SC_NUM_MAX                  ((TX_NUM_MAX) + (RX_NUM_MAX))
+#define NUM_MAX_SC                              (144)
+#define KEY_NUM_MAX                             6
+#define TEST_ITEM_COUNT_MAX                     32
+#define TEST_ITEM_NAME_MAX                      32
+#define TEST_SHORT_RES_MAX                      0xFFFF
+
+/*
+ * factory test registers
+ */
+#define ENTER_WORK_FACTORY_RETRIES              5
+
+#define START_SCAN_RETRIES_INCELL               20
+#define START_SCAN_RETRIES_DELAY_INCELL         16
+#define FACTORY_TEST_RETRY                      50
+#define FACTORY_TEST_DELAY                      18
+#define FACTORY_TEST_RETRY_DELAY                100
+
+#define DIVIDE_MODE_ADDR                        0x00
+#define REG_FW_MAJOR_VER                        0xA6
+#define REG_FW_MINOR_VER                        0xAD
+#define REG_VA_TOUCH_THR                        0x80
+#define REG_VKEY_TOUCH_THR                      0x82
+
+#define FACTORY_REG_LINE_ADDR                   0x01
+#define FACTORY_REG_CHX_NUM                     0x02
+#define FACTORY_REG_CHY_NUM                     0x03
+#define FACTORY_REG_CLB                         0x04
+#define FACTORY_REG_DATA_SELECT                 0x06
+#define FACTORY_REG_RAWBUF_SELECT               0x09
+#define FACTORY_REG_KEY_CBWIDTH                 0x0B
+#define FACTORY_REG_PARAM_UPDATE_STATE          0x0E
+#define FACTORY_REG_PARAM_UPDATE_STATE_TOUCH    0xB5
+#define FACTORY_REG_SHORT_TEST_EN               0x0F
+#define FACTORY_REG_SHORT_TEST_STATE            0x10
+#define FACTORY_REG_LCD_NOISE_START             0x11
+#define FACTORY_REG_LCD_NOISE_FRAME             0x12
+#define FACTORY_REG_LCD_NOISE_TEST_STATE        0x13
+#define FACTORY_REG_LCD_NOISE_TTHR              0x14
+#define FACTORY_REG_OPEN_START                  0x15
+#define FACTORY_REG_OPEN_STATE                  0x16
+#define FACTORY_REG_OPEN_ADDR                   0xCF
+#define FACTORY_REG_OPEN_IDLE                   0x03
+#define FACTORY_REG_OPEN_BUSY                   0x01
+#define FACTORY_REG_CB_ADDR_H                   0x18
+#define FACTORY_REG_CB_ADDR_L                   0x19
+#define FACTORY_REG_ORDER_ADDR_H                0x1A
+#define FACTORY_REG_ORDER_ADDR_L                0x1B
+#define FACTORY_REG_LCD_NOISE_STATE             0x1E
+#define FACTORY_REG_KEYSHORT_EN                 0x2E
+#define FACTORY_REG_KEYSHORT_STATE              0x2F
+#define FACTORY_REG_GCB                         0xBD
+#define FACTORY_REG_LOT_CODE                    0x63
+
+#define FACTORY_REG_LEFT_KEY                    0x1E
+#define FACTORY_REG_RIGHT_KEY                   0x1F
+#define FACTORY_REG_OPEN_REG20                  0x20
+#define FACTORY_REG_OPEN_REG21                  0x21
+#define FACTORY_REG_OPEN_REG22                  0x22
+#define FACTORY_REG_OPEN_REG23                  0x23
+#define FACTORY_REG_OPEN_REG2E                  0x2E
+#define FACTORY_REG_OPEN_REG86                  0x86
+#define FACTORY_REG_K1                          0x31
+#define FACTORY_REG_K2                          0x32
+#define FACTORY_REG_RAWDATA_ADDR                0x6A
+#define FACTORY_REG_ORDER_ADDR                  0x6C
+#define FACTORY_REG_CB_ADDR                     0x6E
+#define FACTORY_REG_SHORT_ADDR                  0x89
+#define FACTORY_REG_RAWDATA_TEST_EN             0x9E
+#define FACTORY_REG_CB_TEST_EN                  0x9F
+#define FACTORY_REG_OPEN_TEST_EN                0xA0
+#define FACTORY_REG_RAWDATA_TARGET              0xCA
+
+
+/* mc_sc */
+#define FACTORY_REG_FRE_LIST                    0x0A
+#define FACTORY_REG_DATA_TYPE                   0x5B
+#define FACTORY_REG_NORMALIZE                   0x16
+#define FACTORY_REG_RAWDATA_ADDR_MC_SC          0x36
+#define FACTORY_REG_PATTERN                     0x53
+#define FACTORY_REG_NOMAPPING                   0x54
+#define FACTORY_REG_CHX_NUM_NOMAP               0x55
+#define FACTORY_REG_CHY_NUM_NOMAP               0x56
+#define FACTORY_REG_WC_SEL                      0x09
+#define FACTORY_REG_HC_SEL                      0x0F
+#define FACTORY_REG_MC_SC_MODE                  0x44
+#define FACTORY_REG_MC_SC_CB_ADDR_OFF           0x45
+#define FACTORY_REG_MC_SC_CB_H_ADDR_OFF         0x49
+#define FACTORY_REG_MC_SC_CB_ADDR               0x4E
+#define FACTROY_REG_SHORT_TEST_EN               0x07
+#define FACTROY_REG_SHORT_CA                    0x01
+#define FACTROY_REG_SHORT_CC                    0x02
+#define FACTROY_REG_SHORT_CG                    0x03
+#define FACTROY_REG_SHORT_OFFSET                0x04
+#define FACTROY_REG_SHORT_AB_CH                 0x58
+#define FACTROY_REG_SHORT_RES_LEVEL             0x5A
+#define FACTORY_REG_SHORT_ADDR_MC               0xF4
+#define FACTORY_REG_FIR                         0xFB
+
+#define FACTROY_REG_SCAP_GCB_TX                 0xBC
+#define FACTROY_REG_SCAP_GCB_RX                 0xBE
+
+#define FACTORY_REG_FRE_LIST_VALUE_MAIN         0x00
+#define FACTORY_REG_FRE_LIST_VALUE_LOWEST       0x80
+#define FACTORY_REG_FRE_LIST_VALUE_HIGHEST      0x81
+
+
+/* noise */
+#define FACTORY_REG_MAXDIFF_EN                  0x1A
+#define FACTORY_REG_MAXDIFF_FLAG                0x1B
+#define FACTORY_REG_FRAME_NUM_H                 0x1C
+#define FACTORY_REG_FRAME_NUM_L                 0x1D
+#define FACTORY_REG_NOISE_ADDR                  0xCE
+
+#define FACTROY_REG_SHORT2_TEST_EN              0xC0
+#define FACTROY_REG_SHORT2_CA                   0x01
+#define FACTROY_REG_SHORT2_CC                   0x02
+#define FACTROY_REG_SHORT2_CG                   0x03
+#define FACTROY_REG_SHORT2_OFFSET               0x04
+#define FACTROY_REG_SHORT2_RES_LEVEL            0xC1
+#define FACTROY_REG_SHORT2_DEALY                0xC2
+#define FACTROY_REG_SHORT2_TEST_STATE           0xC3
+#define FACTORY_REG_SHORT2_ADDR_MC              0xC4
+#define FACTROY_REG_SHORT2_AB_CH                0xC6
+
+/* sc */
+#define FACTORY_REG_SCAN_ADDR2                  0x08
+#define FACTORY_REG_CH_NUM_SC                   0x0A
+#define FACTORY_REG_KEY_NUM_SC                  0x0B
+#define FACTORY_REG_SC_CB_ADDR_OFF              0x33
+#define FACTORY_REG_SC_CB_ADDR                  0x39
+#define FACTORY_REG_RAWDATA_SADDR_SC            0x34
+#define FACTORY_REG_RAWDATA_ADDR_SC             0x35
+#define FACTORY_REG_CB_SEL                      0x41
+#define FACTORY_REG_FMODE                       0xAE
+
+#define TEST_RETVAL_00                          0x00
+#define TEST_RETVAL_AA                          0xAA
+
+#define FTS_MAX_SORT_SC                         32768
+#define FTS_MIN_SORT_SC                         0
+
+#define FTS_TMP_REG_AD                          0xAD    //for Register R/W test
+#define FTS_TMP_REG_88                          0x88    //for Register R/W test
+#define FTS_TMP_REG_SOFT_RESET                  0xFC
+
+/*****************************************************************************
+* enumerations, structures and unions
+*****************************************************************************/
+struct item_info {
+    char name[TEST_ITEM_NAME_MAX];
+    int code;
+    int *data;
+    int datalen;
+    int result;
+    int mc_sc;
+    int key_support;
+};
+
+struct fts_test_data {
+    int item_count;
+    struct item_info info[TEST_ITEM_COUNT_MAX];
+};
+
+/* incell */
+struct incell_testitem {
+    u32 short_test                  : 1;
+    u32 open_test                   : 1;
+    u32 cb_test                     : 1;
+    u32 rawdata_test                : 1;
+    u32 lcdnoise_test               : 1;
+    u32 keyshort_test               : 1;
+    u32 mux_open_test               : 1;
+};
+
+struct incell_threshold_b {
+    int short_res_min;
+    int short_res_vk_min;
+    int open_cb_min;
+    int open_k1_check;
+    int open_k1_value;
+    int open_k2_check;
+    int open_k2_value;
+    int cb_min;
+    int cb_max;
+    int cb_vkey_check;
+    int cb_min_vk;
+    int cb_max_vk;
+    int rawdata_min;
+    int rawdata_max;
+    int rawdata_vkey_check;
+    int rawdata_min_vk;
+    int rawdata_max_vk;
+    int lcdnoise_frame;
+    int lcdnoise_coefficient;
+    int lcdnoise_coefficient_vkey;
+    int open_diff_min;
+    int open_nmos;
+    int keyshort_k1;
+    int keyshort_cb_max;
+    int rawdata2_min;
+    int rawdata2_max;
+    int mux_open_cb_min;
+    int open_delta_V;
+};
+
+struct incell_threshold {
+    struct incell_threshold_b basic;
+    int *rawdata_min;
+    int *rawdata_max;
+    int *rawdata2_min;
+    int *rawdata2_max;
+    int *cb_min;
+    int *cb_max;
+};
+
+struct incell_test {
+    struct incell_threshold thr;
+    union {
+        int tmp;
+        struct incell_testitem item;
+    } u;
+};
+
+/* mc_sc */
+enum mapping_type {
+    MAPPING = 0,
+    NO_MAPPING = 1,
+};
+
+struct mc_sc_testitem {
+    u32 rawdata_test                : 1;
+    u32 rawdata_uniformity_test     : 1;
+    u32 scap_cb_test                : 1;
+    u32 scap_rawdata_test           : 1;
+    u32 short_test                  : 1;
+    u32 panel_differ_test           : 1;
+    u32 noise_test                  : 1;
+    u32 mcap_cmb_test               : 1;
+    u32 scap_noise_test           : 1;
+    u32 low_fre_rawdata_uniformity_test   : 1;
+};
+
+struct mc_sc_threshold_b {
+    int rawdata_h_min;
+    int rawdata_h_max;
+    int rawdata_set_hfreq;
+    int rawdata_l_min;
+    int rawdata_l_max;
+    int rawdata_set_lfreq;
+    int uniformity_check_tx;
+    int uniformity_check_rx;
+    int uniformity_check_min_max;
+    int uniformity_tx_hole;
+    int uniformity_rx_hole;
+    int uniformity_min_max_hole;
+    int scap_cb_off_min;
+    int scap_cb_off_max;
+    int scap_cb_wp_off_check;
+    int scap_cb_on_min;
+    int scap_cb_on_max;
+    int scap_cb_wp_on_check;
+    int scap_rawdata_off_min;
+    int scap_rawdata_off_max;
+    int scap_rawdata_wp_off_check;
+    int scap_rawdata_on_min;
+    int scap_rawdata_on_max;
+    int scap_rawdata_wp_on_check;
+    int short_cg;
+    int short_cc;
+    int panel_differ_min;
+    int panel_differ_max;
+    int scap_cb_hi_min;
+    int scap_cb_hi_max;
+    int scap_cb_hi_check;
+    int scap_rawdata_hi_min;
+    int scap_rawdata_hi_max;
+    int scap_rawdata_hi_check;
+    int scap_cb_hov_min;
+    int scap_cb_hov_max;
+    int scap_cb_hov_check;
+    int scap_rawdata_hov_min;
+    int scap_rawdata_hov_max;
+    int scap_rawdata_hov_check;
+    int noise_max;
+    int noise_framenum;
+    int noise_mode;
+    int noise_polling;
+
+    int scap_cb_on_gcb_min;
+    int scap_cb_on_gcb_max;
+    int scap_cb_off_gcb_min;
+    int scap_cb_off_gcb_max;
+    int scap_cb_hi_gcb_min;
+    int scap_cb_hi_gcb_max;
+    int scap_cb_on_cf_min;
+    int scap_cb_on_cf_max;
+    int scap_cb_off_cf_min;
+    int scap_cb_off_cf_max;
+    int scap_cb_hi_cf_min;
+    int scap_cb_hi_cf_max;
+
+    int scap_cb_hov_gcb_min;
+    int scap_cb_hov_gcb_max;
+    
+    int mcap_cmb_min;
+    int mcap_cmb_max;
+
+    int scap_noise_off_min;
+    int scap_noise_off_max;
+    int scap_noise_wp_off_check;
+
+    int scap_noise_on_min;
+    int scap_noise_on_max;
+    int scap_noise_wp_on_check;
+
+    int scap_noise_hi_min;
+    int scap_noise_hi_max;
+    int scap_noise_hi_check;
+
+    int scap_noise_hov_min;
+    int scap_noise_hov_max;
+    int scap_noise_hov_check;
+    
+    int low_scan_freq;
+    int low_shift;
+    int low_va_vul;
+
+    int high_scan_freq;
+    int high_shift;
+    int high_va_vul;
+
+    int low_freq_uniformity_max;
+    int low_freq_uniformity_min;
+    int high_freq_uniformity_max;
+    int high_freq_uniformity_min;
+
+    int low_freq_uniformity_check_en;
+    int high_freq_uniformity_check_en;
+    
+    int low_freq_uniformity_check_tx;
+    int low_freq_uniformity_check_rx;
+    int high_freq_uniformity_check_tx;
+    int high_freq_uniformity_check_rx;
+
+    int low_freq_uniformity_tx_hol;
+    int low_freq_uniformity_rx_hol;
+    int high_freq_uniformity_tx_hol;
+    int high_freq_uniformity_rx_hol;
+};
+
+struct mc_sc_threshold {
+    struct mc_sc_threshold_b basic;
+    int *rawdata_h_min;
+    int *rawdata_h_max;
+    int *rawdata_l_min;
+    int *rawdata_l_max;
+    int *tx_linearity_max;
+    int *tx_linearity_min;
+    int *rx_linearity_max;
+    int *rx_linearity_min;
+    int *scap_cb_off_min;
+    int *scap_cb_off_max;
+    int *scap_cb_on_min;
+    int *scap_cb_on_max;
+    int *scap_cb_hi_min;
+    int *scap_cb_hi_max;
+    int *scap_cb_hov_min;
+    int *scap_cb_hov_max;
+    int *scap_rawdata_off_min;
+    int *scap_rawdata_off_max;
+    int *scap_rawdata_on_min;
+    int *scap_rawdata_on_max;
+    int *scap_rawdata_hi_min;
+    int *scap_rawdata_hi_max;
+    int *scap_rawdata_hov_min;
+    int *scap_rawdata_hov_max;
+    int *panel_differ_min;
+    int *panel_differ_max;
+
+    int *noise_min;
+    int *noise_max;
+
+    int *low_freq_rawdata_min;
+    int *low_freq_rawdata_max;
+    int *high_freq_rawdata_min;
+    int *high_freq_rawdata_max;
+
+    int *low_freq_rawdata_tx_linearity_max;
+    int *low_freq_rawdata_rx_linearity_max;
+    int *low_freq_rawdata_tx_linearity_min;
+    int *low_freq_rawdata_rx_linearity_min;
+    
+    int *high_freq_rawdata_tx_linearity_max;
+    int *high_freq_rawdata_rx_linearity_max;
+    int *high_freq_rawdata_tx_linearity_min;
+    int *high_freq_rawdata_rx_linearity_min;
+};
+
+struct mc_sc_test {
+    struct mc_sc_threshold thr;
+    union {
+        u32 tmp;
+        struct mc_sc_testitem item;
+    } u;
+};
+
+/* sc */
+struct sc_testitem {
+    u32 rawdata_test                : 1;
+    u32 cb_test                     : 1;
+    u32 delta_cb_test               : 1;
+    u32 short_test                  : 1;
+};
+
+struct sc_threshold_b {
+    int rawdata_min;
+    int rawdata_max;
+    int cb_min;
+    int cb_max;
+    int dcb_base;
+    int dcb_differ_max;
+    int dcb_key_check;
+    int dcb_key_differ_max;
+    int dcb_ds1;
+    int dcb_ds2;
+    int dcb_ds3;
+    int dcb_ds4;
+    int dcb_ds5;
+    int dcb_ds6;
+    int dcb_critical_check;
+    int dcb_cs1;
+    int dcb_cs2;
+    int dcb_cs3;
+    int dcb_cs4;
+    int dcb_cs5;
+    int dcb_cs6;
+    int short_min;
+};
+
+struct sc_threshold {
+    struct sc_threshold_b basic;
+    int *rawdata_min;
+    int *rawdata_max;
+    int *cb_min;
+    int *cb_max;
+    int *dcb_sort;
+    int *dcb_base;
+};
+
+struct sc_test {
+    struct sc_threshold thr;
+    union {
+        u32 tmp;
+        struct sc_testitem item;
+    } u;
+};
+
+enum test_hw_type {
+    IC_HW_INCELL = 1,
+    IC_HW_MC_SC,
+    IC_HW_SC,
+};
+
+enum test_scan_mode {
+    SCAN_NORMAL = 0,
+    SCAN_SC,
+};
+
+struct fts_test_node {
+    int channel_num;
+    int tx_num;
+    int rx_num;
+    int node_num;
+    int key_num;
+};
+
+struct fts_test {
+    struct fts_ts_data *ts_data;
+    struct fts_test_node node;
+    struct fts_test_node sc_node;
+    char *csv_file_buf;
+    u8 fw_major_ver;
+    u8 fw_minor_ver;
+    u8 va_touch_thr;
+    u8 vk_touch_thr;
+    bool key_support;
+    bool v3_pattern;
+    u8 fre_num;
+    int *item1_data;
+    int *item2_data;
+    int *item3_data;
+    int *item4_data;
+    int *item5_data;
+    int *item6_data;
+    int *item7_data;
+    int *item9_data;
+    int csv_item_cnt;
+    int csv_item_scb;
+    int csv_item_snoise;
+    int csv_item_sraw;
+    u8 mapping;
+    u8 normalize;
+    int test_num;
+    int *buffer;
+    int buffer_length;
+    int *node_valid;
+    int *node_valid_sc;
+    int basic_thr_count;
+    int code1;
+    int code2;
+    int offset;
+    int null_noise_max;
+    union {
+        struct incell_test incell;
+        struct mc_sc_test mc_sc;
+        struct sc_test sc;
+    } ic;
+
+    struct seq_file *s;
+    struct test_funcs *func;
+    struct fts_test_data testdata;
+    struct proc_dir_entry *proc_csv;
+    struct proc_dir_entry *proc_txt;
+    char *testresult;
+    int testresult_len;
+    int result;
+#if defined(TEST_SAVE_FAIL_RESULT) && TEST_SAVE_FAIL_RESULT
+    struct timeval tv;
+#endif
+    struct ini_data ini;
+    char *log_buf;
+    int *raw_data;
+    bool pretest_raw;
+
+};
+
+struct test_funcs {
+    u16 ctype[FTS_MAX_COMPATIBLE_TYPE];
+    enum test_hw_type hwtype;
+    int startscan_mode;
+    int key_num_total;
+    bool rawdata2_support;
+    bool force_touch;
+    bool mc_sc_short_v2;
+    bool raw_u16;
+    bool cb_high_support;
+    bool param_update_support;
+    int (*param_init)(void);
+    int (*init)(void);
+    int (*start_test)(void);
+    void (*save_data_private)(char *buf, int *len);
+};
+
+enum byte_mode {
+    DATA_ONE_BYTE,
+    DATA_TWO_BYTE,
+};
+/* mc_sc */
+enum normalize_type {
+    NORMALIZE_OVERALL,
+    NORMALIZE_AUTO,
+};
+
+enum wp_type {
+    WATER_PROOF_OFF = 0,
+    WATER_PROOF_ON = 1,
+    HIGH_SENSITIVITY = 2,
+    HOV = 3,
+    WATER_PROOF_ON_TX = 100,
+    WATER_PROOF_ON_RX,
+    WATER_PROOF_OFF_TX,
+    WATER_PROOF_OFF_RX,
+};
+/* mc end */
+
+/* sc */
+enum factory_mode {
+    FACTORY_NORMAL,
+    FACTORY_TESTMODE_1,
+    FACTORY_TESTMODE_2,
+};
+
+enum dcb_sort_num {
+    DCB_SORT_MIN = 1,
+    DCB_SORT_MAX = 6,
+};
+
+struct dcb_sort_d {
+    int ch_num;
+    int deviation;
+    int critical;
+    int min;
+    int max;
+};
+/* sc end */
+
+enum csv_itemcode_incell {
+    CODE_ENTER_FACTORY_MODE = 0,
+    CODE_RAWDATA_TEST = 7,
+    CODE_CB_TEST = 12,
+    CODE_SHORT_TEST = 15,
+    CODE_OPEN_TEST = 25,
+    CODE_LCD_NOISE_TEST = 27,
+    CODE_MUX_OPEN_TEST = 41,
+};
+
+enum csv_itemcode_mc_sc {
+    CODE_M_RAWDATA_TEST = 7,
+    CODE_M_SCAP_CB_TEST = 9,
+    CODE_M_SCAP_RAWDATA_TEST = 10,
+    CODE_M_WEAK_SHORT_CIRCUIT_TEST = 15,
+    CODE_M_RAWDATA_UNIFORMITY_TEST = 16,
+    CODE_M_PANELDIFFER_TEST = 20,
+    CODE_M_NOISE_TEST = 14,
+    CODE_M_CMB_TEST = 39,
+};
+
+
+enum csv_itemcode_sc {
+    CODE_S_RAWDATA_TEST = 7,
+    CODE_S_CB_TEST = 13,
+    CODE_S_DCB_TEST = 14,
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+extern struct test_funcs test_func_ft5672;
+
+extern struct fts_test *fts_ftest;
+
+void sys_delay(int ms);
+int fts_abs(int value);
+void print_buffer(int *buffer, int length, int line_num);
+int fts_test_read_reg(u8 addr, u8 *val);
+int fts_test_write_reg(u8 addr, u8 val);
+int fts_test_read(u8 addr, u8 *readbuf, int readlen);
+int fts_test_write(u8 addr, u8 *writebuf, int writelen);
+int enter_work_mode(void);
+int enter_factory_mode(void);
+int read_mass_data(u8 addr, int byte_num, int *buf);
+int chip_clb(void);
+int wait_state_update(u8 retval);
+int get_cb_incell(u16 saddr, int byte_num, int *cb_buf);
+int short_get_adcdata_incell(u8 retval, u8 ch_num, int byte_num, int *adc_buf);
+int start_scan(void);
+int get_rawdata(int *data);
+int get_cb_sc(int byte_num, int *cb_buf, enum byte_mode mode);
+bool compare_data(int *data, int min, int max, int min_vk, int max_vk, bool key);
+bool compare_array(int *data, int *min, int *max, bool key);
+void show_data(int *data, bool key);
+/* mc_sc */
+int mapping_switch(u8 mapping);
+bool get_fw_wp(u8 wp_channel_select, enum wp_type water_proof_type);
+int get_cb_mc_sc(u8 wp, int byte_num, int *cb_buf, enum byte_mode mode);
+int get_rawdata_mc_sc(enum wp_type wp, int *data);
+int get_rawdata_mc(u8 fre, u8 fir, int *rawdata);
+int short_get_adc_data_mc(u8 retval, int byte_num, int *adc_buf, u8 mode);
+bool compare_mc_sc(bool, bool, int *, int *, int *);
+void show_data_mc_sc(int *data);
+void *fts_malloc(size_t size);
+void fts_free_proc(void *p);
+void fts_test_save_data(char *name, int code, int *data, int datacnt,
+                        bool mc_sc, bool key, bool result);
+
+#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
+int fts_proc_test_entry(char *ini_file_name);
+int fts_proc_test_exit(void);
+#endif /* IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) */
+
+#define fts_malloc_r(p, size) do {\
+    if (NULL == p) {\
+        p = fts_malloc(size);\
+        if (NULL == p) {\
+            return -ENOMEM;\
+        }\
+    }\
+} while(0)
+
+#define fts_free(p) do {\
+    if (p) {\
+        fts_free_proc(p);\
+        p = NULL;\
+    }\
+} while(0)
+
+#define CSV_SUPPORT             1
+#define TXT_SUPPORT             1
+
+#undef pr_fmt
+#define pr_fmt(fmt) "gtd: FTS_TS: " fmt
+
+#define FTS_TEST_DBG(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
+
+#define FTS_TEST_FUNC_ENTER() pr_debug("%s: Enter\n", __func__)
+#define FTS_TEST_FUNC_EXIT() pr_debug("%s: Exit(%d)\n", __func__, __LINE__)
+
+#define FTS_TEST_INFO(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
+
+#define FTS_TEST_ERROR(fmt, ...) pr_err(fmt, ##__VA_ARGS__)
+
+#define FTS_TEST_SAVE_INFO(fmt, args...) do { \
+    if (fts_ftest->testresult) { \
+        fts_ftest->testresult_len += snprintf( \
+        fts_ftest->testresult + fts_ftest->testresult_len, \
+        TXT_BUFFER_LEN, \
+        fmt, ##args);\
+    } \
+    pr_info(fmt, ##args);\
+} while (0)
+
+#define FTS_TEST_SAVE_ERR(fmt, args...)  do { \
+    if (fts_ftest->testresult && (fts_ftest->testresult_len < TXT_BUFFER_LEN)) { \
+        fts_ftest->testresult_len += snprintf( \
+        fts_ftest->testresult + fts_ftest->testresult_len, \
+        TXT_BUFFER_LEN, \
+        fmt, ##args);\
+    } \
+    pr_err(fmt, ##args);\
+} while (0)
+#endif
diff --git a/ft3683u/focaltech_test/focaltech_test_ini.c b/ft3683u/focaltech_test/focaltech_test_ini.c
new file mode 100644
index 0000000..d78ec91
--- /dev/null
+++ b/ft3683u/focaltech_test/focaltech_test_ini.c
@@ -0,0 +1,1349 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R)£¬All Rights Reserved.
+*
+* File Name: focaltech_test_ini.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Abstract: parsing function of INI file
+*
+************************************************************************/
+#include "focaltech_test.h"
+
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define FTS_INI_REQUEST_SUPPORT              1
+
+struct ini_ic_type ic_types[] = {
+    {"FT5X46",  0x54000002},
+    {"FT5X46i", 0x54010002},
+    {"FT5526",  0x54020002},
+    {"FT3X17",  0x54030002},
+    {"FT5436",  0x54040002},
+    {"FT3X27",  0x54050002},
+    {"FT5526i", 0x54060002},
+    {"FT5416",  0x54070002},
+    {"FT5426",  0x54080002},
+    {"FT5435",  0x54090002},
+    {"FT7681",  0x540A0002},
+    {"FT7661",  0x540B0002},
+    {"FT7511",  0x540C0002},
+    {"FT7421",  0x540D0002},
+    {"FT7311",  0x54100002},
+
+    {"FT5526_003", 0x40020082},
+    {"FT5426_003", 0x40030082},
+    {"FT3427G_003", 0x40040082},
+    {"FT3427_003", 0x40050082},
+    {"FT5446_003", 0x40000082},
+    {"FT5446_Q03", 0x40000082},
+    {"FT5446_P03", 0x55060081},
+    {"FT5446DQS-W01", 0x40000082},
+
+    {"FT5452",  0x55000081},
+    {"FT3518",  0x55010081},
+    {"FT3558",  0x55020081},
+    {"FT3528",  0x55030081},
+    {"FT5536",  0x55040081},
+    {"FT3418",  0x55070081},
+    {"FT5536L", 0x55080081},
+
+    {"FT5472",  0x8F000083},
+    {"FT5446U", 0x8F010083},
+    {"FT5456U", 0x8F020083},
+    {"FT3417U", 0x8F030083},
+    {"FT5426U", 0x8F040083},
+    {"FT3428",  0x8F050083},
+    {"FT3437U", 0x8F060083},
+
+    {"FT5822",  0x58000001},
+    {"FT5626",  0x58010001},
+    {"FT5726",  0x58020001},
+    {"FT5826B", 0x58030001},
+    {"FT3617",  0x58040001},
+    {"FT3717",  0x58050001},
+    {"FT7811",  0x58060001},
+    {"FT5826S", 0x58070001},
+    {"FT3517U", 0x58090001},
+    {"FT3557",  0x580A0001},
+
+    {"FT6X36",  0x63000003},
+    {"FT3X07",  0x63010003},
+    {"FT6416",  0x63020003},
+    {"FT6336G/U", 0x63030003},
+    {"FT7401",  0x63040003},
+    {"FT3407U", 0x63050003},
+    {"FT6236U", 0x63060003},
+    {"FT6436U", 0x63070003},
+
+    {"FT3267",  0x63080004},
+    {"FT3367",  0x63090004},
+
+    {"FT6216",  0x64000084},
+    {"FT7302",  0x64010084},
+    {"FT7202",  0x64020084},
+    {"FT3308",  0x64030084},
+    {"FT6446",  0x64040084},
+
+    {"FT8607",  0x81000009},
+    {"FT8716",  0x82000005},
+    {"FT8716U", 0x44000005},
+    {"FT8716F", 0x8A000005},
+    {"FT8613",  0x4500000C},
+
+    {"FT8736",  0x83000006},
+
+    {"FT8201",  0x87010010},
+    {"FT7250",  0x8702001A},
+
+    {"FT8006U", 0x8900000B},
+    {"FT8006S", 0x8901000B},
+    {"FT8006S-AA", 0x9B000019},
+    {"FT8016", 0x9B01001D},
+
+    {"FT8719",  0x8E00000D},
+    {"FT8615",  0x9100000F},
+
+    {"FT8739",  0x8D00000E},
+
+    {"FT8006P", 0x93000011},
+    {"FT7120",  0x9E00001B},
+
+    {"FT7251",  0x8C000012},
+    {"FT7252",  0x92000013},
+
+    {"FT8613S", 0x94000014},
+
+    {"FT8756",  0x95000015},
+    {"FT8656",  0x95010018},
+
+    {"FT8302",  0x97000016},
+
+    {"FT8009",  0x98000017},
+
+    {"FT8720",  0x9C00001C},
+
+    {"FT3068",  0x65010085},
+    {"FT3168",  0x65020085},
+    {"FT3067",  0x65030085},
+    {"FT3268",  0x65040085},
+    {"FT6346U", 0x65050085},
+    {"FT6146",  0x65060085},
+    {"FT6346G", 0x65070085},
+
+    {"FT5726_V03", 0x580C0086},
+    {"FT5726_003", 0x580C0086},
+
+    {"FT3618",  0x59010087},
+    {"FT5646",  0x59020087},
+    {"FT3A58",  0x59030087},
+    {"FT3B58",  0x59040087},
+    {"FT3D58",  0x59050087},
+    {"FT5A36",  0x59060087},
+    {"FT5B36",  0x59070087},
+    {"FT5D36",  0x59080087},
+    {"FT5A46",  0x59090087},
+    {"FT5B46",  0x590A0087},
+    {"FT5D46",  0x590B0087},
+    {"FT5936",  0x590C0087},
+    {"FT5946",  0x590D0087},
+
+    {"FT3658U", 0x5A010088},
+
+    {"FT2388",  0x9D00001E},
+};
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function prototypes
+*****************************************************************************/
+/* Works only for digits and letters, but small and fast */
+#define TOLOWER(x) ((x) | 0x20)
+static int fts_strncmp(const char *cs, const char *ct, int count)
+{
+    u8 c1 = 0, c2 = 0;
+
+    while (count) {
+        if  ((*cs == '\0') || (*ct == '\0'))
+            return -1;
+        c1 = TOLOWER(*cs++);
+        c2 = TOLOWER(*ct++);
+        if (c1 != c2)
+            return c1 < c2 ? -1 : 1;
+        if (!c1)
+            break;
+        count--;
+    }
+
+    return 0;
+}
+
+static int fts_isspace(int x)
+{
+    if (x == ' ' || x == '\t' || x == '\n' || x == '\f' || x == '\b' || x == '\r')
+        return 1;
+    else
+        return 0;
+}
+
+static int fts_isdigit(int x)
+{
+    if (x <= '9' && x >= '0')
+        return 1;
+    else
+        return 0;
+}
+
+static long fts_atol(char *nptr)
+{
+    int c; /* current char */
+    long total; /* current total */
+    int sign; /* if ''-'', then negative, otherwise positive */
+    /* skip whitespace */
+    while ( fts_isspace((int)(unsigned char)*nptr) )
+        ++nptr;
+    c = (int)(unsigned char) * nptr++;
+    sign = c; /* save sign indication */
+    if (c == '-' || c == '+')
+        c = (int)(unsigned char) * nptr++; /* skip sign */
+    total = 0;
+    while (fts_isdigit(c)) {
+        total = 10 * total + (c - '0'); /* accumulate digit */
+        c = (int)(unsigned char) * nptr++; /* get next char */
+    }
+    if (sign == '-')
+        return -total;
+    else
+        return total; /* return result, negated if necessary */
+}
+
+static int fts_atoi(char *nptr)
+{
+    return (int)fts_atol(nptr);
+}
+
+static int fts_test_get_ini_via_request_firmware(struct ini_data *ini, char *fwname)
+{
+    int ret = 0;
+    const struct firmware *fw = NULL;
+    struct device *dev = &fts_data->input_dev->dev;
+
+#if !FTS_INI_REQUEST_SUPPORT
+    return -EINVAL;
+#endif
+
+    ret = request_firmware(&fw, fwname, dev);
+    if (0 == ret) {
+        FTS_TEST_INFO("firmware request(%s) success", fwname);
+        ini->data = vmalloc(fw->size + 1);
+        if (ini->data == NULL) {
+            FTS_TEST_ERROR("ini->data buffer vmalloc fail");
+            ret = -ENOMEM;
+        } else {
+            memcpy(ini->data, fw->data, fw->size);
+            ini->data[fw->size] = '\n';
+            ini->length = fw->size + 1;
+        }
+    } else {
+        FTS_TEST_INFO("firmware request(%s) fail,ret=%d", fwname, ret);
+    }
+
+    if (fw != NULL) {
+        release_firmware(fw);
+        fw = NULL;
+    }
+
+    return ret;
+}
+
+
+static void str_space_remove(char *str)
+{
+    char *t = str;
+    char *s = str;
+
+    while (*t != '\0') {
+        if (*t != ' ') {
+            *s = *t;
+            s++;
+        }
+        t++;
+    }
+
+    *s = '\0';
+}
+
+static void print_ini_data(struct ini_data *ini)
+{
+    int i = 0;
+    int j = 0;
+    struct ini_section *section = NULL;
+    struct ini_keyword *keyword = NULL;
+    struct fts_test *tdata = fts_ftest;
+
+    if (tdata && tdata->ts_data && (tdata->ts_data->log_level < 10)) {
+        return;
+    }
+
+    if (!ini || !ini->tmp) {
+        FTS_TEST_DBG("ini is null");
+        return;
+    }
+
+    FTS_TEST_DBG("section num:%d, keyword num total:%d",
+                 ini->section_num, ini->keyword_num_total);
+    for (i = 0; i < ini->section_num; i++) {
+        section = &ini->section[i];
+        FTS_TEST_DBG("section name:[%s] keyword num:%d",
+                     section->name, section->keyword_num);
+        for (j = 0; j < section->keyword_num; j++) {
+            keyword = &section->keyword[j];
+            FTS_TEST_DBG("%s=%s", keyword->name, keyword->value);
+        }
+    }
+}
+
+static int ini_get_line(char *filedata, char *line_data, int *line_len)
+{
+    int i = 0;
+    int line_length = 0;
+    int type;
+
+    /* get a line data */
+    for (i = 0; i < MAX_INI_LINE_LEN; i++) {
+        if (('\n' == filedata[i]) || ('\r' == filedata[i])) {
+            line_data[line_length++] = '\0';
+            if (('\n' == filedata[i + 1]) || ('\r' == filedata[i + 1])) {
+                line_length++;
+            }
+            break;
+        } else {
+            line_data[line_length++] = filedata[i];
+        }
+    }
+
+    if (i >= MAX_INI_LINE_LEN) {
+        FTS_TEST_ERROR("line length(%d)>max(%d)", line_length, MAX_INI_LINE_LEN);
+        return -ENODATA;
+    }
+
+    /* remove space */
+    str_space_remove(line_data);
+
+    /* confirm line type */
+    if (('\0' == line_data[0]) || ('#' == line_data[0])) {
+        type = LINE_OTHER;
+    } else if ('[' == line_data[0]) {
+        type = LINE_SECTION;
+    } else {
+        type = LINE_KEYWORD; /* key word */
+    }
+
+    *line_len = line_length;
+    return type;
+}
+
+static int ini_parse_keyword(struct ini_data *ini, char *line_buffer)
+{
+    int i = 0;
+    int offset = 0;
+    int length = strlen(line_buffer);
+    struct ini_section *section = NULL;
+
+    for (i = 0; i < length; i++) {
+        if (line_buffer[i] == '=')
+            break;
+    }
+
+    if ((i == 0) || (i >= length)) {
+        FTS_TEST_ERROR("mark(=)in keyword line fail");
+        return -ENODATA;
+    }
+
+    if ((ini->section_num > 0) && (ini->section_num < MAX_INI_SECTION_NUM)) {
+        section = &ini->section[ini->section_num - 1];
+    }
+
+    if (NULL == section) {
+        FTS_TEST_ERROR("section is null");
+        return -ENODATA;
+    }
+
+    offset = ini->keyword_num_total;
+    if (offset > MAX_KEYWORD_NUM) {
+        FTS_TEST_ERROR("keyword num(%d)>max(%d),please check MAX_KEYWORD_NUM",
+                       ini->keyword_num_total, MAX_KEYWORD_NUM);
+        return -ENODATA;
+    }
+    memcpy(ini->tmp[offset].name, &line_buffer[0], i);
+    ini->tmp[offset].name[i] = '\0';
+    memcpy(ini->tmp[offset].value, &line_buffer[i + 1], length - i - 1);
+    ini->tmp[offset].value[length - i - 1] = '\0';
+    section->keyword_num++;
+    ini->keyword_num_total++;
+
+    return 0;
+}
+
+static int ini_parse_section(struct ini_data *ini, char *line_buffer)
+{
+    int length = strlen(line_buffer);
+    struct ini_section *section = NULL;
+
+    if ((length <= 2) || (length > MAX_KEYWORD_NAME_LEN)) {
+        FTS_TEST_ERROR("section line length fail");
+        return -EINVAL;
+    }
+
+    if ((ini->section_num < 0) || (ini->section_num >= MAX_INI_SECTION_NUM)) {
+        FTS_TEST_ERROR("section_num(%d) fail", ini->section_num);
+        return -EINVAL;
+    }
+    section = &ini->section[ini->section_num];
+    memcpy(section->name, line_buffer + 1, length - 2);
+    section->name[length - 2] = '\0';
+    FTS_TEST_INFO("section:%s, keyword offset:%d",
+                  section->name, ini->keyword_num_total);
+    section->keyword = (struct ini_keyword *)&ini->tmp[ini->keyword_num_total];
+    section->keyword_num = 0;
+    ini->section_num++;
+    if (ini->section_num > MAX_INI_SECTION_NUM) {
+        FTS_TEST_ERROR("section num(%d)>max(%d), please check MAX_INI_SECTION_NUM",
+                       ini->section_num, MAX_INI_SECTION_NUM);
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+static int ini_init_inidata(struct ini_data *ini)
+{
+    int pos = 0;
+    int ret = 0;
+    char line_buffer[MAX_INI_LINE_LEN] = { 0 };
+    int line_len = 0;
+
+    if (!ini || !ini->data || !ini->tmp) {
+        FTS_TEST_DBG("ini/data/tmp is null");
+        return -EINVAL;
+    }
+
+    while (pos < ini->length) {
+        ret = ini_get_line(ini->data + pos, line_buffer, &line_len);
+        if (ret < 0) {
+            FTS_TEST_ERROR("ini_get_line fail");
+            return ret;
+        } else if (ret == LINE_KEYWORD) {
+            ret = ini_parse_keyword(ini, line_buffer);
+            if (ret < 0) {
+                FTS_TEST_ERROR("ini_parse_keyword fail");
+                return ret;
+            }
+        } else if (ret == LINE_SECTION) {
+            ret = ini_parse_section(ini, line_buffer);
+            if (ret < 0) {
+                FTS_TEST_ERROR("ini_parse_section fail");
+                return ret;
+            }
+        }
+
+        pos += line_len;
+    }
+
+    print_ini_data(ini);
+    return 0;
+}
+
+static int ini_get_key(char *section_name, char *key_name, char *value)
+{
+    int i = 0;
+    int j = 0;
+    struct ini_data *ini = &fts_ftest->ini;
+    struct ini_section *section;
+    struct ini_keyword *keyword;
+    int key_len = 0;
+    int log_level = fts_ftest->ts_data->log_level;
+
+    if (log_level >= 10) {
+        FTS_TEST_DBG("section name:%s, key name:%s", section_name, key_name);
+        FTS_TEST_DBG("section num:%d", ini->section_num);
+    }
+
+    for (i = 0; i < ini->section_num; i++) {
+        section = &ini->section[i];
+        key_len = strlen(section_name);
+        if (key_len != strlen(section->name))
+            continue;
+        if (fts_strncmp(section->name, section_name, key_len) != 0)
+            continue;
+
+        if (log_level >= 10) {
+            FTS_TEST_DBG("section name:%s keyword num:%d",
+                         section->name, section->keyword_num);
+        }
+        for (j = 0; j < section->keyword_num; j++) {
+            keyword = &section->keyword[j];
+            key_len = strlen(key_name);
+            if (key_len == strlen(keyword->name)) {
+                if (0 == fts_strncmp(keyword->name, key_name, key_len)) {
+                    key_len = strlen(keyword->value);
+                    memcpy(value, keyword->value, key_len);
+                    if (log_level >= 3) {
+                        FTS_TEST_DBG("section:%s,%s=%s",
+                                     section_name, key_name, value);
+                    }
+
+                    return key_len;
+                }
+            }
+        }
+    }
+
+    return -ENODATA;
+}
+
+/* return keyword's value length if success */
+static int ini_get_string_value(char *section_name, char *key_name, char *rval)
+{
+    if (!section_name || !key_name || !rval) {
+        FTS_TEST_ERROR("section_name/key_name/rval is null");
+        return -EINVAL;
+    }
+
+    return ini_get_key(section_name, key_name, rval);
+}
+
+int get_keyword_value(char *section, char *name, int *value)
+{
+    int ret = 0;
+    char str[MAX_KEYWORD_VALUE_LEN] = { 0 };
+
+    ret = ini_get_string_value(section, name, str);
+    if (ret > 0) {
+        /* search successfully, so change value, otherwise keep default */
+        *value = fts_atoi(str);
+    }
+
+    return ret;
+}
+
+static void fts_init_buffer(int *buffer, int value, int len, bool key_check, int key_value, int key_len)
+{
+    int i = 0;
+    int va_len = 0;
+
+    if (NULL == buffer) {
+        FTS_TEST_ERROR("buffer is null\n");
+        return;
+    }
+
+    va_len = len - key_len;
+    if (va_len < 0) {
+        FTS_TEST_ERROR("total len(0x%x) less key len(0x%x)\n", len, key_len);
+        return;
+    }
+
+    for (i = 0; i < len; i++) {
+        buffer[i] = value;
+    }
+
+    if (key_check) {
+        for (i = 0; i < key_len; i++) {
+            buffer[va_len + i] = key_value;
+        }
+    }
+
+}
+
+static int get_test_item(char name[][MAX_KEYWORD_NAME_LEN], int length, int *val)
+{
+    int i = 0;
+    int ret = 0;
+    int tmpval = 0;
+
+    if (length > TEST_ITEM_COUNT_MAX) {
+        FTS_TEST_SAVE_ERR("test item count(%d) > max(%d)\n",
+                          length, TEST_ITEM_COUNT_MAX);
+        return -EINVAL;
+    }
+
+    FTS_TEST_INFO("test items in total of driver:%d", length);
+    *val = 0;
+    for (i = 0; i < length; i++) {
+        tmpval = 0;
+        ret = get_value_testitem(name[i], &tmpval);
+        if (ret < 0) {
+            FTS_TEST_DBG("test item:%s not found", name[i]);
+        } else {
+            FTS_TEST_DBG("test item:%s=%d", name[i], tmpval);
+            *val |= (tmpval << i);
+        }
+    }
+
+    return 0;
+}
+
+static int get_basic_threshold(char name[][MAX_KEYWORD_NAME_LEN], int length, int *val)
+{
+    int i = 0;
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+    int log_level = tdata->ts_data->log_level;
+
+    FTS_TEST_INFO("basic_thr string length(%d), count(%d)\n", length, tdata->basic_thr_count);
+    if (length > fts_ftest->basic_thr_count) {
+        FTS_TEST_SAVE_ERR("basic_thr string length > count\n");
+        return -EINVAL;
+    }
+
+    for (i = 0; i < length; i++) {
+        ret = get_value_basic(name[i], &val[i]);
+        if (log_level >= 3) {
+            if (ret < 0) {
+                FTS_TEST_DBG("basic thr:%s not found", name[i]);
+            } else {
+                FTS_TEST_DBG("basic thr:%s=%d", name[i], val[i]);
+            }
+        }
+    }
+
+    return 0;
+}
+
+static void get_detail_threshold(char *key_name, bool is_prex, int *thr, int node_num)
+{
+    char str[MAX_KEYWORD_VALUE_LEN] = { 0 };
+    char str_temp[MAX_KEYWORD_NAME_LEN] = { 0 };
+    char str_tmp[MAX_KEYWORD_VALUE_ONE_LEN] = { 0 };
+    struct fts_test *tdata = fts_ftest;
+    int divider_pos = 0;
+    int index = 0;
+    int i = 0;
+    int j = 0;
+    int k = 0;
+    int tx_num = 0;
+    int rx_num = 0;
+    int thr_pos = 0;
+
+    if (!key_name || !thr) {
+        FTS_TEST_ERROR("key_name/thr is null");
+        return;
+    }
+
+    if (is_prex) {
+        tx_num = tdata->node.tx_num;
+        rx_num = tdata->node.rx_num;
+    }
+    for (i = 0; i < tx_num + 1; i++) {
+        if (is_prex) {
+            snprintf(str_temp, MAX_KEYWORD_NAME_LEN, "%s%d", key_name, (i + 1));
+        } else {
+            snprintf(str_temp, MAX_KEYWORD_NAME_LEN, "%s", key_name);
+        }
+        divider_pos = ini_get_string_value("SpecialSet", str_temp, str);
+        if (divider_pos <= 0)
+            continue;
+        index = 0;
+        k = 0;
+        memset(str_tmp, 0, sizeof(str_tmp));
+        for (j = 0; j < divider_pos; j++) {
+            if (',' == str[j]) {
+                thr_pos = i * rx_num + k;
+                if (thr_pos >= node_num) {
+                    FTS_TEST_ERROR("key:%s %d,dthr_num(%d>=%d) fail",
+                                   key_name, i, thr_pos, node_num);
+                    break;
+                }
+                thr[thr_pos] = (int)(fts_atoi(str_tmp));
+                index = 0;
+                memset(str_tmp, 0x00, sizeof(str_tmp));
+                k++;
+            } else {
+                if (' ' == str[j])
+                    continue;
+                str_tmp[index] = str[j];
+                index++;
+            }
+        }
+    }
+}
+
+static int init_node_valid(void)
+{
+    char str[MAX_KEYWORD_NAME_LEN] = {0};
+    int i = 0;
+    int j = 0;
+    int chy = 0;
+    int node_num = 0;
+    int cnt = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if (!tdata || !tdata->node_valid || !tdata->node_valid_sc) {
+        FTS_TEST_ERROR("tdata/node_valid/node_valid_sc is null");
+        return -EINVAL;
+    }
+
+    chy = tdata->node.rx_num;
+    node_num = tdata->node.node_num;
+    fts_init_buffer(tdata->node_valid, 1 , node_num, false, 0, 0);
+    if ((tdata->func->hwtype == IC_HW_INCELL) || (tdata->func->hwtype == IC_HW_MC_SC)) {
+        for (cnt = 0; cnt < node_num; cnt++) {
+            i = cnt / chy + 1;
+            j = cnt % chy + 1;
+            snprintf(str, MAX_KEYWORD_NAME_LEN, "InvalidNode[%d][%d]", i, j);
+            get_keyword_value("INVALID_NODE", str, &tdata->node_valid[cnt]);
+        }
+    }
+
+    if (tdata->func->hwtype == IC_HW_MC_SC) {
+        chy = tdata->sc_node.rx_num;
+        node_num = tdata->sc_node.node_num;
+        fts_init_buffer(tdata->node_valid_sc, 1, node_num, false, 0, 0);
+
+        for (cnt = 0; cnt < node_num; cnt++) {
+            i = (cnt >= chy) ? 2 : 1;
+            j = (cnt >= chy) ? (cnt - chy + 1) : (cnt + 1);
+            snprintf(str, MAX_KEYWORD_NAME_LEN, "InvalidNodeS[%d][%d]", i, j);
+            get_keyword_value("INVALID_NODES", str, &tdata->node_valid_sc[cnt]);
+        }
+    }
+
+    print_buffer(tdata->node_valid, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(tdata->node_valid_sc, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    return 0;
+}
+
+/* incell */
+static int get_test_item_incell(void)
+{
+    int ret = 0;
+    char item_name[][MAX_KEYWORD_NAME_LEN] = TEST_ITEM_INCELL;
+    int length = sizeof(item_name) / MAX_KEYWORD_NAME_LEN;
+    int item_val = 0;
+
+    ret = get_test_item(item_name, length, &item_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get test item fail\n");
+        return ret;
+    }
+
+    fts_ftest->ic.incell.u.tmp = item_val;
+    return 0;
+}
+
+static char bthr_name_incell[][MAX_KEYWORD_NAME_LEN] = BASIC_THRESHOLD_INCELL;
+static int get_test_threshold_incell(void)
+{
+    int ret = 0;
+    int length = sizeof(bthr_name_incell) / MAX_KEYWORD_NAME_LEN;
+    struct fts_test *tdata = fts_ftest;
+    struct incell_threshold *thr = &tdata->ic.incell.thr;
+    int node_num = tdata->node.node_num;
+    int key_num = tdata->node.key_num;
+    bool raw_key_check = thr->basic.rawdata_vkey_check;
+    bool cb_key_check = thr->basic.cb_vkey_check;
+
+    tdata->basic_thr_count = sizeof(struct incell_threshold_b) / sizeof(int);
+    /* get standard basic threshold */
+    ret = get_basic_threshold(bthr_name_incell, length, (int *)&thr->basic);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get basic thr fail\n");
+        return ret;
+    }
+
+    /* basic special set by ic */
+    if (tdata->func->param_init) {
+        ret = tdata->func->param_init();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("special basic thr init fail\n");
+            return ret;
+        }
+    }
+
+    /* init buffer */
+    fts_init_buffer(thr->rawdata_max, thr->basic.rawdata_max, node_num, raw_key_check, thr->basic.rawdata_max_vk, key_num);
+    fts_init_buffer(thr->rawdata_min, thr->basic.rawdata_min, node_num, raw_key_check, thr->basic.rawdata_min_vk, key_num);
+    if (tdata->func->rawdata2_support) {
+        fts_init_buffer(thr->rawdata2_max, thr->basic.rawdata2_max, node_num, false, 0, 0);
+        fts_init_buffer(thr->rawdata2_min, thr->basic.rawdata2_min, node_num, false, 0, 0);
+    }
+    fts_init_buffer(thr->cb_max, thr->basic.cb_max, node_num, cb_key_check, thr->basic.cb_max_vk, key_num);
+    fts_init_buffer(thr->cb_min, thr->basic.cb_min, node_num, cb_key_check, thr->basic.cb_min_vk, key_num);
+
+    /* detail threshold */
+    get_detail_threshold("RawData_Max_Tx", true, thr->rawdata_max, node_num);
+    get_detail_threshold("RawData_Min_Tx", true, thr->rawdata_min, node_num);
+    get_detail_threshold("CB_Max_Tx", true, thr->cb_max, node_num);
+    get_detail_threshold("CB_Min_Tx", true, thr->cb_min, node_num);
+
+    return 0;
+}
+
+static void print_thr_incell(void)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct incell_threshold *thr = &tdata->ic.incell.thr;
+
+    if (tdata->ts_data->log_level < 3) {
+        return;
+    }
+
+    FTS_TEST_DBG("short_res_min:%d", thr->basic.short_res_min);
+    FTS_TEST_DBG("short_res_vk_min:%d", thr->basic.short_res_vk_min);
+    FTS_TEST_DBG("open_cb_min:%d", thr->basic.open_cb_min);
+    FTS_TEST_DBG("open_k1_check:%d", thr->basic.open_k1_check);
+    FTS_TEST_DBG("open_k1_value:%d", thr->basic.open_k1_value);
+    FTS_TEST_DBG("open_k2_check:%d", thr->basic.open_k2_check);
+    FTS_TEST_DBG("open_k2_value:%d", thr->basic.open_k2_value);
+    FTS_TEST_DBG("cb_min:%d", thr->basic.cb_min);
+    FTS_TEST_DBG("cb_max:%d", thr->basic.cb_max);
+    FTS_TEST_DBG("cb_vkey_check:%d", thr->basic.cb_vkey_check);
+    FTS_TEST_DBG("cb_min_vk:%d", thr->basic.cb_min_vk);
+    FTS_TEST_DBG("cb_max_vk:%d", thr->basic.cb_max_vk);
+    FTS_TEST_DBG("rawdata_min:%d", thr->basic.rawdata_min);
+    FTS_TEST_DBG("rawdata_max:%d", thr->basic.rawdata_max);
+    FTS_TEST_DBG("rawdata_vkey_check:%d", thr->basic.rawdata_vkey_check);
+    FTS_TEST_DBG("rawdata_min_vk:%d", thr->basic.rawdata_min_vk);
+    FTS_TEST_DBG("rawdata_max_vk:%d", thr->basic.rawdata_max_vk);
+    FTS_TEST_DBG("lcdnoise_frame:%d", thr->basic.lcdnoise_frame);
+    FTS_TEST_DBG("lcdnoise_coefficient:%d", thr->basic.lcdnoise_coefficient);
+    FTS_TEST_DBG("lcdnoise_coefficient_vkey:%d", thr->basic.lcdnoise_coefficient_vkey);
+    FTS_TEST_DBG("open_diff_min:%d", thr->basic.open_diff_min);
+
+    FTS_TEST_DBG("open_nmos:%d", thr->basic.open_nmos);
+    FTS_TEST_DBG("keyshort_k1:%d", thr->basic.keyshort_k1);
+    FTS_TEST_DBG("keyshort_cb_max:%d", thr->basic.keyshort_cb_max);
+    FTS_TEST_DBG("rawdata2_min:%d", thr->basic.rawdata2_min);
+    FTS_TEST_DBG("rawdata2_max:%d", thr->basic.rawdata2_max);
+    FTS_TEST_DBG("mux_open_cb_min:%d", thr->basic.mux_open_cb_min);
+    FTS_TEST_DBG("open_delta_V:%d", thr->basic.open_delta_V);
+
+    print_buffer(thr->rawdata_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata2_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata2_max, tdata->node.node_num, tdata->node.rx_num);
+}
+
+static int ini_init_test_incell(void)
+{
+    int ret = 0;
+
+    ret = get_test_item_incell();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get incell test item fail\n");
+        return ret;
+    }
+
+
+    ret = get_test_threshold_incell();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get incell threshold fail\n");
+        return ret;
+    }
+
+    print_thr_incell();
+    return 0;
+}
+
+/* mc_sc */
+static int get_test_item_mc_sc(void)
+{
+    int ret = 0;
+    char item_name[][MAX_KEYWORD_NAME_LEN] = TEST_ITEM_MC_SC;
+    int length = sizeof(item_name) / MAX_KEYWORD_NAME_LEN;
+    int item_val = 0;
+
+    ret = get_test_item(item_name, length, &item_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get test item fail\n");
+        return ret;
+    }
+
+    fts_ftest->ic.mc_sc.u.tmp = item_val;
+    FTS_TEST_INFO("test item:0x%x in ini", fts_ftest->ic.mc_sc.u.tmp);
+    return 0;
+}
+
+static char bthr_name_mc_sc[][MAX_KEYWORD_NAME_LEN] = BASIC_THRESHOLD_MC_SC;
+static int get_test_threshold_mc_sc(void)
+{
+    int ret = 0;
+    int length = sizeof(bthr_name_mc_sc) / MAX_KEYWORD_NAME_LEN;
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+    int node_num = tdata->node.node_num;
+    int sc_num = tdata->sc_node.node_num;
+
+    tdata->basic_thr_count = sizeof(struct mc_sc_threshold_b) / sizeof(int);
+    /* get standard basic threshold */
+    ret = get_basic_threshold(bthr_name_mc_sc, length, (int *)&thr->basic);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get basic thr fail\n");
+        return ret;
+    }
+
+    /* basic special set by ic */
+    if (tdata->func->param_init) {
+        ret = tdata->func->param_init();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("special basic thr init fail\n");
+            return ret;
+        }
+    }
+
+    /* init buffer */
+    fts_init_buffer(thr->rawdata_h_min, thr->basic.rawdata_h_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->rawdata_h_max, thr->basic.rawdata_h_max, node_num, false, 0, 0);
+    if (tdata->func->rawdata2_support) {
+        fts_init_buffer(thr->rawdata_l_min, thr->basic.rawdata_l_min, node_num, false, 0, 0);
+        fts_init_buffer(thr->rawdata_l_max, thr->basic.rawdata_l_max, node_num, false, 0, 0);
+    }
+    fts_init_buffer(thr->tx_linearity_max, thr->basic.uniformity_tx_hole, node_num, false, 0, 0);
+    fts_init_buffer(thr->tx_linearity_min, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->rx_linearity_max, thr->basic.uniformity_rx_hole, node_num, false, 0, 0);
+    fts_init_buffer(thr->rx_linearity_min, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_off_min, thr->basic.scap_cb_off_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_off_max, thr->basic.scap_cb_off_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_on_min, thr->basic.scap_cb_on_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_on_max, thr->basic.scap_cb_on_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hi_min, thr->basic.scap_cb_hi_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hi_max, thr->basic.scap_cb_hi_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hov_min, thr->basic.scap_cb_hov_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_cb_hov_max, thr->basic.scap_cb_hov_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_off_min, thr->basic.scap_rawdata_off_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_off_max, thr->basic.scap_rawdata_off_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_on_min, thr->basic.scap_rawdata_on_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_on_max, thr->basic.scap_rawdata_on_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hi_min, thr->basic.scap_rawdata_hi_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hi_max, thr->basic.scap_rawdata_hi_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hov_min, thr->basic.scap_rawdata_hov_min, sc_num, false, 0, 0);
+    fts_init_buffer(thr->scap_rawdata_hov_max, thr->basic.scap_rawdata_hov_max, sc_num, false, 0, 0);
+    fts_init_buffer(thr->panel_differ_min, thr->basic.panel_differ_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->panel_differ_max, thr->basic.panel_differ_max, node_num, false, 0, 0);
+    fts_init_buffer(thr->noise_min, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->noise_max, thr->basic.noise_max, node_num, false, 0, 0);
+
+    fts_init_buffer(thr->low_freq_rawdata_min, 
+        thr->basic.low_freq_uniformity_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->low_freq_rawdata_max, 
+        thr->basic.low_freq_uniformity_max, node_num, false, 0, 0);
+    fts_init_buffer(thr->high_freq_rawdata_min, 
+        thr->basic.high_freq_uniformity_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->high_freq_rawdata_max, 
+        thr->basic.high_freq_uniformity_max, node_num, false, 0, 0);
+
+    fts_init_buffer(thr->high_freq_rawdata_rx_linearity_max, 
+        thr->basic.high_freq_uniformity_rx_hol, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->high_freq_rawdata_rx_linearity_min, 0, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->high_freq_rawdata_tx_linearity_max, 
+        thr->basic.high_freq_uniformity_tx_hol, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->high_freq_rawdata_tx_linearity_min, 0, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->low_freq_rawdata_rx_linearity_max, 
+        thr->basic.low_freq_uniformity_rx_hol, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->low_freq_rawdata_rx_linearity_min, 0, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->low_freq_rawdata_tx_linearity_max, 
+        thr->basic.low_freq_uniformity_tx_hol, node_num, false, 0, 0);
+    
+    fts_init_buffer(thr->low_freq_rawdata_tx_linearity_min, 0, node_num, false, 0, 0);
+
+    /* detail threshold */
+    get_detail_threshold("RawData_Min_High_Tx", true, thr->rawdata_h_min, node_num);
+    get_detail_threshold("RawData_Max_High_Tx", true, thr->rawdata_h_max, node_num);
+    if (tdata->func->rawdata2_support) {
+        get_detail_threshold("RawData_Min_Low_Tx", true, thr->rawdata_l_min, node_num);
+        get_detail_threshold("RawData_Max_Low_Tx", true, thr->rawdata_l_max, node_num);
+    }
+    get_detail_threshold("Tx_Linearity_Max_Tx", true, thr->tx_linearity_max, node_num);
+    get_detail_threshold("Rx_Linearity_Max_Tx", true, thr->rx_linearity_max, node_num);
+    get_detail_threshold("ScapCB_OFF_Min_", true, thr->scap_cb_off_min, sc_num);
+    get_detail_threshold("ScapCB_OFF_Max_", true, thr->scap_cb_off_max, sc_num);
+    get_detail_threshold("ScapCB_ON_Min_", true, thr->scap_cb_on_min, sc_num);
+    get_detail_threshold("ScapCB_ON_Max_", true, thr->scap_cb_on_max, sc_num);
+    get_detail_threshold("ScapCB_High_Min_", true, thr->scap_cb_hi_min, sc_num);
+    get_detail_threshold("ScapCB_High_Max_", true, thr->scap_cb_hi_max, sc_num);
+    get_detail_threshold("ScapCB_Hov_Min_", true, thr->scap_cb_hov_min, sc_num);
+    get_detail_threshold("ScapCB_Hov_Max_", true, thr->scap_cb_hov_max, sc_num);
+    get_detail_threshold("ScapRawData_OFF_Min_", true, thr->scap_rawdata_off_min, sc_num);
+    get_detail_threshold("ScapRawData_OFF_Max_", true, thr->scap_rawdata_off_max, sc_num);
+    get_detail_threshold("ScapRawData_ON_Min_", true, thr->scap_rawdata_on_min, sc_num);
+    get_detail_threshold("ScapRawData_ON_Max_", true, thr->scap_rawdata_on_max, sc_num);
+    get_detail_threshold("ScapRawData_High_Min_", true, thr->scap_rawdata_hi_min, sc_num);
+    get_detail_threshold("ScapRawData_High_Max_", true, thr->scap_rawdata_hi_max, sc_num);
+    get_detail_threshold("ScapRawData_Hov_Min_", true, thr->scap_rawdata_hov_min, sc_num);
+    get_detail_threshold("ScapRawData_Hov_Max_", true, thr->scap_rawdata_hov_max, sc_num);
+    get_detail_threshold("Panel_Differ_Min_Tx", true, thr->panel_differ_min, node_num);
+    get_detail_threshold("Panel_Differ_Max_Tx", true, thr->panel_differ_max, node_num);
+    get_detail_threshold("NoistTestCoefficient_Tx", true, thr->noise_max, node_num);
+
+    get_detail_threshold("High_Fre_RawData_Min_Tx", true, thr->low_freq_rawdata_min, node_num);
+    get_detail_threshold("High_Fre_RawData_Max_Tx", true, thr->low_freq_rawdata_max, node_num);
+    get_detail_threshold("Low_Fre_RawData_Min_Tx", true, thr->high_freq_rawdata_min, node_num);
+    get_detail_threshold("Low_Fre_RawData_Max_Tx", true, thr->high_freq_rawdata_max, node_num);
+
+    
+    get_detail_threshold("High_Fre_Rawdata_Rx_Linearity_Max_Tx", true, 
+        thr->high_freq_rawdata_rx_linearity_max, node_num);
+    get_detail_threshold("High_Fre_Rawdata_Tx_Linearity_Max_Tx", true, 
+        thr->high_freq_rawdata_tx_linearity_max, node_num);
+    
+    get_detail_threshold("Low_Fre_Rawdata_Rx_Linearity_Max_Tx", true, 
+        thr->low_freq_rawdata_rx_linearity_max, node_num);
+    get_detail_threshold("Low_Fre_Rawdata_Tx_Linearity_Max_Tx", true, 
+        thr->low_freq_rawdata_tx_linearity_max, node_num);
+
+    return 0;
+}
+
+static void print_thr_mc_sc(void)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    if (tdata->ts_data->log_level < 3) {
+        return;
+    }
+
+    FTS_TEST_DBG("rawdata_h_min:%d", thr->basic.rawdata_h_min);
+    FTS_TEST_DBG("rawdata_h_max:%d", thr->basic.rawdata_h_max);
+    FTS_TEST_DBG("rawdata_set_hfreq:%d", thr->basic.rawdata_set_hfreq);
+    FTS_TEST_DBG("rawdata_l_min:%d", thr->basic.rawdata_l_min);
+    FTS_TEST_DBG("rawdata_l_max:%d", thr->basic.rawdata_l_max);
+    FTS_TEST_DBG("rawdata_set_lfreq:%d", thr->basic.rawdata_set_lfreq);
+    FTS_TEST_DBG("uniformity_check_tx:%d", thr->basic.uniformity_check_tx);
+    FTS_TEST_DBG("uniformity_check_rx:%d", thr->basic.uniformity_check_rx);
+    FTS_TEST_DBG("uniformity_check_min_max:%d", thr->basic.uniformity_check_min_max);
+    FTS_TEST_DBG("uniformity_tx_hole:%d", thr->basic.uniformity_tx_hole);
+    FTS_TEST_DBG("uniformity_rx_hole:%d", thr->basic.uniformity_rx_hole);
+    FTS_TEST_DBG("uniformity_min_max_hole:%d", thr->basic.uniformity_min_max_hole);
+    FTS_TEST_DBG("scap_cb_off_min:%d", thr->basic.scap_cb_off_min);
+    FTS_TEST_DBG("scap_cb_off_max:%d", thr->basic.scap_cb_off_max);
+    FTS_TEST_DBG("scap_cb_wp_off_check:%d", thr->basic.scap_cb_wp_off_check);
+    FTS_TEST_DBG("scap_cb_on_min:%d", thr->basic.scap_cb_on_min);
+    FTS_TEST_DBG("scap_cb_on_max:%d", thr->basic.scap_cb_on_max);
+    FTS_TEST_DBG("scap_cb_wp_on_check:%d", thr->basic.scap_cb_wp_on_check);
+    FTS_TEST_DBG("scap_rawdata_off_min:%d", thr->basic.scap_rawdata_off_min);
+    FTS_TEST_DBG("scap_rawdata_off_max:%d", thr->basic.scap_rawdata_off_max);
+    FTS_TEST_DBG("scap_rawdata_wp_off_check:%d", thr->basic.scap_rawdata_wp_off_check);
+    FTS_TEST_DBG("scap_rawdata_on_min:%d", thr->basic.scap_rawdata_on_min);
+    FTS_TEST_DBG("scap_rawdata_on_max:%d", thr->basic.scap_rawdata_on_max);
+    FTS_TEST_DBG("scap_rawdata_wp_on_check:%d", thr->basic.scap_rawdata_wp_on_check);
+    FTS_TEST_DBG("short_cg:%d", thr->basic.short_cg);
+    FTS_TEST_DBG("short_cc:%d", thr->basic.short_cc);
+    FTS_TEST_DBG("panel_differ_min:%d", thr->basic.panel_differ_min);
+    FTS_TEST_DBG("panel_differ_max:%d", thr->basic.panel_differ_max);
+    FTS_TEST_DBG("noise_max:%d,frame_num:%d,noise_mode:%d,polling:%d", thr->basic.noise_max,
+                 thr->basic.noise_framenum, thr->basic.noise_mode, thr->basic.noise_polling);
+
+    print_buffer(thr->rawdata_h_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_h_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_l_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_l_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->tx_linearity_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rx_linearity_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->scap_cb_off_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_off_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_on_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_on_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hi_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hi_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hov_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_cb_hov_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_off_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_off_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_on_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_on_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hi_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hi_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hov_min, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->scap_rawdata_hov_max, tdata->sc_node.node_num, tdata->sc_node.rx_num);
+    print_buffer(thr->panel_differ_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->panel_differ_max, tdata->node.node_num, tdata->node.rx_num);
+}
+
+static int ini_init_test_mc_sc(void)
+{
+    int ret = 0;
+
+    ret = get_test_item_mc_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get mc_sc test item fail\n");
+        return ret;
+    }
+
+    ret = get_test_threshold_mc_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get mc_sc threshold fail\n");
+        return ret;
+    }
+
+    print_thr_mc_sc();
+    return 0;
+}
+
+/* sc */
+static int get_test_item_sc(void)
+{
+    int ret = 0;
+    char item_name[][MAX_KEYWORD_NAME_LEN] = TEST_ITEM_SC;
+    int length = sizeof(item_name) / MAX_KEYWORD_NAME_LEN;
+    int item_val = 0;
+
+    ret = get_test_item(item_name, length, &item_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get test item fail\n");
+        return ret;
+    }
+
+    fts_ftest->ic.sc.u.tmp = item_val;
+    return 0;
+}
+
+static char bthr_name_sc[][MAX_KEYWORD_NAME_LEN] = BASIC_THRESHOLD_SC;
+static int get_test_threshold_sc(void)
+{
+    int ret = 0;
+    int length = sizeof(bthr_name_sc) / MAX_KEYWORD_NAME_LEN;
+    struct fts_test *tdata = fts_ftest;
+    struct sc_threshold *thr = &tdata->ic.sc.thr;
+    int node_num = tdata->node.node_num;
+
+    tdata->basic_thr_count = sizeof(struct sc_threshold_b) / sizeof(int);
+    /* get standard basic threshold */
+    ret = get_basic_threshold(bthr_name_sc, length, (int *)&thr->basic);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get basic thr fail\n");
+        return ret;
+    }
+
+    /* basic special set by ic */
+    if (tdata->func->param_init) {
+        ret = tdata->func->param_init();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("special basic thr init fail\n");
+            return ret;
+        }
+    }
+
+    /* init buffer */
+    fts_init_buffer(thr->rawdata_min, thr->basic.rawdata_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->rawdata_max, thr->basic.rawdata_max, node_num, false, 0, 0);
+    fts_init_buffer(thr->cb_min, thr->basic.cb_min, node_num, false, 0, 0);
+    fts_init_buffer(thr->cb_max, thr->basic.cb_max, node_num, false, 0, 0);
+    fts_init_buffer(thr->dcb_sort, 0, node_num, false, 0, 0);
+    fts_init_buffer(thr->dcb_base, thr->basic.dcb_base, node_num, false, 0, 0);
+
+    /* detail threshold */
+    get_detail_threshold("RawDataTest_Min", false, thr->rawdata_min, node_num);
+    get_detail_threshold("RawDataTest_Max", false, thr->rawdata_max, node_num);
+    get_detail_threshold("CbTest_Min", false, thr->cb_min, node_num);
+    get_detail_threshold("CbTest_Max", false, thr->cb_max, node_num);
+    get_detail_threshold("DeltaCxTest_Sort", false, thr->dcb_sort, node_num);
+    get_detail_threshold("DeltaCbTest_Base", false, thr->dcb_base, node_num);
+
+    return 0;
+}
+
+static void print_thr_sc(void)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct sc_threshold *thr = &tdata->ic.sc.thr;
+
+    if (tdata->ts_data->log_level < 3) {
+        return;
+    }
+
+    FTS_TEST_DBG("rawdata_min:%d", thr->basic.rawdata_min);
+    FTS_TEST_DBG("rawdata_max:%d", thr->basic.rawdata_max);
+    FTS_TEST_DBG("cb_min:%d", thr->basic.cb_min);
+    FTS_TEST_DBG("cb_max:%d", thr->basic.cb_max);
+    FTS_TEST_DBG("dcb_differ_max:%d", thr->basic.dcb_differ_max);
+    FTS_TEST_DBG("dcb_key_check:%d", thr->basic.dcb_key_check);
+    FTS_TEST_DBG("dcb_key_differ_max:%d", thr->basic.dcb_key_differ_max);
+    FTS_TEST_DBG("dcb_ds1:%d", thr->basic.dcb_ds1);
+    FTS_TEST_DBG("dcb_ds2:%d", thr->basic.dcb_ds2);
+    FTS_TEST_DBG("dcb_ds3:%d", thr->basic.dcb_ds3);
+    FTS_TEST_DBG("dcb_ds4:%d", thr->basic.dcb_ds4);
+    FTS_TEST_DBG("dcb_ds5:%d", thr->basic.dcb_ds5);
+    FTS_TEST_DBG("dcb_ds6:%d", thr->basic.dcb_ds6);
+    FTS_TEST_DBG("dcb_critical_check:%d", thr->basic.dcb_critical_check);
+    FTS_TEST_DBG("dcb_cs1:%d", thr->basic.dcb_cs1);
+    FTS_TEST_DBG("dcb_cs2:%d", thr->basic.dcb_cs2);
+    FTS_TEST_DBG("dcb_cs3:%d", thr->basic.dcb_cs3);
+    FTS_TEST_DBG("dcb_cs4:%d", thr->basic.dcb_cs4);
+    FTS_TEST_DBG("dcb_cs5:%d", thr->basic.dcb_cs5);
+    FTS_TEST_DBG("dcb_cs6:%d", thr->basic.dcb_cs6);
+
+    print_buffer(thr->rawdata_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->rawdata_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_min, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->cb_max, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->dcb_sort, tdata->node.node_num, tdata->node.rx_num);
+    print_buffer(thr->dcb_base, tdata->node.node_num, tdata->node.rx_num);
+}
+
+static int ini_init_test_sc(void)
+{
+    int ret = 0;
+
+    ret = get_test_item_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get sc test item fail\n");
+        return ret;
+    }
+
+    ret = get_test_threshold_sc();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get sc threshold fail\n");
+        return ret;
+    }
+
+    print_thr_sc();
+    return 0;
+}
+
+static u32 ini_get_ic_code(char *ic_name)
+{
+    int i = 0;
+    int type_size = 0;
+    int ini_icname_len = 0;
+    int ic_types_len = 0;
+
+    ini_icname_len = strlen(ic_name);
+    type_size = sizeof(ic_types) / sizeof(ic_types[0]);
+    for (i = 0; i < type_size; i++) {
+        ic_types_len = strlen(ic_name);
+        if (ini_icname_len == ic_types_len) {
+            if (0 == strncmp(ic_name, ic_types[i].ic_name, ic_types_len))
+                return ic_types[i].ic_type;
+        }
+    }
+
+    FTS_TEST_ERROR("no IC type match");
+    return 0;
+}
+
+
+static void ini_init_interface(struct ini_data *ini)
+{
+    char str[MAX_KEYWORD_VALUE_LEN] = { 0 };
+    u32 value = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    /* IC type */
+    ini_get_string_value("Interface", "IC_Type", str);
+    snprintf(ini->ic_name, MAX_IC_NAME_LEN, "%s", str);
+
+    value = ini_get_ic_code(str);
+    ini->ic_code = value;
+    FTS_TEST_INFO("ic name:%s, ic code:%x", ini->ic_name, ini->ic_code);
+
+    if (IC_HW_MC_SC == tdata->func->hwtype) {
+        get_value_interface("Normalize_Type", &value);
+        tdata->normalize = (u8)value;
+        FTS_TEST_DBG("normalize:%d", tdata->normalize);
+    }
+}
+
+static int ini_init_test(struct ini_data *ini)
+{
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    /* interface init */
+    ini_init_interface(ini);
+
+    /* node valid */
+    ret = init_node_valid();
+    if (ret < 0) {
+        FTS_TEST_ERROR("init node valid fail");
+        return ret;
+    }
+
+    switch (tdata->func->hwtype) {
+    case IC_HW_INCELL:
+        ret = ini_init_test_incell();
+        break;
+    case IC_HW_MC_SC:
+        ret = ini_init_test_mc_sc();
+        break;
+    case IC_HW_SC:
+        ret = ini_init_test_sc();
+        break;
+    default:
+        FTS_TEST_SAVE_ERR("test ic type(%d) fail\n", tdata->func->hwtype);
+        ret = -EINVAL;
+        break;
+    }
+
+    return ret;
+}
+
+/*
+ * fts_test_get_testparam_from_ini - get test parameters from ini
+ *
+ * read, parse the configuration file, initialize the test variable
+ *
+ * return 0 if succuss, else errro code
+ */
+int fts_test_get_testparam_from_ini(char *config_name)
+{
+    int ret = 0;
+    struct ini_data *ini = &fts_ftest->ini;
+
+    ret = fts_test_get_ini_via_request_firmware(ini, config_name);
+    if (ret != 0) {
+        FTS_TEST_INFO("read ini fail,ret=%d", ret);
+        return ret;
+    }
+
+    ini->keyword_num_total = 0;
+    ini->section_num = 0;
+
+    ini->tmp = vmalloc(sizeof(struct ini_keyword) * MAX_KEYWORD_NUM);
+    if (ini->tmp == NULL) {
+        FTS_TEST_ERROR("malloc memory for ini tmp fail");
+        return -ENOMEM;
+    }
+    memset(ini->tmp, 0, sizeof(struct ini_keyword) * MAX_KEYWORD_NUM);
+
+    /* parse ini data to get keyword name&value */
+    ret = ini_init_inidata(ini);
+    if (ret < 0) {
+        FTS_TEST_ERROR("ini_init_inidata fail");
+        goto get_ini_err;
+    }
+
+    /* parse threshold & test item */
+    ret = ini_init_test(ini);
+    if (ret < 0) {
+        FTS_TEST_ERROR("ini init fail");
+        goto get_ini_err;
+    }
+
+get_ini_err:
+    if (ini->tmp) {
+        vfree(ini->tmp);
+        ini->tmp = NULL;
+    }
+
+    if (ini->data) {
+        vfree(ini->data);
+        ini->data = NULL;
+    }
+
+    return ret;
+}
diff --git a/ft3683u/focaltech_test/focaltech_test_ini.h b/ft3683u/focaltech_test/focaltech_test_ini.h
new file mode 100644
index 0000000..f995ad5
--- /dev/null
+++ b/ft3683u/focaltech_test/focaltech_test_ini.h
@@ -0,0 +1,153 @@
+/************************************************************************
+* Copyright (c) 2012-2020, Focaltech Systems (R)£¬All Rights Reserved.
+*
+* File Name: focaltech_test_ini.h
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2016-08-01
+*
+* Abstract: parsing function of INI file
+*
+************************************************************************/
+#ifndef _INI_H
+#define _INI_H
+/*****************************************************************************
+* Private constant and macro definitions using #define
+*****************************************************************************/
+#define MAX_KEYWORD_NUM                         (1000)
+#define MAX_KEYWORD_NAME_LEN                    (50)
+#define MAX_KEYWORD_VALUE_LEN                   (512)
+#define MAX_KEYWORD_VALUE_ONE_LEN               (16)
+#define MAX_INI_LINE_LEN        (MAX_KEYWORD_NAME_LEN + MAX_KEYWORD_VALUE_LEN)
+#define MAX_INI_SECTION_NUM                     (20)
+#define MAX_IC_NAME_LEN                         (32)
+#define MAX_TEST_ITEM                           (20)
+#define IC_CODE_OFFSET                          (16)
+
+/*****************************************************************************
+* enumerations, structures and unions
+*****************************************************************************/
+struct ini_ic_type {
+    char ic_name[MAX_IC_NAME_LEN];
+    u32 ic_type;
+};
+
+enum line_type {
+    LINE_SECTION = 1,
+    LINE_KEYWORD = 2 ,
+    LINE_OTHER = 3,
+};
+
+struct ini_keyword {
+    char name[MAX_KEYWORD_NAME_LEN];
+    char value[MAX_KEYWORD_VALUE_LEN];
+};
+
+struct ini_section {
+    char name[MAX_KEYWORD_NAME_LEN];
+    int keyword_num;
+    /* point to ini.tmp, don't need free */
+    struct ini_keyword *keyword;
+};
+
+struct ini_data {
+    char *data;
+    int length;
+    int keyword_num_total;
+    int section_num;
+    struct ini_section section[MAX_INI_SECTION_NUM];
+    struct ini_keyword *tmp;
+    char ic_name[MAX_IC_NAME_LEN];
+    u32 ic_code;
+};
+
+#define TEST_ITEM_INCELL            { \
+    "SHORT_CIRCUIT_TEST", \
+    "OPEN_TEST", \
+    "CB_TEST", \
+    "RAWDATA_TEST", \
+    "LCD_NOISE_TEST", \
+    "KEY_SHORT_TEST", \
+    "MUX_OPEN_TEST", \
+}
+
+#define BASIC_THRESHOLD_INCELL      { \
+    "ShortCircuit_ResMin", "ShortCircuit_VkResMin", \
+    "OpenTest_CBMin", "OpenTest_Check_K1", "OpenTest_K1Threshold", "OpenTest_Check_K2", "OpenTest_K2Threshold", \
+    "CBTest_Min", "CBTest_Max", \
+    "CBTest_VKey_Check", "CBTest_Min_Vkey", "CBTest_Max_Vkey", \
+    "RawDataTest_Min", "RawDataTest_Max", \
+    "RawDataTest_VKey_Check", "RawDataTest_Min_VKey", "RawDataTest_Max_VKey", \
+    "LCD_NoiseTest_Frame", "LCD_NoiseTest_Coefficient", "LCD_NoiseTest_Coefficient_key", \
+    "OpenTest_DifferMin", \
+}
+
+
+#define TEST_ITEM_MC_SC             { \
+    "RAWDATA_TEST", \
+    "UNIFORMITY_TEST", \
+    "SCAP_CB_TEST", \
+    "SCAP_RAWDATA_TEST", \
+    "WEAK_SHORT_CIRCUIT_TEST", \
+    "PANEL_DIFFER_TEST", \
+    "NOISE_TEST", \
+    "Mcap_CMB_TEST", \
+    "SCAP_NOISE_TEST", \
+    "LOW_FRE_RAWDATA_UNIFORMITY_TEST", \
+}
+
+
+#define BASIC_THRESHOLD_MC_SC       { \
+    "RawDataTest_High_Min", "RawDataTest_High_Max", "RawDataTest_HighFreq", \
+    "RawDataTest_Low_Min", "RawDataTest_Low_Max", "RawDataTest_LowFreq", \
+    "UniformityTest_Check_Tx", "UniformityTest_Check_Rx","UniformityTest_Check_MinMax", \
+    "UniformityTest_Tx_Hole", "UniformityTest_Rx_Hole", "UniformityTest_MinMax_Hole", \
+    "SCapCbTest_OFF_Min", "SCapCbTest_OFF_Max", "ScapCBTest_SetWaterproof_OFF", \
+    "SCapCbTest_ON_Min", "SCapCbTest_ON_Max", "ScapCBTest_SetWaterproof_ON", \
+    "SCapRawDataTest_OFF_Min", "SCapRawDataTest_OFF_Max", "SCapRawDataTest_SetWaterproof_OFF", \
+    "SCapRawDataTest_ON_Min", "SCapRawDataTest_ON_Max", "SCapRawDataTest_SetWaterproof_ON", \
+    "WeakShortTest_CG", "WeakShortTest_CC", \
+    "PanelDifferTest_Min", "PanelDifferTest_Max", \
+    "SCapCbTest_High_Min", "SCapCbTest_High_Max", "ScapCBTest_SetHighSensitivity", \
+    "SCapRawDataTest_High_Min", "SCapRawDataTest_High_Max", "SCapRawDataTest_SetHighSensitivity", \
+    "SCapCbTest_Hov_Min", "SCapCbTest_Hov_Max", "ScapCBTest_SetHov", \
+    "SCapRawDataTest_Hov_Min", "SCapRawDataTest_Hov_Max", "SCapRawDataTest_SetHov", \
+    "NoiseTest_Max", "NoiseTest_Frames", "NoiseTest_FwNoiseMode", "Polling_Frequency", \
+}
+
+
+#define TEST_ITEM_SC                { \
+    "RAWDATA_TEST", \
+    "CB_TEST", \
+    "DELTA_CB_TEST", \
+    "WEAK_SHORT_TEST", \
+}
+
+#define BASIC_THRESHOLD_SC          { \
+    "RawDataTest_Min", "RawDataTest_Max", \
+    "CbTest_Min", "CbTest_Max", \
+    "DeltaCbTest_Base", "DeltaCbTest_Differ_Max", \
+    "DeltaCbTest_Include_Key_Test", "DeltaCbTest_Key_Differ_Max", \
+    "DeltaCbTest_Deviation_S1", "DeltaCbTest_Deviation_S2", "DeltaCbTest_Deviation_S3", \
+    "DeltaCbTest_Deviation_S4", "DeltaCbTest_Deviation_S5", "DeltaCbTest_Deviation_S6", \
+    "DeltaCbTest_Set_Critical", "DeltaCbTest_Critical_S1", "DeltaCbTest_Critical_S2", \
+    "DeltaCbTest_Critical_S3", "DeltaCbTest_Critical_S4", \
+    "DeltaCbTest_Critical_S5", "DeltaCbTest_Critical_S6", \
+}
+
+/*****************************************************************************
+* Global variable or extern global variabls/functions
+*****************************************************************************/
+int fts_test_get_testparam_from_ini(char *config_name);
+int get_keyword_value(char *section, char *name, int *value);
+
+#define get_value_interface(name, value) \
+    get_keyword_value("Interface", name, value)
+#define get_value_basic(name, value) \
+    get_keyword_value("Basic_Threshold", name, value)
+#define get_value_detail(name, value) \
+    get_keyword_value("SpecialSet", name, value)
+#define get_value_testitem(name, value) \
+    get_keyword_value("TestItem", name, value)
+#endif /* _INI_H */
diff --git a/ft3683u/focaltech_test/supported_ic/Makefile b/ft3683u/focaltech_test/supported_ic/Makefile
new file mode 100644
index 0000000..a8e162d
--- /dev/null
+++ b/ft3683u/focaltech_test/supported_ic/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_test_ft3683u.o
\ No newline at end of file
diff --git a/ft3683u/focaltech_test/supported_ic/focaltech_test_ft3683u.c b/ft3683u/focaltech_test/supported_ic/focaltech_test_ft3683u.c
new file mode 100644
index 0000000..f4c4dac
--- /dev/null
+++ b/ft3683u/focaltech_test/supported_ic/focaltech_test_ft3683u.c
@@ -0,0 +1,4527 @@
+/************************************************************************
+* Copyright (c) 2012-2021, Focaltech Systems (R), All Rights Reserved.
+*
+* File Name: Focaltech_test_ft5672.c
+*
+* Author: Focaltech Driver Team
+*
+* Created: 2021-08-16
+*
+* Abstract:
+*
+************************************************************************/
+
+/*****************************************************************************
+* included header files
+*****************************************************************************/
+#include "../focaltech_test.h"
+
+/*****************************************************************************
+* private constant and macro definitions using #define
+*****************************************************************************/
+
+/*****************************************************************************
+* Static function
+*****************************************************************************/
+
+static int short_test_ch_to_all(
+    struct fts_test *tdata, int *res, u8 *ab_ch, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    int min_cg = tdata->ic.mc_sc.thr.basic.short_cg;
+    int ch_num = tdata->sc_node.tx_num + tdata->sc_node.rx_num;
+    int byte_num = ch_num * 2;
+    u8 ab_ch_num = 0;
+    int temp = 0;
+
+    FTS_TEST_DBG("short test:channel to all other\n");
+    /*get resistance data*/
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &res[0], \
+                                FACTROY_REG_SHORT2_CA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+
+    *result = true;
+
+    for (i = 0; i < ch_num; i++) {
+        temp = res[i];
+
+        if (temp > 1500) {
+            *result = false;
+            FTS_TEST_SAVE_ERR("ADC value abnormal %2d ", temp);
+            continue;
+        }
+
+        if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+            res[i] = 50000;
+            continue;
+        }
+        res[i] = fts_abs(((temp * 25 / 2047 + 4) * 2005) / (212 - ((temp * 250 / 2047) + 40)));
+        if (res[i] < min_cg) {
+            ab_ch_num++;
+            ab_ch[ab_ch_num] = i;
+            *result = false;
+        }
+    }
+
+    if (ab_ch_num) {
+        print_buffer(res, ch_num, ch_num);
+        ab_ch[0] = ab_ch_num;
+        printk("[FTS_TS]ab_ch:");
+        for (i = 0; i < ab_ch_num + 1; i++) {
+            printk("%2d ", ab_ch[i]);
+        }
+        printk("\n");
+    }
+
+    return 0;
+}
+
+static int short_test_ch_to_gnd(
+    struct fts_test *tdata, int *res, u8 *ab_ch, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    int min_cg = tdata->ic.mc_sc.thr.basic.short_cg;
+    int tx_num = tdata->sc_node.tx_num;
+    int byte_num = 0;
+    u8 ab_ch_num = 0;
+    bool is_cg_short = false;
+    int temp = 0;
+
+    FTS_TEST_DBG("short test:channel to gnd\n");
+    ab_ch_num = ab_ch[0];
+    ret = fts_test_write(FACTROY_REG_SHORT2_AB_CH, ab_ch, ab_ch_num + 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write abnormal channel fail\n");
+        return ret;
+    }
+
+    /*get resistance data*/
+    byte_num = ab_ch_num * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &res[0], \
+                                FACTROY_REG_SHORT2_CG);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+
+    *result = true;
+    for (i = 0; i < ab_ch_num; i++) {
+        temp = res[i];
+        if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+            res[i] = 50000;
+            continue;
+        }
+        res[i] = fts_abs(((temp * 25 / 2047 + 4) * 2005) / (212 - ((temp * 250 / 2047) + 40)));
+        if (res[i] < min_cg) {
+            *result = false;
+            if (!is_cg_short) {
+                FTS_TEST_SAVE_INFO("\nGND Short:\n");
+                is_cg_short = true;
+            }
+
+            if (ab_ch[i + 1] <= tx_num) {
+                FTS_TEST_SAVE_INFO("Tx%d with GND:", ab_ch[i + 1]);
+            } else {
+                FTS_TEST_SAVE_INFO( "Rx%d with GND:", (ab_ch[i + 1] - tx_num));
+            }
+            FTS_TEST_SAVE_INFO("%d(K)\n", res[i]);
+        }
+    }
+
+    return 0;
+}
+
+static int short_test_ch_to_ch(
+    struct fts_test *tdata, int *res, u8 *ab_ch, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    int j = 0;
+    int adc_cnt = 0;
+    int min_cc = tdata->ic.mc_sc.thr.basic.short_cc;
+    int tx_num = tdata->sc_node.tx_num;
+    int ch_num = tdata->sc_node.tx_num + tdata->sc_node.rx_num;
+    int byte_num = 0;
+    int tmp_num = 0;
+    u8 ab_ch_num = 0;
+    bool is_cc_short = false;
+    int temp = 0;
+
+    FTS_TEST_DBG("short test:channel to channel\n");
+    ab_ch_num = ab_ch[0];
+    if (ab_ch_num < 2) {
+        FTS_TEST_DBG("abnormal channel number<2, not run ch_ch test");
+        return ret;
+    }
+
+    ret = fts_test_write(FACTROY_REG_SHORT2_AB_CH, ab_ch, ab_ch_num + 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write abnormal channel fail\n");
+        return ret;
+    }
+
+    /*get resistance data*/
+    /*channel to channel: num * (num - 1) / 2, max. node_num*/
+    tmp_num = ab_ch_num * (ab_ch_num - 1) / 2;
+    tmp_num = (tmp_num > ch_num) ? ch_num : tmp_num;
+    byte_num = tmp_num * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &res[0], \
+                                FACTROY_REG_SHORT2_CC);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+
+    *result = true;
+    for (i = 0; i < ab_ch_num; i++) {
+        for (j = i + 1; j < ab_ch_num; j++) {
+            if (adc_cnt >= tmp_num)
+                break;
+
+            temp = res[adc_cnt];
+            if ((212 - ((temp * 250 / 2047) + 40)) == 0) {
+                res[adc_cnt] = 50000;
+                continue;
+            }
+            res[adc_cnt] = fts_abs(((temp * 25 / 2047 + 4) * 2005) / (212 - ((temp * 250 / 2047) + 40)));
+            if (res[adc_cnt] < min_cc) {
+                *result = false;
+                if (!is_cc_short) {
+                    FTS_TEST_SAVE_INFO("\nMutual Short:\n");
+                    is_cc_short = true;
+                }
+
+                if (ab_ch[i + 1] <= tx_num) {
+                    FTS_TEST_SAVE_INFO("Tx%d with", (ab_ch[i + 1]));
+                } else {
+                    FTS_TEST_SAVE_INFO("Rx%d with", (ab_ch[i + 1] - tx_num));
+                }
+
+                if (ab_ch[j + 1] <= tx_num) {
+                    FTS_TEST_SAVE_INFO(" Tx%d", (ab_ch[j + 1] ) );
+                } else {
+                    FTS_TEST_SAVE_INFO(" Rx%d", (ab_ch[j + 1] - tx_num));
+                }
+                FTS_TEST_SAVE_INFO(":%d(K)\n", res[adc_cnt]);
+            }
+            adc_cnt++;
+        }
+    }
+
+    return 0;
+}
+
+static int get_cb_ft5672(int *cb_buf, int byte_num, bool is_cf)
+{
+    int ret = 0;
+    int i = 0;
+    u8 cb[SC_NUM_MAX] = { 0 };
+
+    if (byte_num > SC_NUM_MAX) {
+        FTS_TEST_SAVE_ERR("CB byte(%d)>max(%d)", byte_num, SC_NUM_MAX);
+        return -EINVAL;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_CB_H_ADDR_OFF, 0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write cb_h addr offset fail\n");
+        return ret;
+    }
+
+
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_CB_ADDR_OFF, 0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write cb addr offset fail\n");
+        return ret;
+    }
+
+    ret = fts_test_read(FACTORY_REG_MC_SC_CB_ADDR, cb, byte_num);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read cb fail\n");
+        return ret;
+    }
+
+    for (i = 0; i < byte_num; i++) {
+        if (is_cf)
+            cb_buf[i] = (cb[i] & 0x80) ? -((cb[i] & 0x3F)) : (cb[i] & 0x3F);
+        else
+            cb_buf[i] = (cb[i] & 0x80) ? -((cb[i] & 0x7F)) : (cb[i] & 0x7F);
+    }
+
+    return 0;
+}
+
+static int scap_cb_ccbypass(struct fts_test *tdata, int *scap_cb, bool *result)
+{
+    int ret = 0;
+    int i = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    u8 scap_gcb_tx = 0;
+    u8 scap_gcb_rx = 0;
+    int byte_num = tdata->sc_node.node_num;
+    bool tmp_result = false;
+    bool tmp2_result = false;
+    bool tmp3_result = false;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scb_tmp = NULL;
+    int scb_cnt = 0;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    if (!thr->scap_cb_on_min || !thr->scap_cb_on_max
+        || !thr->scap_cb_off_min || !thr->scap_cb_off_max
+        || !thr->scap_cb_hi_min || !thr->scap_cb_hi_max) {
+        FTS_TEST_SAVE_ERR("scap_cb_on/off/hi_min/max is null\n");
+        ret = -EINVAL;
+        return ret;
+    }
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* water proof on check */
+    tmp_result = true;
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (thr->basic.scap_cb_wp_on_check && fw_wp_check) {
+        tdata->csv_item_cnt += 2;
+        tdata->csv_item_scb |= 0x01;
+        scb_tmp = scap_cb + scb_cnt;
+        /* 1:waterproof 0:non-waterproof */
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, WATER_PROOF_ON);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("set mc_sc mode fail\n");
+            return ret;
+        }
+
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            return ret;
+        }
+
+        ret = get_cb_ft5672(scb_tmp, byte_num, false);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            return ret;
+        }
+
+        ret = fts_test_read_reg(FACTROY_REG_SCAP_GCB_RX, &scap_gcb_rx);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read GCB_RX fail,ret=%d\n", ret);
+            return ret;
+        }
+
+        ret = fts_test_read_reg(FACTROY_REG_SCAP_GCB_TX, &scap_gcb_tx);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read GCB_TX fail,ret=%d\n", ret);
+            return ret;
+        }
+        scb_tmp[tdata->sc_node.node_num] = scap_gcb_rx;
+        scb_tmp[tdata->sc_node.node_num + tdata->sc_node.rx_num] = scap_gcb_tx;
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in waterproof on mode:\n");
+        show_data_mc_sc(scb_tmp);
+        FTS_TEST_SAVE_INFO("GCB RX:%d,TX:%d\n", scap_gcb_rx, scap_gcb_tx);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((scb_tmp[i] < thr->scap_cb_on_min[i]) ||
+                    (scb_tmp[i] > thr->scap_cb_on_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, scb_tmp[i],
+                                      thr->scap_cb_on_min[i],
+                                      thr->scap_cb_on_max[i]);
+                    tmp_result = false;
+                }
+            }
+        }
+
+        if (rx_check && ((scap_gcb_rx < thr->basic.scap_cb_on_gcb_min) ||
+                         (scap_gcb_rx > thr->basic.scap_cb_on_gcb_max))) {
+            FTS_TEST_SAVE_ERR("test fail,gcb_rx:%5d,range=(%5d,%5d)\n",
+                              scap_gcb_rx,
+                              thr->basic.scap_cb_on_gcb_min,
+                              thr->basic.scap_cb_on_gcb_max);
+            tmp_result = false;
+        }
+
+        if (tx_check && ((scap_gcb_tx < thr->basic.scap_cb_on_gcb_min) ||
+                         (scap_gcb_tx > thr->basic.scap_cb_on_gcb_max))) {
+            FTS_TEST_SAVE_ERR("test fail,gcb_tx:%5d,range=(%5d,%5d)\n",
+                              scap_gcb_tx,
+                              thr->basic.scap_cb_on_gcb_min,
+                              thr->basic.scap_cb_on_gcb_max);
+            tmp_result = false;
+        }
+
+        scb_cnt += tdata->sc_node.node_num * 2;
+    }
+
+    /* water proof off check */
+    tmp2_result = true;
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (thr->basic.scap_cb_wp_off_check && fw_wp_check) {
+        tdata->csv_item_cnt += 2;
+        tdata->csv_item_scb |= 0x02;
+        scb_tmp = scap_cb + scb_cnt;
+        /* 1:waterproof 0:non-waterproof */
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, WATER_PROOF_OFF);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("set mc_sc mode fail\n");
+            return ret;
+        }
+
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            return ret;
+        }
+
+        ret = get_cb_ft5672(scb_tmp, byte_num, false);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            return ret;
+        }
+
+        ret = fts_test_read_reg(FACTROY_REG_SCAP_GCB_RX, &scap_gcb_rx);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read GCB_RX fail,ret=%d\n", ret);
+            return ret;
+        }
+
+        ret = fts_test_read_reg(FACTROY_REG_SCAP_GCB_TX, &scap_gcb_tx);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read GCB_TX fail,ret=%d\n", ret);
+            return ret;
+        }
+        scb_tmp[tdata->sc_node.node_num] = scap_gcb_rx;
+        scb_tmp[tdata->sc_node.node_num + tdata->sc_node.rx_num] = scap_gcb_tx;
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in waterproof off mode:\n");
+        show_data_mc_sc(scb_tmp);
+        FTS_TEST_SAVE_INFO("GCB RX:%d,TX:%d\n", scap_gcb_rx, scap_gcb_tx);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((scb_tmp[i] < thr->scap_cb_off_min[i]) ||
+                    (scb_tmp[i] > thr->scap_cb_off_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, scb_tmp[i],
+                                      thr->scap_cb_off_min[i],
+                                      thr->scap_cb_off_max[i]);
+                    tmp2_result = false;
+                }
+            }
+        }
+
+        if (rx_check && ((scap_gcb_rx < thr->basic.scap_cb_off_gcb_min) ||
+                         (scap_gcb_rx > thr->basic.scap_cb_off_gcb_max))) {
+            FTS_TEST_SAVE_ERR("test fail,gcb_rx:%5d,range=(%5d,%5d)\n",
+                              scap_gcb_rx,
+                              thr->basic.scap_cb_off_gcb_min,
+                              thr->basic.scap_cb_off_gcb_max);
+            tmp2_result = false;
+        }
+
+        if (tx_check && ((scap_gcb_tx < thr->basic.scap_cb_off_gcb_min) ||
+                         (scap_gcb_tx > thr->basic.scap_cb_off_gcb_max))) {
+            FTS_TEST_SAVE_ERR("test fail,gcb_tx:%5d,range=(%5d,%5d)\n",
+                              scap_gcb_tx,
+                              thr->basic.scap_cb_off_gcb_min,
+                              thr->basic.scap_cb_off_gcb_max);
+            tmp2_result = false;
+        }
+
+        scb_cnt += tdata->sc_node.node_num * 2;
+    }
+
+    /*high mode*/
+    tmp3_result = true;
+    hov_high = (hc_sel & 0x03);
+    if (thr->basic.scap_cb_hov_check && hov_high) {
+        tdata->csv_item_cnt += 2;
+        tdata->csv_item_scb |= 0x04;
+        scb_tmp = scap_cb + scb_cnt;
+        /* 1:waterproof 0:non-waterproof */
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, HOV);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("set mc_sc mode fail\n");
+            return ret;
+        }
+
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            return ret;
+        }
+
+        ret = get_cb_ft5672(scb_tmp, byte_num, false);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            return ret;
+        }
+
+        ret = fts_test_read_reg(FACTROY_REG_SCAP_GCB_RX, &scap_gcb_rx);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read GCB_RX fail,ret=%d\n", ret);
+            return ret;
+        }
+
+        ret = fts_test_read_reg(FACTROY_REG_SCAP_GCB_TX, &scap_gcb_tx);
+        if (ret) {
+            FTS_TEST_SAVE_ERR("read GCB_TX fail,ret=%d\n", ret);
+            return ret;
+        }
+        scb_tmp[tdata->sc_node.node_num] = scap_gcb_rx;
+        scb_tmp[tdata->sc_node.node_num + tdata->sc_node.rx_num] = scap_gcb_tx;
+
+        /* show Scap CB */
+        FTS_TEST_SAVE_INFO("scap_cb in high mode:\n");
+        show_data_mc_sc(scb_tmp);
+        FTS_TEST_SAVE_INFO("GCB RX:%d,TX:%d\n", scap_gcb_rx, scap_gcb_tx);
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((scb_tmp[i] < thr->scap_cb_hov_min[i]) ||
+                    (scb_tmp[i] > thr->scap_cb_hov_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, scb_tmp[i],
+                                      thr->scap_cb_hov_min[i],
+                                      thr->scap_cb_hov_max[i]);
+                    tmp3_result = false;
+                }
+            }
+        }
+
+        if (rx_check && ((scap_gcb_rx < thr->basic.scap_cb_hi_gcb_min) ||
+                         (scap_gcb_rx > thr->basic.scap_cb_hi_gcb_max))) {
+            FTS_TEST_SAVE_ERR("test fail,gcb_rx:%5d,range=(%5d,%5d)\n",
+                              scap_gcb_rx,
+                              thr->basic.scap_cb_hi_gcb_min,
+                              thr->basic.scap_cb_hi_gcb_max);
+            tmp3_result = false;
+        }
+
+        if (tx_check && ((scap_gcb_tx < thr->basic.scap_cb_hi_gcb_min) ||
+                         (scap_gcb_tx > thr->basic.scap_cb_hi_gcb_max))) {
+            FTS_TEST_SAVE_ERR("test fail,gcb_tx:%5d,range=(%5d,%5d)\n",
+                              scap_gcb_tx,
+                              thr->basic.scap_cb_hi_gcb_min,
+                              thr->basic.scap_cb_hi_gcb_max);
+            tmp3_result = false;
+        }
+
+        scb_cnt += tdata->sc_node.node_num * 2;
+    }
+
+    *result = tmp_result && tmp2_result && tmp3_result;
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+/*
+ * start_scan - start to scan a frame
+ */
+static int start_scan_ft5672(int frame_num)
+{
+    int ret = 0;
+    u8 addr = 0;
+    u8 val = 0;
+    u8 finish_val = 0;
+    int times = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    addr = DIVIDE_MODE_ADDR;
+    val = 0xC0;
+    finish_val = 0x40;
+
+    /* write register to start scan */
+    ret = fts_test_write_reg(addr, val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write start scan mode fail\n");
+        return ret;
+    }
+
+    sys_delay(frame_num * FACTORY_TEST_DELAY / 2);
+    /* Wait for the scan to complete */
+    while (times++ < 100) {
+        sys_delay(FACTORY_TEST_DELAY);
+
+        ret = fts_test_read_reg(addr, &val);
+        if ((ret >= 0) && (val == finish_val)) {
+            break;
+        } else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", addr, val, times);
+    }
+
+    if (times >= 100) {
+        FTS_TEST_SAVE_ERR("scan timeout\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int get_noise_ft5672(struct fts_test *tdata, int *noise_data, u8 fre)
+{
+    int ret = 0;
+    int byte_num = 0;
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequecy fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+        return ret;
+    }
+
+
+    ret = fts_test_write_reg(0x1B, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set scan mode fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = start_scan_ft5672(tdata->ic.mc_sc.thr.basic.noise_framenum);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("start_scan fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_write_reg(0x01, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x01 fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    byte_num = tdata->node.node_num * 2;
+    ret = read_mass_data(0xCE, byte_num, noise_data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read noise fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+static int get_null_noise(struct fts_test *tdata)
+{
+    int ret = 0;
+    int *null_noise;
+    int null_byte_num = 0;
+
+    tdata->csv_item_cnt++;
+    null_byte_num = tdata->node.rx_num * tdata->fre_num + 1;
+    FTS_TEST_INFO("null noise num:%d", null_byte_num);
+    null_noise = kzalloc(null_byte_num * sizeof(int), GFP_KERNEL);
+    if (!null_noise) {
+        FTS_TEST_ERROR("null_noise malloc fail");
+        return -ENOMEM;
+    }
+
+    ret = fts_test_write_reg(0x01, 0xB0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x01 fail,ret=%d\n", ret);
+        goto err_null_noise;
+    }
+
+    ret = read_mass_data(0xCE, null_byte_num * 2, null_noise);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read null noise fail\n");
+        goto err_null_noise;
+    }
+
+    tdata->null_noise_max = null_noise[0];
+    FTS_TEST_SAVE_INFO("null noise:%d\n", null_noise[0]);
+    print_buffer(&null_noise[1], null_byte_num - 1, tdata->node.rx_num);
+    ret = 0;
+
+err_null_noise:
+    kfree(null_noise);
+    null_noise = NULL;
+    return ret;
+}
+
+static int ft5672_rawdata_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    int j = 0;
+    int *temp_rawdata = NULL;
+    int *rawdata = NULL;
+    u8 fre = 0;
+    u8 data_type = 0;
+    bool result = false;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: rawdata test\n");
+    rawdata = tdata->item1_data;
+    tdata->csv_item_cnt++;
+
+    if (!rawdata || !thr->rawdata_h_min || !thr->rawdata_h_max) {
+        FTS_TEST_SAVE_ERR("rawdata_h_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+    temp_rawdata = fts_malloc(tdata->node.node_num * sizeof(int));
+    if (!temp_rawdata) {
+        FTS_TEST_SAVE_ERR("memory temp_rawdata malloc fails");
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* rawdata test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x06 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set frequency main */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST,
+              FACTORY_REG_FRE_LIST_VALUE_MAIN);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequecy fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+        goto restore_reg;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    for (i = 0; i < 3; i++) {
+        /* lost 3 frames, in order to obtain stable data */
+        ret = start_scan();
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("scan fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /*********************GET RAWDATA*********************/
+    for (i = 0; i < 3; i++) {
+        /* lost 3 frames, in order to obtain stable data */
+        ret = get_rawdata(temp_rawdata);
+        for (j = 0; j < tdata->node.node_num; j++) {
+            rawdata[j] += temp_rawdata[j];
+        }
+    }
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    for (j = 0; j < tdata->node.node_num; j++) {
+        rawdata[j] = (rawdata[j] / 3);
+    }
+
+    /* show test data */
+    show_data(rawdata, false);
+
+    /* compare */
+    result = compare_array(rawdata,
+                           thr->rawdata_h_min,
+                           thr->rawdata_h_max,
+                           false);
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore reg0A fail,ret=%d\n", ret);
+    }
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+test_err:
+    fts_free(temp_rawdata);
+    if (result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("------ rawdata test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_INFO("------ rawdata test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5672_uniformity_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int row = 0;
+    int col = 1;
+    int i = 0;
+    int deviation = 0;
+    int max = 0;
+    int min = 0;
+    int uniform = 0;
+    int *rawdata = NULL;
+    int *rawdata_linearity = NULL;
+    int *rl_tmp = NULL;
+    int rl_cnt = 0;
+    int offset = 0;
+    int offset2 = 0;
+    int tx_num = 0;
+    int rx_num = 0;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    bool result = false;
+    bool result2 = false;
+    bool result3 = false;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: rawdata unfiormity test\n");
+    memset(tdata->buffer, 0, tdata->buffer_length);
+    rawdata = tdata->item1_data;
+    tx_num = tdata->node.tx_num;
+    rx_num = tdata->node.rx_num;
+
+    rawdata_linearity = tdata->item2_data;
+    if (!rawdata_linearity) {
+        FTS_TEST_SAVE_ERR("rawdata_linearity buffer fail");
+        ret = -ENOMEM;
+        goto test_err;
+    }
+
+    if (!thr->tx_linearity_max || !thr->rx_linearity_max
+        || !tdata->node_valid) {
+        FTS_TEST_SAVE_ERR("tx/rx_lmax/node_valid is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    print_buffer(rawdata, tdata->node.node_num, tdata->node.rx_num);
+
+    result = true;
+    if (thr->basic.uniformity_check_tx) {
+        FTS_TEST_SAVE_INFO("Check Tx Linearity\n");
+        tdata->csv_item_cnt++;
+        rl_tmp = rawdata_linearity + rl_cnt;
+        for (row = 0; row < tx_num; row++) {
+            for (col = 1; col <  rx_num; col++) {
+                offset = row * rx_num + col;
+                offset2 = row * rx_num + col - 1;
+                deviation = abs( rawdata[offset] - rawdata[offset2]);
+                max = max(rawdata[offset], rawdata[offset2]);
+                max = max ? max : 1;
+                rl_tmp[offset] = 100 * deviation / max;
+            }
+        }
+        /*show data in result.txt*/
+        FTS_TEST_SAVE_INFO(" Tx Linearity:\n");
+        show_data(rl_tmp, false);
+        FTS_TEST_SAVE_INFO("\n" );
+
+        /* compare */
+        result = compare_array(rl_tmp,
+                               thr->tx_linearity_min,
+                               thr->tx_linearity_max,
+                               false);
+
+        rl_cnt += tdata->node.node_num;
+    }
+
+    result2 = true;
+    if (thr->basic.uniformity_check_rx) {
+        FTS_TEST_SAVE_INFO("Check Rx Linearity\n");
+        tdata->csv_item_cnt++;
+        rl_tmp = rawdata_linearity + rl_cnt;
+        for (row = 1; row < tx_num; row++) {
+            for (col = 0; col < rx_num; col++) {
+                offset = row * rx_num + col;
+                offset2 = (row - 1) * rx_num + col;
+                deviation = abs(rawdata[offset] - rawdata[offset2]);
+                max = max(rawdata[offset], rawdata[offset2]);
+                max = max ? max : 1;
+                rl_tmp[offset] = 100 * deviation / max;
+            }
+        }
+
+        FTS_TEST_SAVE_INFO("Rx Linearity:\n");
+        show_data(rl_tmp, false);
+        FTS_TEST_SAVE_INFO("\n");
+
+        /* compare */
+        result2 = compare_array(rl_tmp,
+                                thr->rx_linearity_min,
+                                thr->rx_linearity_max,
+                                false);
+        rl_cnt += tdata->node.node_num;
+    }
+
+    result3 = true;
+    if (thr->basic.uniformity_check_min_max) {
+        FTS_TEST_SAVE_INFO("Check Min/Max\n") ;
+        min = 100000;
+        max = -100000;
+        for (i = 0; i < tdata->node.node_num; i++) {
+            if (0 == tdata->node_valid[i])
+                continue;
+            min = min(min, rawdata[i]);
+            max = max(max, rawdata[i]);
+        }
+        max = !max ? 1 : max;
+        uniform = 100 * abs(min) / abs(max);
+
+        FTS_TEST_SAVE_INFO("min:%d, max:%d, get value of min/max:%d\n",
+                           min, max, uniform);
+        if (uniform < thr->basic.uniformity_min_max_hole) {
+            result3 = false;
+            FTS_TEST_SAVE_ERR("min_max out of range, set value: %d\n",
+                              thr->basic.uniformity_min_max_hole);
+        }
+    }
+
+test_err:
+    if (result && result2 && result3) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("uniformity test is OK\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("uniformity test is NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+
+
+static int ft5672_low_high_freq_rawdata_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    int j = 0;
+    int k = 0;
+    int *temp_rawdata = NULL;
+    int *rawdata = NULL;
+    int *off_rawdata = NULL;
+    //u8 fre = 0;
+    //u8 data_type = 0;
+    u8 scan_freq[2] = {0};
+    u8 va_mu = 0;
+    u8 shift = 0;
+
+    u8 scan_freq_temp[2] = {0};
+    u8 vamu_temp = 0;
+    u8 shift_temp = 0;
+
+    int row = 0;
+    int col = 1;
+    int deviation = 0;
+    int max = 0;
+    //int min = 0;
+    //int uniform = 0;
+    int *rawdata_linearity = NULL;
+    int *rl_tmp = NULL;
+    int rl_cnt = 0;
+    int offset = 0;
+    int offset2 = 0;
+    int tx_num = 0;
+    int rx_num = 0;
+    bool result2 = false;
+    bool result3 = false;
+
+    int *rawdata_max = NULL;
+    int *rawdata_min = NULL;
+    bool tx_check = 0;
+    bool rx_check = 0;
+
+    int *tx_max = NULL;
+    int *tx_min = NULL;
+    int *rx_max = NULL;
+    int *rx_min = NULL;
+
+    bool result = false;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: low high freq rawdata test\n");
+    rawdata_linearity = tdata->item9_data;
+    tdata->csv_item_cnt++;
+
+    tx_num = tdata->node.tx_num;
+    rx_num = tdata->node.rx_num;
+
+    if (!rawdata_linearity) {
+        FTS_TEST_SAVE_ERR("rawdata_linearity is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+    
+    temp_rawdata = fts_malloc(tdata->node.node_num * sizeof(int));
+    if (!temp_rawdata) {
+        FTS_TEST_SAVE_ERR("memory temp_rawdata malloc fails");
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* rawdata test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read(0x5E, scan_freq, 2);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5E fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(0x22, &va_mu);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x22 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(0x23, &shift);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x23 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    for (k = 0; k < 2; k++) {
+
+        switch (k)
+            {
+            case 0:
+            FTS_TEST_INFO("switch to low freq teste rawdata");
+                if (thr->basic.low_freq_uniformity_check_en) {
+                    rl_cnt = 0;
+                    rawdata_max = thr->low_freq_rawdata_max;
+                    rawdata_min = thr->low_freq_rawdata_min;
+
+                    tx_max = thr->low_freq_rawdata_tx_linearity_max;
+                    rx_max = thr->low_freq_rawdata_rx_linearity_max;
+
+                    tx_min = thr->low_freq_rawdata_tx_linearity_min;
+                    rx_min = thr->low_freq_rawdata_rx_linearity_min;
+                
+                    off_rawdata = rawdata_linearity;
+                    rawdata = off_rawdata;
+                    
+                    tx_check = thr->basic.low_freq_uniformity_check_tx;
+                    rx_check = thr->basic.low_freq_uniformity_check_rx;
+
+                    scan_freq_temp[0] = BYTE_OFF_8(thr->basic.low_scan_freq);
+                    scan_freq_temp[1] = BYTE_OFF_0(thr->basic.low_scan_freq);
+                    vamu_temp = thr->basic.low_va_vul;
+                    shift_temp = thr->basic.low_shift;
+                } else {
+                    continue;
+                }
+                break;
+            case 1:
+                if (thr->basic.high_freq_uniformity_check_en) {
+                    FTS_TEST_INFO("switch to high freq teste rawdata");
+                    rl_cnt = 0;
+                    rawdata_max = thr->high_freq_rawdata_max;
+                    rawdata_min = thr->high_freq_rawdata_min;
+
+                    tx_max = thr->high_freq_rawdata_tx_linearity_max;
+                    rx_max = thr->high_freq_rawdata_rx_linearity_max;
+
+                    tx_min = thr->high_freq_rawdata_tx_linearity_min;
+                    rx_min = thr->high_freq_rawdata_rx_linearity_min;
+
+                    off_rawdata = rawdata_linearity + tdata->node.node_num * 3;
+                    rawdata = off_rawdata;
+                    tx_check = thr->basic.high_freq_uniformity_check_tx;
+                    rx_check = thr->basic.high_freq_uniformity_check_rx;
+
+                    scan_freq_temp[0] = BYTE_OFF_8(thr->basic.high_scan_freq);
+                    scan_freq_temp[1] = BYTE_OFF_0(thr->basic.high_scan_freq);
+                    vamu_temp = thr->basic.high_va_vul;
+                    shift_temp = thr->basic.high_shift;
+                } else {
+                    continue;
+                }
+                break;
+            }
+
+            fts_test_write(0x5E, scan_freq_temp, 2);
+            msleep(18);
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto restore_reg;
+            }
+            fts_test_write_reg(0x23, shift_temp);
+            msleep(18);
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto restore_reg;
+            }
+            fts_test_write_reg(0x22, vamu_temp);
+            msleep(18);
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto restore_reg;
+            }
+
+        for (i = 0; i < 1; i++) {
+            /* lost 3 frames, in order to obtain stable data */
+            ret = start_scan();
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("scan fail\n");
+                goto restore_reg;
+            }
+        }
+
+        /*********************GET RAWDATA*********************/
+        for (i = 0; i < 5; i++) {
+            /* lost 3 frames, in order to obtain stable data */
+            ret = get_rawdata(temp_rawdata);
+            for (j = 0; j < tdata->node.node_num; j++) {
+                rawdata[j] += temp_rawdata[j];
+            }
+        }
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+            goto restore_reg;
+        }
+
+        for (j = 0; j < tdata->node.node_num; j++) {
+            rawdata[j] = (rawdata[j] / 5);
+        }
+
+        show_data(rawdata, false);
+
+        /* compare */
+        result = compare_array(rawdata,
+                               rawdata_min,
+                               rawdata_max,
+                               false);
+
+        rl_cnt += tdata->node.node_num;
+        result = true;
+        if (tx_check) {
+            FTS_TEST_SAVE_INFO("Check Tx Linearity\n");
+            tdata->csv_item_cnt++;
+            rl_tmp = off_rawdata + rl_cnt;
+            for (row = 0; row < tx_num; row++) {
+                for (col = 1; col <  rx_num; col++) {
+                    offset = row * rx_num + col;
+                    offset2 = row * rx_num + col - 1;
+                    deviation = abs( rawdata[offset] - rawdata[offset2]);
+                    max = max(rawdata[offset], rawdata[offset2]);
+                    max = max ? max : 1;
+                    rl_tmp[offset] = 100 * deviation / max;
+                }
+            }
+            /*show data in result.txt*/
+            FTS_TEST_SAVE_INFO(" Tx Linearity:\n");
+            show_data(rl_tmp, false);
+            FTS_TEST_SAVE_INFO("\n" );
+
+            /* compare */
+            result2 = compare_array(rl_tmp,
+                                   tx_min,
+                                   tx_max,
+                                   false);
+
+            rl_cnt += tdata->node.node_num;
+        }
+
+        result2 = true;
+        if (rx_check) {
+            FTS_TEST_SAVE_INFO("Check Rx Linearity\n");
+            tdata->csv_item_cnt++;
+            rl_tmp = off_rawdata + rl_cnt;
+            for (row = 1; row < tx_num; row++) {
+                for (col = 0; col < rx_num; col++) {
+                    offset = row * rx_num + col;
+                    offset2 = (row - 1) * rx_num + col;
+                    deviation = abs(rawdata[offset] - rawdata[offset2]);
+                    max = max(rawdata[offset], rawdata[offset2]);
+                    max = max ? max : 1;
+                    rl_tmp[offset] = 100 * deviation / max;
+                }
+            }
+
+            FTS_TEST_SAVE_INFO("Rx Linearity:\n");
+            show_data(rl_tmp, false);
+            FTS_TEST_SAVE_INFO("\n");
+
+            /* compare */
+            result3 = compare_array(rl_tmp,
+                                    rx_min,
+                                    rx_max,
+                                    false);
+            rl_cnt += tdata->node.node_num;
+        }
+    }
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write(0x5E, scan_freq, 2);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x5E fail,ret=%d\n", ret);
+    }
+
+    msleep(18);
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(0x22, va_mu);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x22 fail,ret=%d\n", ret);
+    }
+
+    msleep(18);
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(0x23, shift);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x23 fail,ret=%d\n", ret);
+    }
+
+    msleep(18);
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    
+    ret = fts_test_write_reg(0x5B, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x5B fail,ret=%d\n", ret);
+    }
+
+    fts_reset_proc(200);
+
+test_err:
+    fts_free(temp_rawdata);
+    if (result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("------ low and high freq rawdata test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_INFO("------ low and high freq rawdata test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+
+static int get_scap_noise_data(enum wp_type wp, int *data)
+{
+    
+    int ret = 0;
+    u8 val = 0;
+    u8 addr = 0;
+    u8 noise_addr = 0;
+    int byte_num = 0;
+    struct fts_test *tdata = fts_ftest;
+
+    if ((NULL == tdata) || (NULL == tdata->func)) {
+        FTS_TEST_SAVE_ERR("test/func is null\n");
+        return -EINVAL;
+    }
+
+    byte_num = tdata->sc_node.node_num * 2;
+    addr = FACTORY_REG_LINE_ADDR;
+    noise_addr = 0xCE;
+    if (WATER_PROOF_ON == wp) {
+        val = 0xAC;
+    } else if (WATER_PROOF_OFF == wp) {
+        val = 0xAB;
+    } else if (HIGH_SENSITIVITY == wp) {
+        val = 0xA0;
+    } else if (HOV == wp) {
+        val = 0xA1;
+        byte_num = 4 * 2;
+    }
+
+    ret = fts_test_write_reg(addr, val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wirte line/start addr fail\n");
+        return ret;
+    }
+
+    ret = read_mass_data(noise_addr, byte_num, data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read rawdata fail\n");
+        return ret;
+    }
+
+    return 0;
+}
+
+
+static int ft5672_scap_noise_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    bool tmp_result = false;
+    bool tmp2_result = false;
+    bool tmp3_result = false;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    bool cc_en = false;
+    int *scap_noise = NULL;
+    int *snoise_tmp = NULL;
+    int snoise_cnt = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    //u8 data_type = 0;
+    //u8 scap_cfg = 0;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Scap noise Test\n");
+    scap_noise = tdata->item7_data;
+
+    if (!scap_noise) {
+        FTS_TEST_SAVE_ERR("scap noise fails");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+    
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* water proof on check */
+    tmp_result = true;
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (thr->basic.scap_noise_wp_on_check && fw_wp_check) {
+        tdata->csv_item_cnt++;
+        tdata->csv_item_snoise |= 0x01;
+        snoise_tmp = scap_noise + snoise_cnt;
+        ret = get_scap_noise_data(WATER_PROOF_ON, snoise_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_ON) noise fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_noise in waterproof on mode:\n");
+        show_data_mc_sc(snoise_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (cc_en && ((i == 0) || (i == tdata->sc_node.rx_num)))
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((snoise_tmp[i] < thr->basic.scap_noise_on_min) ||
+                    (snoise_tmp[i] > thr->basic.scap_noise_on_max)) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, snoise_tmp[i],
+                                      thr->basic.scap_noise_on_min,
+                                      thr->basic.scap_noise_on_max);
+                    tmp_result = false;
+                }
+            }
+        }
+
+        snoise_cnt += tdata->sc_node.node_num;
+    }
+
+    /* water proof off check */
+    tmp2_result = true;
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (thr->basic.scap_noise_wp_off_check && fw_wp_check) {
+        tdata->csv_item_cnt++;
+        tdata->csv_item_snoise |= 0x02;
+        snoise_tmp = scap_noise + snoise_cnt;
+        ret = get_scap_noise_data(WATER_PROOF_OFF, snoise_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_OFF) noise fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_noise in waterproof off mode:\n");
+        show_data_mc_sc(snoise_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (cc_en && ((i == 0) || (i == tdata->sc_node.rx_num)))
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((snoise_tmp[i] < thr->basic.scap_noise_off_min) ||
+                    (snoise_tmp[i] > thr->basic.scap_noise_off_max)) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, snoise_tmp[i],
+                                      thr->basic.scap_noise_off_min,
+                                      thr->basic.scap_noise_off_max);
+                    tmp2_result = false;
+                }
+            }
+        }
+        snoise_cnt += tdata->sc_node.node_num;
+    }
+
+    /*high mode*/
+    tmp3_result = true;
+    hov_high = (hc_sel & 0x03);
+    if (thr->basic.scap_noise_hov_check && hov_high) {
+        tdata->csv_item_cnt++;
+        tdata->csv_item_snoise |= 0x04;
+        snoise_tmp = scap_noise + snoise_cnt;
+        ret = get_scap_noise_data(HOV, snoise_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(HOV) noise fail\n");
+            goto restore_reg;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_noise in hs mode:\n");
+        show_data_mc_sc(snoise_tmp);
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if (cc_en && ((i == 0) || (i == tdata->sc_node.rx_num)))
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((snoise_tmp[i] < thr->basic.scap_noise_hov_min) ||
+                    (snoise_tmp[i] > thr->basic.scap_noise_hov_max)) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, snoise_tmp[i],
+                                      thr->basic.scap_noise_hov_min,
+                                      thr->basic.scap_noise_hov_max);
+                    tmp3_result = false;
+                }
+            }
+        }
+
+        snoise_cnt += tdata->sc_node.node_num;
+    }
+
+restore_reg:
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+test_err:
+    if (tmp_result && tmp2_result && tmp3_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("\n------ scap noise test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_INFO("\n------ scap noise test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+
+static int ft5672_scap_cb_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    u8 sc_mode = 0;
+    bool tmp_result = false;
+    int *scap_cb = NULL;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Scap CB Test\n");
+    scap_cb = tdata->item3_data;
+
+    if (!scap_cb) {
+        FTS_TEST_SAVE_ERR("scap_cb fails");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* SCAP CB is in no-mapping mode */
+    ret = mapping_switch(NO_MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch no-mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_MC_SC_MODE, &sc_mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read sc_mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+
+    ret = scap_cb_ccbypass(tdata, scap_cb, &tmp_result);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scap_cb fail,ret:%d", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, sc_mode);/* set the origin value */
+    if (ret) {
+        FTS_TEST_SAVE_ERR("restore sc mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+test_err:
+    if (tmp_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("\n------ scap cb test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("\n------ scap cb test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5672_scap_rawdata_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int i = 0;
+    bool tmp_result = false;
+    bool tmp2_result = false;
+    bool tmp3_result = false;
+    
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scap_rawdata = NULL;
+    int *srawdata_tmp = NULL;
+    int srawdata_cnt = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Scap Rawdata Test\n");
+    scap_rawdata = tdata->item4_data;
+
+    if (!scap_rawdata) {
+        FTS_TEST_SAVE_ERR("scap rawdata fails");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    if (!thr->scap_rawdata_on_min || !thr->scap_rawdata_on_max
+        || !thr->scap_rawdata_off_min || !thr->scap_rawdata_off_max) {
+        FTS_TEST_SAVE_ERR("scap_rawdata_on/off/hi_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* SCAP RAWDATA is in no-mapping mode */
+    ret = mapping_switch(NO_MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch no-mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* scan rawdata */
+    ret = start_scan();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scan scap rawdata fail\n");
+        goto test_err;
+    }
+
+    ret = start_scan();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("scan scap rawdata(2) fail\n");
+        goto test_err;
+    }
+
+    /* water proof on check */
+    tmp_result = true;
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (thr->basic.scap_rawdata_wp_on_check && fw_wp_check) {
+        tdata->csv_item_cnt++;
+        tdata->csv_item_sraw |= 0x01;
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(WATER_PROOF_ON, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_ON) rawdata fail\n");
+            goto test_err;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in waterproof on mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((srawdata_tmp[i] < thr->scap_rawdata_on_min[i]) ||
+                    (srawdata_tmp[i] > thr->scap_rawdata_on_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, srawdata_tmp[i],
+                                      thr->scap_rawdata_on_min[i],
+                                      thr->scap_rawdata_on_max[i]);
+                    tmp_result = false;
+                }
+            }
+        }
+
+        srawdata_cnt += tdata->sc_node.node_num;
+    }
+
+    /* water proof off check */
+    tmp2_result = true;
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (thr->basic.scap_rawdata_wp_off_check && fw_wp_check) {
+        tdata->csv_item_cnt++;
+        tdata->csv_item_sraw |= 0x02;
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(WATER_PROOF_OFF, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_OFF) rawdata fail\n");
+            goto test_err;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in waterproof off mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((srawdata_tmp[i] < thr->scap_rawdata_off_min[i]) ||
+                    (srawdata_tmp[i] > thr->scap_rawdata_off_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, srawdata_tmp[i],
+                                      thr->scap_rawdata_off_min[i],
+                                      thr->scap_rawdata_off_max[i]);
+                    tmp2_result = false;
+                }
+            }
+        }
+        srawdata_cnt += tdata->sc_node.node_num;
+    }
+
+    /*high mode*/
+    tmp3_result = true;
+    hov_high = (hc_sel & 0x03);
+    if (thr->basic.scap_rawdata_hov_check && hov_high) {
+        tdata->csv_item_cnt++;
+        tdata->csv_item_sraw |= 0x04;
+        srawdata_tmp = scap_rawdata + srawdata_cnt;
+        ret = get_rawdata_mc_sc(HOV, srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(HS) rawdata fail\n");
+            goto test_err;
+        }
+
+        FTS_TEST_SAVE_INFO("scap_rawdata in hs mode:\n");
+        show_data_mc_sc(srawdata_tmp);
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        for (i = 0; i < tdata->sc_node.node_num; i++) {
+            if (0 == tdata->node_valid_sc[i])
+                continue;
+
+            if ((rx_check && (i < tdata->sc_node.rx_num)) ||
+                (tx_check && (i >= tdata->sc_node.rx_num))) {
+                if ((srawdata_tmp[i] < thr->scap_rawdata_hov_min[i]) ||
+                    (srawdata_tmp[i] > thr->scap_rawdata_hov_max[i])) {
+                    FTS_TEST_SAVE_ERR("test fail,CH%d=%5d,range=(%5d,%5d)\n",
+                                      i + 1, srawdata_tmp[i],
+                                      thr->scap_rawdata_hov_min[i],
+                                      thr->scap_rawdata_hov_max[i]);
+                    tmp3_result = false;
+                }
+            }
+        }
+
+        srawdata_cnt += tdata->sc_node.node_num;
+    }
+
+test_err:
+    if (tmp_result && tmp2_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("\n------ scap rawdata test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_INFO("\n------ scap rawdata test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5672_short_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    int ch_num = 0;
+    int short_res[SC_NUM_MAX + 1] = { 0 };
+    u8 ab_ch[SC_NUM_MAX + 1] = { 0 };
+    bool ca_result = false;
+    bool cg_result = false;
+    bool cc_result = false;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Short Test\n");
+    ch_num = tdata->sc_node.tx_num + tdata->sc_node.rx_num;
+
+    if (ch_num >= SC_NUM_MAX) {
+        FTS_TEST_SAVE_ERR("sc_node ch_num(%d)>max(%d)", ch_num, SC_NUM_MAX);
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* short is in no-mapping mode */
+    ret = mapping_switch(NO_MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch no-mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* get short resistance and exceptional channel */
+    ret = short_test_ch_to_all(tdata, short_res, ab_ch, &ca_result);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("short test of channel to all fails\n");
+        goto test_err;
+    }
+
+    if (!ca_result) {
+        /*weak short fail, get short values*/
+        ret = short_test_ch_to_gnd(tdata, short_res, ab_ch, &cg_result);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("short test of channel to gnd fails\n");
+            goto test_err;
+        }
+
+        ret = short_test_ch_to_ch(tdata, short_res, ab_ch, &cc_result);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("short test of channel to channel fails\n");
+            goto test_err;
+        }
+    }
+
+test_err:
+    if (ca_result) {
+        FTS_TEST_SAVE_INFO("------ short test PASS\n");
+        *test_result = true;
+    } else {
+        FTS_TEST_SAVE_ERR("------ short test NG\n");
+        *test_result = false;
+    }
+
+    ret = fts_test_write_reg(FACTROY_REG_SHORT2_TEST_STATE, 0x03);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short state c3 03 fail\n");
+    }
+    sys_delay(50);
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5672_panel_differ_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    bool tmp_result = false;
+    int i = 0;
+    u8 fre = 0;
+    u8 g_cb = 0;
+    u8 data_type = 0;
+    int *panel_differ = NULL;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Panel Differ Test\n");
+    panel_differ = tdata->item5_data;
+    tdata->csv_item_cnt++;
+
+    if (!panel_differ || !thr->panel_differ_min || !thr->panel_differ_max) {
+        FTS_TEST_SAVE_ERR("panel_differ_h_min/max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* panel differ test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_GCB, &g_cb);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read regBD fail,ret=%d\n", ret);
+        goto test_err;
+    }
+    if (g_cb == 0) {
+        tmp_result = true;
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set frequecy high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST,
+              FACTORY_REG_FRE_LIST_VALUE_HIGHEST);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequecy fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+        goto restore_reg;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    sys_delay(10);
+
+    ret = fts_test_write_reg(FACTORY_REG_GCB, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+        goto restore_reg;
+    }
+
+    /* get rawdata */
+    for (i = 0; i < 3; i++) {
+        ret = get_rawdata(panel_differ);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get rawdata fail\n");
+            goto restore_reg;
+        }
+    }
+
+    for (i = 0; i < tdata->node.node_num; i++) {
+        panel_differ[i] = panel_differ[i] / 10;
+    }
+
+    /* show test data */
+    show_data(panel_differ, false);
+
+    /* compare */
+    tmp_result = compare_array(panel_differ,
+                               thr->panel_differ_min,
+                               thr->panel_differ_max,
+                               false);
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_GCB, g_cb);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0xBD fail,ret=%d\n", ret);
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+test_err:
+    /* result */
+    if (tmp_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("------ panel differ test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("------ panel differ test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+static int ft5672_noise_test(struct fts_test *tdata, bool *test_result)
+{
+    int ret = 0;
+    bool tmp_result = false;
+    int i = 0;
+    u8 fre = 0;
+    u8 reg1a_val = 0;
+    u8 reg1b_val = 0;
+    u8 touch_value = 0;
+    u8 data_sel = 0;
+    u8 frame_h_byte = 0;
+    u8 frame_l_byte = 0;
+    int *noise = NULL;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Noise Test\n");
+    noise = tdata->item6_data;
+    tdata->csv_item_cnt++;
+
+    if (!noise || !thr->noise_min || !thr->noise_max) {
+        FTS_TEST_SAVE_ERR("noise/noise_min/noise_max is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* panel differ test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(0x0D, &touch_value);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read touch_value fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    FTS_TEST_INFO("noise(touch) value:%d", touch_value);
+    for (i = 0; i < tdata->node.node_num; i++) {
+        thr->noise_max[i] = touch_value * 4 * thr->noise_max[i] / 100;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x06 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(0x1A, &reg1a_val);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read reg1a fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(0x1B, &reg1b_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read reg1b fail,ret=%d\n", ret);
+        goto test_err;
+    }
+    FTS_TEST_INFO("fre:%d,data_sel:%d,reg1a:%d,reg1b:%d", fre, data_sel, reg1a_val, reg1b_val);
+
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data_sel fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+    sys_delay(10);
+
+    ret = fts_test_write_reg(0x1A, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set reg1A fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    frame_h_byte = BYTE_OFF_8(thr->basic.noise_framenum);
+    frame_l_byte = BYTE_OFF_0(thr->basic.noise_framenum);
+    FTS_TEST_INFO("noise frame num:%d,%d", frame_h_byte, frame_l_byte);
+    ret = fts_test_write_reg(0x1c, frame_h_byte);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1c fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    ret = fts_test_write_reg(0x1d, frame_l_byte);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1d fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    ret = get_noise_ft5672(tdata, &noise[0], FACTORY_REG_FRE_LIST_VALUE_MAIN);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get noise fails,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* show test data */
+    show_data(noise, false);
+
+    /* compare */
+    tmp_result = compare_array(noise, thr->noise_min, thr->noise_max, false);
+
+    get_null_noise(tdata);
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(0x1A, reg1a_val);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("restore reg1a fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(0x1B, reg1b_val);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore reg1b fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore data_sel fail,ret=%d\n", ret);
+    }
+
+test_err:
+    /* result */
+    if (tmp_result) {
+        *test_result = true;
+        FTS_TEST_SAVE_INFO("------ noise test PASS\n");
+    } else {
+        *test_result = false;
+        FTS_TEST_SAVE_ERR("------ noise test NG\n");
+    }
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+
+static int malloc_item_data(struct fts_test *tdata)
+{
+    tdata->item1_data = fts_malloc(tdata->node.node_num * sizeof(int));
+    if (!tdata->item1_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item2_data = fts_malloc(tdata->node.node_num * sizeof(int) * 2);
+    if (!tdata->item2_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item3_data = fts_malloc(tdata->sc_node.node_num * sizeof(int) * 6);
+    if (!tdata->item3_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item4_data = fts_malloc(tdata->sc_node.node_num * sizeof(int) * 3);
+    if (!tdata->item4_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item5_data = fts_malloc(tdata->node.node_num * sizeof(int));
+    if (!tdata->item5_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item6_data = fts_malloc(tdata->node.node_num * sizeof(int));
+    if (!tdata->item6_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item7_data = fts_malloc(tdata->sc_node.node_num * sizeof(int) * 3);
+    if (!tdata->item7_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    tdata->item9_data = fts_malloc(tdata->node.node_num * sizeof(int) * 6);
+    if (!tdata->item9_data) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+static void free_item_data(struct fts_test *tdata)
+{
+    fts_free(tdata->item1_data);
+    fts_free(tdata->item2_data);
+    fts_free(tdata->item3_data);
+    fts_free(tdata->item4_data);
+    fts_free(tdata->item5_data);
+    fts_free(tdata->item6_data);
+    fts_free(tdata->item7_data);
+    fts_free(tdata->item9_data);
+}
+
+static int start_test_ft5672(void)
+{
+    int ret = 0;
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_testitem *test_item = &tdata->ic.mc_sc.u.item;
+    bool temp_result = false;
+    bool test_result = true;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_INFO("test item:0x%x", tdata->ic.mc_sc.u.tmp);
+
+    ret = fts_test_read_reg(0x14, &tdata->fre_num);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read fre_num fails");
+        return ret;
+    }
+    FTS_TEST_INFO("fre_num:%d", tdata->fre_num);
+
+    ret = malloc_item_data(tdata);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("memory malloc fails");
+        return -ENOMEM;
+    }
+
+    /* rawdata test */
+    if (true == test_item->rawdata_test) {
+        ret = ft5672_rawdata_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    if (true == test_item->rawdata_uniformity_test) {
+        ret = ft5672_uniformity_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    if (true == test_item->low_fre_rawdata_uniformity_test) {
+        ret = ft5672_low_high_freq_rawdata_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    /* panel differ test */
+    if (true == test_item->panel_differ_test) {
+        ret = ft5672_panel_differ_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    /* noise test */
+    if (true == test_item->noise_test) {
+        ret = ft5672_noise_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    if (true == test_item->scap_noise_test) {
+        ret = ft5672_scap_noise_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    /* scap_rawdata test */
+    if (true == test_item->scap_rawdata_test) {
+        ret = ft5672_scap_rawdata_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    /* scap_cb test */
+    if (true == test_item->scap_cb_test) {
+        ret = ft5672_scap_cb_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    /* short test */
+    if (true == test_item->short_test) {
+        ret = ft5672_short_test(tdata, &temp_result);
+        if ((ret < 0) || (false == temp_result)) {
+            test_result = false;
+        }
+    }
+
+    /* restore mapping state */
+    fts_test_write_reg(FACTORY_REG_NOMAPPING, tdata->mapping);
+
+    FTS_TEST_FUNC_EXIT();
+    return test_result;
+}
+
+static int param_init_ft5672(void)
+{
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+
+    get_value_basic("SCapCbTest_ON_Global_Cb_Min", &thr->basic.scap_cb_on_gcb_min);
+    get_value_basic("SCapCbTest_ON_Global_Cb_Max", &thr->basic.scap_cb_on_gcb_max);
+    get_value_basic("SCapCbTest_OFF_Global_Cb_Min", &thr->basic.scap_cb_off_gcb_min);
+    get_value_basic("SCapCbTest_OFF_Global_Cb_Max", &thr->basic.scap_cb_off_gcb_max);
+    get_value_basic("SCapCbTest_High_Global_Cb_Min", &thr->basic.scap_cb_hi_gcb_min);
+    get_value_basic("SCapCbTest_High_Global_Cb_Max", &thr->basic.scap_cb_hi_gcb_max);
+    get_value_basic("SCapCbTest_High_Global_Cb_Max", &thr->basic.scap_cb_hov_gcb_min);
+    get_value_basic("SCapCbTest_High_Global_Cb_Max", &thr->basic.scap_cb_hov_gcb_max);
+    FTS_TEST_INFO("GCB,On(%d,%d),Off(%d,%d),High(%d,%d)",
+                  thr->basic.scap_cb_on_gcb_min, thr->basic.scap_cb_on_gcb_max,
+                  thr->basic.scap_cb_off_gcb_min, thr->basic.scap_cb_off_gcb_max,
+                  thr->basic.scap_cb_hi_gcb_min, thr->basic.scap_cb_hi_gcb_max);
+
+    get_value_basic("SCapCbTest_ON_Cf_Cb_Min", &thr->basic.scap_cb_on_cf_min);
+    get_value_basic("SCapCbTest_ON_Cf_Cb_Max", &thr->basic.scap_cb_on_cf_max);
+    get_value_basic("SCapCbTest_OFF_Cf_Cb_Min", &thr->basic.scap_cb_off_cf_min);
+    get_value_basic("SCapCbTest_OFF_Cf_Cb_Max", &thr->basic.scap_cb_off_cf_max);
+    get_value_basic("SCapCbTest_High_Cf_Cb_Min", &thr->basic.scap_cb_hi_cf_min);
+    get_value_basic("SCapCbTest_High_Cf_Cb_Max", &thr->basic.scap_cb_hi_cf_max);
+
+    get_value_basic("SCapNoiseTest_SetWaterproof_OFF", &thr->basic.scap_noise_wp_off_check);
+    get_value_basic("SCapNoiseTest_SetWaterproof_ON", &thr->basic.scap_noise_wp_on_check);
+    get_value_basic("SCapNoiseTest_SetHigh_ON", &thr->basic.scap_noise_hov_check);
+
+    get_value_basic("SCapNoiseTest_OFF_Threshold", &thr->basic.scap_noise_off_max);
+    get_value_basic("SCapNoiseTest_ON_Threshold", &thr->basic.scap_noise_on_max);
+    get_value_basic("SCapNoiseTest_High_Threshold", &thr->basic.scap_noise_hov_max);
+    
+    get_value_basic("Low_Fre_Scan_Fre", &thr->basic.low_scan_freq);
+    get_value_basic("Low_Fre_Shift", &thr->basic.low_shift);
+    get_value_basic("Low_Fre_Va_Mul", &thr->basic.low_va_vul);
+
+    get_value_basic("High_Fre_Scan_Fre", &thr->basic.high_scan_freq);
+    get_value_basic("High_Fre_Shift", &thr->basic.high_shift);
+    get_value_basic("High_Fre_Va_Mul", &thr->basic.high_va_vul);
+
+    get_value_basic("Low_Fre_RawDataTest_Max", &thr->basic.low_freq_uniformity_max);
+    get_value_basic("Low_Fre_RawDataTest_Min", &thr->basic.low_freq_uniformity_min);
+    get_value_basic("High_Fre_RawDataTest_Max", &thr->basic.high_freq_uniformity_max);
+    get_value_basic("High_Fre_RawDataTest_Min", &thr->basic.high_freq_uniformity_min);
+
+    get_value_basic("Low_Fre_RawdtaUniformityTest_Low_Fre_En", 
+        &thr->basic.low_freq_uniformity_check_en);
+    get_value_basic("High_Fre_RawdtaUniformityTest_Fre_En", 
+        &thr->basic.high_freq_uniformity_check_en);
+    
+    get_value_basic("Low_Fre_RawdataUniformityTest_Check_Tx", 
+        &thr->basic.low_freq_uniformity_check_tx);
+    get_value_basic("Low_Fre_RawdataUniformityTest_Check_Rx", 
+        &thr->basic.low_freq_uniformity_check_rx);
+    get_value_basic("High_Fre_RawdataUniformityTest_Check_Tx", 
+        &thr->basic.high_freq_uniformity_check_tx);
+    get_value_basic("High_Fre_RawdataUniformityTest_Check_Rx", 
+        &thr->basic.high_freq_uniformity_check_rx);
+
+    get_value_basic("Low_Fre_RawdataUniformityTest_Tx_Hole", 
+        &thr->basic.low_freq_uniformity_tx_hol);
+    get_value_basic("Low_Fre_RawdataUniformityTest_Rx_Hole", 
+        &thr->basic.low_freq_uniformity_rx_hol);
+    get_value_basic("High_Fre_RawdataUniformityTest_Tx_Hole", 
+        &thr->basic.high_freq_uniformity_tx_hol);
+    get_value_basic("High_Fre_RawdataUniformityTest_Rx_Hole", 
+        &thr->basic.high_freq_uniformity_rx_hol);
+    //fts_ftest->ic.mc_sc.u.item.low_fre_rawdata_uniformity_test = 1;
+
+    /*thr->basic.low_freq_uniformity_check_en = 1;
+    thr->basic.high_freq_uniformity_check_en = 1;
+    thr->basic.low_freq_uniformity_check_tx = 1;
+    thr->basic.low_freq_uniformity_check_rx = 1;
+    thr->basic.high_freq_uniformity_check_tx = 1;
+    thr->basic.high_freq_uniformity_check_rx = 1;*/
+    
+
+    return 0;
+}
+
+static void save_data_ft5672(char *buf, int *data_length)
+{
+    struct fts_test *tdata = fts_ftest;
+    struct mc_sc_testitem *test_item = &tdata->ic.mc_sc.u.item;
+    struct mc_sc_threshold *thr = &fts_ftest->ic.mc_sc.thr;
+    u32 cnt = 0;
+    u32 tmp_cnt = 0;
+    int *tmp_data = NULL;
+    int line_num = 11;
+    int i = 0;
+    u8 tx = tdata->node.tx_num;
+    u8 rx = tdata->node.rx_num;
+    u8 sc_rx = (tdata->sc_node.tx_num > tdata->sc_node.rx_num) ? tdata->sc_node.tx_num : tdata->sc_node.rx_num;
+
+    /* line 1 */
+    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "ECC, 85, 170, IC Name, %s, IC Code, %x\n", tdata->ini.ic_name, 0);
+
+    /*line 2*/
+    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "TestItem Num, %d, ", tdata->csv_item_cnt);
+    if (true == test_item->rawdata_test) {
+        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "Rawdata Test", CODE_M_RAWDATA_TEST, tx,
+                        rx, line_num, 2);
+        line_num += tx;
+    }
+
+    if (true == test_item->rawdata_uniformity_test) {
+        if (thr->basic.uniformity_check_tx) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 1);
+            line_num += tx;
+        }
+        if (thr->basic.uniformity_check_rx) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 2);
+            line_num += tx;
+        }
+    }
+
+    if (true == test_item->low_fre_rawdata_uniformity_test) {
+        if (thr->basic.low_freq_uniformity_check_en) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Low And High Freq Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 1);
+            line_num += tx;
+        }
+        if (thr->basic.low_freq_uniformity_check_tx) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Low And High Freq Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 1);
+            line_num += tx;
+        }
+        if (thr->basic.low_freq_uniformity_check_rx) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Low And High Freq Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 2);
+            line_num += tx;
+        }
+        if (thr->basic.high_freq_uniformity_check_en) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Low And High Freq Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 1);
+            line_num += tx;
+        }
+
+        if (thr->basic.high_freq_uniformity_check_tx) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Low And High Freq Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 1);
+            line_num += tx;
+        }
+        if (thr->basic.high_freq_uniformity_check_rx) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ",
+                            "Low And High Freq Rawdata Uniformity Test", CODE_M_RAWDATA_UNIFORMITY_TEST, tx, rx, line_num, 2);
+            line_num += tx;
+        }
+    }
+
+    if (true == test_item->panel_differ_test) {
+        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "Panel Differ Test",
+                        CODE_M_PANELDIFFER_TEST, tx, rx, line_num, 1);
+        line_num += tx;
+    }
+
+    if (true == test_item->noise_test) {
+        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "Noise Test", CODE_M_NOISE_TEST, tx, rx,
+                        line_num, 1);
+        line_num += tx;
+
+        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "Null Noise", 41, 1, 1, line_num, 1);
+        line_num += 1;
+    }
+
+    
+    if (true == test_item->scap_noise_test) {
+            if (tdata->csv_item_snoise & 0x01) {
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP Noise Test",
+                                CODE_M_SCAP_RAWDATA_TEST, 2, sc_rx, line_num, 1);
+                line_num += 2;
+            }
+    
+            if (tdata->csv_item_snoise & 0x02) {
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP Noise Test",
+                                CODE_M_SCAP_RAWDATA_TEST, 2, sc_rx, line_num, 2);
+                line_num += 2;
+            }
+    
+            if (tdata->csv_item_snoise & 0x04) {
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP Noise Test",
+                                CODE_M_SCAP_RAWDATA_TEST, 2, sc_rx, line_num, 3);
+                line_num += 2;
+            }
+        }
+
+
+    if (true == test_item->scap_rawdata_test) {
+        if (tdata->csv_item_sraw & 0x01) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP Rawdata Test",
+                            CODE_M_SCAP_RAWDATA_TEST, 2, sc_rx, line_num, 1);
+            line_num += 2;
+        }
+
+        if (tdata->csv_item_sraw & 0x02) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP Rawdata Test",
+                            CODE_M_SCAP_RAWDATA_TEST, 2, sc_rx, line_num, 2);
+            line_num += 2;
+        }
+
+        if (tdata->csv_item_sraw & 0x04) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP Rawdata Test",
+                            CODE_M_SCAP_RAWDATA_TEST, 2, sc_rx, line_num, 3);
+            line_num += 2;
+        }
+    }
+
+    if (true == test_item->scap_cb_test) {
+        if (tdata->csv_item_scb & 0x01) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP CB Test", CODE_M_SCAP_CB_TEST, 2,
+                            sc_rx, line_num, 1);
+            line_num += 2;
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP CB Test", CODE_M_SCAP_CB_TEST, 2,
+                            1, line_num, 2);
+            line_num += 2;
+        }
+
+        if (tdata->csv_item_scb & 0x02) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP CB Test", CODE_M_SCAP_CB_TEST, 2,
+                            sc_rx, line_num, 3);
+            line_num += 2;
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP CB Test", CODE_M_SCAP_CB_TEST, 2,
+                            1, line_num, 4);
+            line_num += 2;
+        }
+
+        if (tdata->csv_item_scb & 0x04) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP CB Test", CODE_M_SCAP_CB_TEST, 2,
+                            sc_rx, line_num, 5);
+            line_num += 2;
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%s, %d, %d, %d, %d, %d, ", "SCAP CB Test", CODE_M_SCAP_CB_TEST, 2,
+                            1, line_num, 6);
+            line_num += 2;
+        }
+    }
+
+    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n\n\n\n\n\n\n\n\n");
+
+    /*data*/
+    if (true == test_item->rawdata_test && tdata->item1_data) {
+        for (i = 0; i < tdata->node.node_num; i++) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tdata->item1_data[i]);
+            if (((i + 1) % tdata->node.rx_num) == 0)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+        }
+    }
+
+    if (true == test_item->rawdata_uniformity_test && tdata->item2_data) {
+        tmp_cnt = 0;
+        if (thr->basic.uniformity_check_tx) {
+            tmp_data = tdata->item2_data + tmp_cnt;
+            for (i = 0; i < tdata->node.node_num; i++) {
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                if (((i + 1) % tdata->node.rx_num) == 0)
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            }
+            tmp_cnt += tdata->node.node_num;
+        }
+
+        if (thr->basic.uniformity_check_rx) {
+            tmp_data = tdata->item2_data + tmp_cnt;
+            for (i = 0; i < tdata->node.node_num; i++) {
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                if (((i + 1) % tdata->node.rx_num) == 0)
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            }
+            tmp_cnt += tdata->node.node_num;
+        }
+    }
+
+    
+    if (true == test_item->low_fre_rawdata_uniformity_test && tdata->item9_data) {
+            tmp_cnt = 0;
+    
+            if (thr->basic.low_freq_uniformity_check_en) {
+                tmp_data = tdata->item9_data + tmp_cnt;
+                for (i = 0; i < tdata->node.node_num; i++) {
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                    if (((i + 1) % tdata->node.rx_num) == 0)
+                        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+                }
+                tmp_cnt += tdata->node.node_num;
+            }
+            if (thr->basic.low_freq_uniformity_check_tx) {
+                tmp_data = tdata->item9_data + tmp_cnt;
+                for (i = 0; i < tdata->node.node_num; i++) {
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                    if (((i + 1) % tdata->node.rx_num) == 0)
+                        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+                }
+                tmp_cnt += tdata->node.node_num;
+            }
+    
+            if (thr->basic.low_freq_uniformity_check_rx) {
+                tmp_data = tdata->item9_data + tmp_cnt;
+                for (i = 0; i < tdata->node.node_num; i++) {
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                    if (((i + 1) % tdata->node.rx_num) == 0)
+                        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+                }
+                tmp_cnt += tdata->node.node_num;
+            }
+    
+            tmp_cnt = tdata->node.node_num * 3;
+    
+            if (thr->basic.high_freq_uniformity_check_en) {
+                tmp_data = tdata->item9_data + tmp_cnt;
+                for (i = 0; i < tdata->node.node_num; i++) {
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                    if (((i + 1) % tdata->node.rx_num) == 0)
+                        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+                }
+                tmp_cnt += tdata->node.node_num;
+            }
+            
+            if (thr->basic.high_freq_uniformity_check_tx) {
+                tmp_data = tdata->item9_data + tmp_cnt;
+                for (i = 0; i < tdata->node.node_num; i++) {
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                    if (((i + 1) % tdata->node.rx_num) == 0)
+                        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+                }
+                tmp_cnt += tdata->node.node_num;
+            }
+    
+            if (thr->basic.high_freq_uniformity_check_rx) {
+                tmp_data = tdata->item9_data + tmp_cnt;
+                for (i = 0; i < tdata->node.node_num; i++) {
+                    cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+                    if (((i + 1) % tdata->node.rx_num) == 0)
+                        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+                }
+                tmp_cnt += tdata->node.node_num;
+            }
+        }
+
+    if (true == test_item->panel_differ_test && tdata->item5_data) {
+        for (i = 0; i < tdata->node.node_num; i++) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tdata->item5_data[i]);
+            if (((i + 1) % tdata->node.rx_num) == 0)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+        }
+    }
+
+    if (true == test_item->noise_test && tdata->item6_data) {
+        for (i = 0; i < tdata->node.node_num; i++) {
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tdata->item6_data[i]);
+            if (((i + 1) % tdata->node.rx_num) == 0)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+        }
+
+        cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tdata->null_noise_max);
+    }
+
+    if (true == test_item->scap_noise_test && tdata->item7_data) {
+        tmp_cnt = 0;
+        if (tdata->csv_item_snoise & 0x01) {
+            tmp_data = tdata->item7_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+
+        if (tdata->csv_item_snoise & 0x02) {
+            tmp_data = tdata->item7_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+
+        if (tdata->csv_item_snoise & 0x04) {
+            tmp_data = tdata->item7_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+    }
+
+    if (true == test_item->scap_rawdata_test && tdata->item4_data) {
+        tmp_cnt = 0;
+        if (tdata->csv_item_sraw & 0x01) {
+            tmp_data = tdata->item4_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+
+        if (tdata->csv_item_sraw & 0x02) {
+            tmp_data = tdata->item4_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+
+        if (tdata->csv_item_sraw & 0x04) {
+            tmp_data = tdata->item4_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+    }
+
+    if (true == test_item->scap_cb_test && tdata->item3_data) {
+        tmp_cnt = 0;
+        if (tdata->csv_item_scb & 0x01) {
+            tmp_data = tdata->item3_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+
+            tmp_data = tdata->item3_data + tmp_cnt;
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tmp_data[0]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tmp_data[tdata->sc_node.rx_num]);
+
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+
+        if (tdata->csv_item_scb & 0x02) {
+            tmp_data = tdata->item3_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+
+            tmp_data = tdata->item3_data + tmp_cnt;
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tmp_data[0]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tmp_data[tdata->sc_node.rx_num]);
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+
+        if (tdata->csv_item_scb & 0x04) {
+            tmp_data = tdata->item3_data + tmp_cnt;
+            for (i = 0; i < tdata->sc_node.rx_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            for (i = tdata->sc_node.rx_num; i < tdata->sc_node.node_num; i++)
+                cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,", tmp_data[i]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "\n");
+            tmp_cnt += tdata->sc_node.node_num;
+
+            tmp_data = tdata->item3_data + tmp_cnt;
+
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tmp_data[0]);
+            cnt += snprintf(buf + cnt, CSV_BUFFER_LEN - cnt, "%d,\n", tmp_data[tdata->sc_node.rx_num]);
+            tmp_cnt += tdata->sc_node.node_num;
+        }
+    }
+
+    *data_length = cnt;
+    free_item_data(tdata);
+}
+
+
+struct test_funcs test_func_ft5672 = {
+    .ctype = {0x90, 0x92},
+    .hwtype = IC_HW_MC_SC,
+    .key_num_total = 0,
+    .mc_sc_short_v2 = true,
+    .cb_high_support = true,
+    .param_update_support = true,
+    .param_init = param_init_ft5672,
+    .start_test = start_test_ft5672,
+    .save_data_private = save_data_ft5672,
+};
+
+/*static int ft3683u_panel_id_test(void)
+{
+    u8 val = 0;
+    u8 panel_id = 0;
+
+    enter_factory_mode();
+    fts_test_write_reg(0x50, 0x06);
+    fts_test_read_reg(0x51, &val);
+
+    FTS_TEST_INFO("panel id %#x\n", val);
+    if(val == panel_id) {
+        FTS_TEST_SAVE_INFO("------ panel id test PASS\n");
+    } else {
+        FTS_TEST_SAVE_ERR("------ panel id test NG\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+*/
+static int fts_test_get_raw_restore_reg(u8 fre, u8 data_sel, u8 data_type) {
+    int ret = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_FUNC_ENTER();
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+int fts_test_get_raw(int *raw, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int times = 0;
+    int node_num = tx * rx;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    u8 val = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: rawdata test start\n");
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x0A fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x06 fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* set frequency main */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST,
+              FACTORY_REG_FRE_LIST_VALUE_MAIN);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set frequency fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+            return ret;
+        }
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set fir fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+            return ret;
+        }
+    }
+
+    /*********************GET RAWDATA*********************/
+    for (i = 0; i < 3; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            continue;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2),
+              raw);
+    }
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+        return ret;
+    }
+
+    fts_test_get_raw_restore_reg(fre, data_sel, data_type);
+    FTS_TEST_INFO("====== Test Item: rawdata test end\n");
+    return ret;
+}
+
+int fts_test_get_baseline(int *raw,int *base_raw, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int times = 0;
+    int node_num = tx * rx;
+    u8 fre = 0;
+    u8 data_sel = 0;
+    u8 data_type = 0;
+    u8 val = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: baseline test start\n");
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_ERROR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (0xAA == state);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x0A fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &data_type);
+    if (ret) {
+        FTS_ERROR("read 0x5B fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_ERROR("read 0x06 error,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST,
+              FACTORY_REG_FRE_LIST_VALUE_HIGHEST);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set frequency fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set fir fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto restore_reg;
+        }
+    }
+
+    /*********************GET RAWDATA*********************/
+    FTS_TEST_INFO("get rawdata,i=%d", i);
+    ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write start scan mode fail\n");
+    }
+
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+        ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+        if ((ret >= 0) && (val == 0x40)) {
+            break;
+        } else {
+            FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+        }
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_ERROR("scan timeout\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write line/start addr fail\n");
+    }
+
+    ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2),raw);
+
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x01);
+    if (ret < 0) {
+        goto restore_reg;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            goto restore_reg;
+        }
+    }
+
+    /*********************GET DATA*********************/
+    ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+    while (times++ < FACTORY_TEST_RETRY) {
+        sys_delay(FACTORY_TEST_DELAY);
+
+        ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+        if ((ret >= 0) && (val == 0x40)) {
+            break;
+        } else {
+            FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+        }
+    }
+
+    if (times >= FACTORY_TEST_RETRY) {
+        FTS_TEST_ERROR("scan timeout\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_ERROR("write line/start addr fail\n");
+    }
+    ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), base_raw);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get rawdata fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, data_type);
+    if (ret < 0) {
+        FTS_TEST_ERROR("set raw type fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+test_err:
+    FTS_TEST_INFO("====== Test Item: baseline test end\n");
+    return ret;
+}
+
+int fts_test_get_strength(u8 *base_raw, u16 base_raw_size)
+{
+    int ret = 0;
+    u8 id_cmd[1] = {0};
+
+    FTS_TEST_INFO("====== Test Item: strength test start\n");
+    id_cmd[0] = FTS_CMD_READ_TOUCH_DATA;
+    sys_delay(500);
+    ret = fts_read(id_cmd, 1, base_raw, base_raw_size);
+    if (ret < 0) {
+        FTS_TEST_ERROR("get strength fail,ret=%d\n", ret);
+    }
+
+    FTS_TEST_INFO("====== Test Item: strength test end\n");
+    return ret;
+}
+
+int fts_test_get_uniformity_data(int *raw, int *rawdata_linearity, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int row = 0;
+    int col = 1;
+    int deviation = 0;
+    int max = 0;
+    int *rl_tmp = NULL;
+    int offset = 0;
+    int offset2 = 0;
+    int node_num = tx * rx;
+
+    FTS_TEST_INFO("====== Test Item: rawdata unfiormity test start\n");
+
+    FTS_TEST_INFO("Check Tx Linearity\n");
+    rl_tmp = rawdata_linearity;
+    for (row = 0; row < tx; row++) {
+        for (col = 1; col <  rx; col++) {
+            offset = row * rx + col;
+            offset2 = row * rx + col - 1;
+            deviation = abs( raw[offset] - raw[offset2]);
+            max = max(raw[offset], raw[offset2]);
+            max = max ? max : 1;
+            rl_tmp[offset] = 100 * deviation / max;
+        }
+    }
+
+    FTS_TEST_INFO("Check Rx Linearity\n");
+    rl_tmp = rawdata_linearity + node_num;
+    for (row = 1; row < tx; row++) {
+        for (col = 0; col < rx; col++) {
+            offset = row * rx + col;
+            offset2 = (row - 1) * rx + col;
+            deviation = abs(raw[offset] - raw[offset2]);
+            max = max(raw[offset], raw[offset2]);
+            max = max ? max : 1;
+            rl_tmp[offset] = 100 * deviation / max;
+        }
+    }
+
+    /* set the origin value */
+    FTS_TEST_INFO("====== Test Item: rawdata unfiormity test end\n");
+    return ret;
+}
+
+int fts_test_get_scap_cb(int *scap_cb, u8 tx, u8 rx, int *fwcheck)
+{
+    int ret = 0;
+    u8 wc_sel = 0;
+    u8 sc_mode = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    int node_num = (tx + rx);
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scb_tmp = NULL;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: Scap CB Test start\n");
+    *fwcheck = 0;
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read water_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_MC_SC_MODE, &sc_mode);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read sc_mode fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read high_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (fw_wp_check) {
+        scb_tmp = scap_cb;
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, WATER_PROOF_ON);
+        if (ret < 0) {
+            FTS_TEST_ERROR("set mc_sc mode fail\n");
+            goto exit;
+        }
+
+        if (param_update_support) {
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto exit;
+            }
+        }
+
+        
+        ret = get_cb_ft5672(scb_tmp, node_num, false);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto exit;
+        }
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        *fwcheck |= (rx_check ? 0x01 : 0x00);
+        *fwcheck |= (tx_check ? 0x02 : 0x00);
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (fw_wp_check) {
+        scb_tmp = scap_cb + node_num;
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, WATER_PROOF_OFF);
+        if (ret < 0) {
+            FTS_TEST_ERROR("set mc_sc mode fail\n");
+            goto exit;
+        }
+
+        if (param_update_support) {
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto exit;
+            }
+        }
+        
+        ret = get_cb_ft5672(scb_tmp, node_num, false);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto exit;
+        }
+        
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        *fwcheck |= (rx_check ? 0x04 : 0x00);
+        *fwcheck |= (tx_check ? 0x08 : 0x00);
+    }
+
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    FTS_TEST_INFO("hov_high = %#x\n", hov_high);
+    if (hov_high) {
+        scb_tmp = scap_cb + node_num * 2;
+        ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, HOV);
+        if (ret < 0) {
+            FTS_TEST_ERROR("set mc_sc mode fail\n");
+            goto exit;
+        }
+
+        if (param_update_support) {
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto exit;
+            }
+        }
+        ret = get_cb_ft5672(scb_tmp, node_num, false);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("read sc_cb fail,ret=%d\n", ret);
+            goto exit;
+        }
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        *fwcheck |= (rx_check ? 0x10 : 0x00);
+        *fwcheck |= (tx_check ? 0x20 : 0x00);
+    }
+
+exit:
+    ret = fts_test_write_reg(FACTORY_REG_MC_SC_MODE, sc_mode);/* set the origin value */
+    if (ret) {
+        FTS_TEST_ERROR("restore sc mode fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    FTS_TEST_INFO("====== Test Item: Scap CB Test end\n");
+    return ret;
+}
+
+int fts_test_get_scap_raw(int *scap_raw, u8 tx, u8 rx, int *fwcheck)
+{
+    int ret = 0;
+    int i = 0;
+    int times = 0;
+    int node_num = tx + rx;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *srawdata_tmp = NULL;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 val = 0;
+    u8 hov_high = 0;
+
+    FTS_TEST_INFO("====== Test Item: Scap Rawdata Test start\n");
+    *fwcheck = 0;
+
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read water_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_ERROR("read high_channel_sel fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* scan rawdata 2 times*/
+    for (i = 0; i < 2; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+    }
+    if (ret < 0) {
+        FTS_TEST_ERROR("scan scap rawdata fail\n");
+        goto exit;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (fw_wp_check) {
+        srawdata_tmp = scap_raw;
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAC);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            goto exit;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get scap(WP_ON) rawdata fail\n");
+            goto exit;
+        }
+
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        *fwcheck |= (rx_check ? 0x01 : 0x00);
+        *fwcheck |= (tx_check ? 0x02 : 0x00);
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (fw_wp_check) {
+        srawdata_tmp = scap_raw + node_num;
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAB);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            goto exit;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get scap(WP_OFF) rawdata fail\n");
+            goto exit;
+        }
+
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        *fwcheck |= (rx_check ? 0x04 : 0x00);
+        *fwcheck |= (tx_check ? 0x08 : 0x00);
+    }
+
+    
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    FTS_TEST_INFO("hov_high = %#x\n", hov_high);
+    if (hov_high) {
+        srawdata_tmp = scap_raw + node_num * 2;
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xA1);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            goto exit;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), srawdata_tmp);
+        if (ret < 0) {
+            FTS_TEST_ERROR("get scap(WP_OFF) rawdata fail\n");
+            goto exit;
+        }
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        *fwcheck |= (rx_check ? 0x10 : 0x00);
+        *fwcheck |= (tx_check ? 0x20 : 0x00);
+    }
+
+exit:
+
+    FTS_TEST_INFO("====== Test Item: Scap Rawdata Test end\n");
+    return ret;
+}
+
+int fts_test_get_short(int *short_data, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    u8 short_en_reg = FACTROY_REG_SHORT2_TEST_EN;
+    u8 short_state_reg = FACTROY_REG_SHORT2_TEST_STATE;
+    u8 short_data_reg = FACTORY_REG_SHORT2_ADDR_MC;
+    u8 short_state = 0;
+    int byte_num = (tx + rx) * 2;
+
+    FTS_TEST_INFO("====== Test Item: Short Test start");
+    /* select short test mode & start test */
+    ret = fts_test_write_reg(short_en_reg, 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short test mode fail\n");
+    }
+    
+    for (i = 0; i < FACTORY_TEST_RETRY; i++) {
+        sys_delay(FACTORY_TEST_RETRY_DELAY);
+
+        ret = fts_test_read_reg(short_state_reg, &short_state);
+        if ((ret >= 0) && (0xAA == short_state))
+            break;
+        else
+            FTS_TEST_DBG("reg%x=%x,retry:%d", short_state_reg, short_state, i);
+    }
+    if (i >= FACTORY_TEST_RETRY) {
+        FTS_TEST_SAVE_ERR("short test timeout, ADC data not OK\n");
+        ret = -EIO;
+    }
+
+    ret = read_mass_data(short_data_reg, byte_num, short_data);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get short(adc) data fail\n");
+    }
+
+    //FTS_INFO("adc:%*ph", (tx + rx), short_data);
+    ret = fts_test_write_reg(short_state_reg, 0x03);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short state c3 03 fail\n");
+    }
+    sys_delay(50);
+
+    FTS_TEST_INFO("====== Test Item: Short Test end");
+    return ret;
+}
+
+int fts_test_get_short_ch_to_gnd(int *res, u8 *ab_ch, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int byte_num = 0;
+    u8 ab_ch_num = 0;
+
+    FTS_TEST_DBG("short test:channel to gnd\n");
+    ab_ch_num = ab_ch[0];
+    ret = fts_test_write(FACTROY_REG_SHORT2_AB_CH, ab_ch, ab_ch_num + 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write abnormal channel fail\n");
+        return ret;
+    }
+
+    /*get resistance data*/
+    byte_num = ab_ch_num * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &res[0], \
+                                FACTROY_REG_SHORT2_CG);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+
+    FTS_INFO("cg adc:%*ph", ab_ch_num, res);
+
+    ret = fts_test_write_reg(0xC3, 0x03);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short state c3 03 fail\n");
+    }
+    sys_delay(50);
+
+    FTS_TEST_INFO("====== Test Item: Short Test CG end");
+    return 0;
+}
+
+int fts_test_get_short_ch_to_ch(int *res, u8 *ab_ch, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int byte_num = 0;
+    int tmp_num = 0;
+    u8 ab_ch_num = 0;
+    int ch_num = tx + rx;
+
+    FTS_TEST_DBG("short test:channel to channel\n");
+    ab_ch_num = ab_ch[0];
+    if (ab_ch_num < 2) {
+        FTS_TEST_DBG("abnormal channel number<2, not run ch_ch test");
+        return ret;
+    }
+
+    ret = fts_test_write(FACTROY_REG_SHORT2_AB_CH, ab_ch, ab_ch_num + 1);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write abnormal channel fail\n");
+        return ret;
+    }
+
+    /*get resistance data*/
+    /*channel to channel: num * (num - 1) / 2, max. node_num*/
+    tmp_num = ab_ch_num * (ab_ch_num - 1) / 2;
+    tmp_num = (tmp_num > ch_num) ? ch_num : tmp_num;
+    byte_num = tmp_num * 2;
+    ret = short_get_adc_data_mc(TEST_RETVAL_AA, byte_num, &res[0], \
+                                FACTROY_REG_SHORT2_CC);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("get weak short data fail,ret:%d\n", ret);
+        return ret;
+    }
+    FTS_INFO("cc adc:%*ph", ab_ch_num, res);
+    ret = fts_test_write_reg(0xC3, 0x03);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write short state c3 03 fail\n");
+    }
+    sys_delay(50);
+
+    FTS_TEST_INFO("====== Test Item: Short Test CC end");
+
+    return 0;
+}
+
+
+int fts_test_get_scap_noise(int *scap_noise_data, u8 tx, u8 rx, int *fwcheck)
+{
+    int ret = 0;
+    bool fw_wp_check = false;
+    bool tx_check = false;
+    bool rx_check = false;
+    int *scap_noise = NULL;
+    int *snoise_tmp = NULL;
+    int snoise_cnt = 0;
+    u8 wc_sel = 0;
+    u8 hc_sel = 0;
+    u8 hov_high = 0;
+    int node_num = tx + rx;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: Scap noise Test\n");
+    scap_noise = scap_noise_data;
+
+    *fwcheck = 0;
+    if (!scap_noise) {
+        FTS_TEST_SAVE_ERR("scap noise fails");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("enter factory mode fail,ret=%d\n", ret);
+        goto test_err;
+    }
+    
+    /* get waterproof channel select */
+    ret = fts_test_read_reg(FACTORY_REG_WC_SEL, &wc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read water_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_HC_SEL, &hc_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read high_channel_sel fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* water proof on check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_ON);
+    if (fw_wp_check) {
+        snoise_tmp = scap_noise + snoise_cnt;
+        ret = get_scap_noise_data(WATER_PROOF_ON, snoise_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_ON) noise fail\n");
+            goto restore_reg;
+        }
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_ON_RX);
+        *fwcheck |= (rx_check ? 0x01 : 0x00);
+        *fwcheck |= (tx_check ? 0x02 : 0x00);
+
+        //show_data_mc_sc(snoise_tmp);
+        
+        snoise_cnt += node_num;
+    }
+
+    /* water proof off check */
+    fw_wp_check = get_fw_wp(wc_sel, WATER_PROOF_OFF);
+    if (fw_wp_check) {
+        snoise_tmp = scap_noise + snoise_cnt;
+        ret = get_scap_noise_data(WATER_PROOF_OFF, snoise_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(WP_OFF) noise fail\n");
+            goto restore_reg;
+        }
+
+
+        /* compare */
+        tx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_TX);
+        rx_check = get_fw_wp(wc_sel, WATER_PROOF_OFF_RX);
+        *fwcheck |= (rx_check ? 0x04 : 0x00);
+        *fwcheck |= (tx_check ? 0x08 : 0x00);
+        
+        snoise_cnt += node_num;
+    }
+
+    /*high mode*/
+    hov_high = (hc_sel & 0x03);
+    if (hov_high) {
+        snoise_tmp = scap_noise + snoise_cnt;
+        ret = get_scap_noise_data(HOV, snoise_tmp);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get scap(HOV) noise fail\n");
+            goto restore_reg;
+        }
+
+        /* compare */
+        tx_check = ((hov_high == 1) || (hov_high == 3));
+        rx_check = ((hov_high == 2) || (hov_high == 3));
+        *fwcheck |= (rx_check ? 0x10 : 0x00);
+        *fwcheck |= (tx_check ? 0x20 : 0x00);
+
+        snoise_cnt += node_num;
+    }
+
+restore_reg:
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+test_err:
+
+    FTS_TEST_INFO("====== Test Item: Scap Noise Test end\n");
+
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+
+int fts_test_get_noise(int *noise, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int node_num = (tx * rx);
+    u8 fre = 0;    
+    u8 data_sel = 0;
+    u16 noise_frame = 20;
+    u8 noise_mode = 0;
+    u8 state = 0;
+    bool param_update_support = false;
+
+    FTS_TEST_INFO("====== Test Item: Noise test start");
+
+    fts_test_read_reg(FACTORY_REG_PARAM_UPDATE_STATE_TOUCH, &state);
+    param_update_support = (state == 0xAA);
+    FTS_TEST_INFO("Param update:%d", param_update_support);
+
+    ret = fts_test_read_reg(FACTORY_REG_DATA_SELECT, &data_sel);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read FACTORY_REG_DATA_SELECT error,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read FACTORY_REG_FRE_LIST fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* select rawdata */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    /* set frequency main */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST,
+              FACTORY_REG_FRE_LIST_VALUE_MAIN);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+            goto exit;
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_MAXDIFF_EN, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1A fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRAME_NUM_H, (noise_frame >> 8));
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1C fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRAME_NUM_L, noise_frame);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1D fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    noise_mode = 1;
+    FTS_TEST_INFO("noise_mode = %x\n", noise_mode);
+    ret = fts_test_write_reg(FACTORY_REG_MAXDIFF_FLAG, noise_mode);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x1B fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = start_scan_ft5672(noise_frame);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("ft5652_start_scan fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write 0x01 fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = read_mass_data(FACTORY_REG_NOISE_ADDR, (node_num * 2), noise);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read 0xCE fail\n");
+        return ret;
+    }
+
+exit:
+    ret = fts_test_write_reg(FACTORY_REG_MAXDIFF_FLAG, 0x0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x1B fail,ret=%d\n", ret);
+    }
+
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, data_sel);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x06 fail,ret=%d\n", ret);
+    }
+
+    if (param_update_support) {
+        ret = wait_state_update(TEST_RETVAL_AA);
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("wait state update fail\n");
+        }
+    }
+
+    FTS_TEST_INFO("====== Test Item: Noise test end");
+    return ret;
+}
+
+int fts_test_get_panel_differ(int *panel_differ, u8 tx, u8 rx)
+{
+    int ret = 0;
+    int i = 0;
+    int node_num = tx * rx;
+    int times = 0;
+    u8 val = 0;
+    u8 fre = 0;
+    u8 value_5b = 0;
+    u8 g_cb = 0;
+
+    FTS_TEST_INFO("====== Test Item: Panel Differ Test start");
+
+    /* save origin value */
+    ret = fts_test_read_reg(FACTORY_REG_DATA_TYPE, &value_5b);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("read normalize fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    ret = fts_test_read_reg(FACTORY_REG_FRE_LIST, &fre);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x0A fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    
+    ret = fts_test_read_reg(FACTORY_REG_GCB, &g_cb);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read regBD fail,ret=%d\n", ret);
+        return ret;
+    }
+
+    /* set to overall normalize */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("write normalize fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    /* set frequency high */
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST,
+              FACTORY_REG_FRE_LIST_VALUE_HIGHEST);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set frequency fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+        goto exit;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_GCB, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set fir fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+        goto exit;
+    }
+
+    for (i = 0; i < 3; i++) {
+        FTS_TEST_INFO("get rawdata,i=%d", i);
+        ret = fts_test_write_reg(DIVIDE_MODE_ADDR, 0xC0);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write start scan mode fail\n");
+            continue;
+        }
+
+        while (times++ < FACTORY_TEST_RETRY) {
+            sys_delay(FACTORY_TEST_DELAY);
+
+            ret = fts_test_read_reg(DIVIDE_MODE_ADDR, &val);
+            if ((ret >= 0) && (val == 0x40)) {
+                break;
+            } else {
+                FTS_TEST_DBG("reg%x=%x,retry:%d", DIVIDE_MODE_ADDR, val, times);
+            }
+        }
+
+        if (times >= FACTORY_TEST_RETRY) {
+            FTS_TEST_ERROR("scan timeout\n");
+            continue;
+        }
+
+        ret = fts_test_write_reg(FACTORY_REG_LINE_ADDR, 0xAA);
+        if (ret < 0) {
+            FTS_TEST_ERROR("write line/start addr fail\n");
+            continue;
+        }
+
+        ret = read_mass_data(FACTORY_REG_RAWDATA_ADDR_MC_SC, (node_num * 2), panel_differ);
+    }
+
+    if (ret < 0) {
+        FTS_TEST_ERROR("get panel_differ fail,ret=%d\n", ret);
+        goto exit;
+    }
+
+    for (i = 0; i < node_num; i++) {
+        panel_differ[i] = panel_differ[i] / 10;
+    }
+
+exit:
+    /* set the origin value */
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, value_5b);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore normalize fail,ret=%d\n", ret);
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_FRE_LIST, fre);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x0A fail,ret=%d\n", ret);
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_GCB, g_cb);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set raw type fail,ret=%d\n", ret);
+    }
+
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    FTS_TEST_INFO("====== Test Item: Panel Differ Test end");
+    return ret;
+}
+
+int fts_get_low_high_freq_rawdata(struct fts_test *tdata, int *data,
+                                  bool only_high)
+{
+    int ret = 0;
+    int i = 0;
+    int j = 0;
+    int k = 0;
+    int *temp_rawdata = NULL;
+    int *rawdata = NULL;
+    int *off_rawdata = NULL;
+    //u8 fre = 0;
+    //u8 data_type = 0;
+    u8 scan_freq[2] = {0};
+    u8 va_mu = 0;
+    u8 shift = 0;
+
+    u8 scan_freq_temp[2] = {0};
+    u8 vamu_temp = 0;
+    u8 shift_temp = 0;
+
+    int row = 0;
+    int col = 1;
+    int deviation = 0;
+    int max = 0;
+    //int min = 0;
+    //int uniform = 0;
+    int *rawdata_linearity = NULL;
+    int *rl_tmp = NULL;
+    int rl_cnt = 0;
+    int offset = 0;
+    int offset2 = 0;
+    int tx_num = 0;
+    int rx_num = 0;
+
+    int *rawdata_max = NULL;
+    int *rawdata_min = NULL;
+    bool tx_check = 0;
+    bool rx_check = 0;
+
+    int *tx_max = NULL;
+    int *tx_min = NULL;
+    int *rx_max = NULL;
+    int *rx_min = NULL;
+
+    bool result1 = true;
+    bool result2 = true;
+    bool result3 = true;
+    struct mc_sc_threshold *thr = &tdata->ic.mc_sc.thr;
+    char *log_buf = tdata->log_buf;
+    int count = 0;
+
+    int row_start = 0;
+
+    FTS_TEST_FUNC_ENTER();
+    FTS_TEST_SAVE_INFO("\n============ Test Item: low high freq rawdata test\n");
+    rawdata_linearity = data;
+    tdata->csv_item_cnt++;
+
+    tx_num = tdata->node.tx_num;
+    rx_num = tdata->node.rx_num;
+
+    if (!rawdata_linearity) {
+        FTS_TEST_SAVE_ERR("rawdata_linearity is null\n");
+        ret = -EINVAL;
+        goto test_err;
+    }
+
+    temp_rawdata = fts_malloc(tdata->node.node_num * sizeof(int));
+    if (!temp_rawdata) {
+        FTS_TEST_SAVE_ERR("memory temp_rawdata malloc fails");
+        goto test_err;
+    }
+
+    ret = enter_factory_mode();
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("failed to enter factory mode,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* rawdata test in mapping mode */
+    ret = mapping_switch(MAPPING);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("switch mapping fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    /* save origin value */
+    ret = fts_test_read(0x5E, scan_freq, 2);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x5E fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(0x22, &va_mu);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x22 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_read_reg(0x23, &shift);
+    if (ret) {
+        FTS_TEST_SAVE_ERR("read 0x23 fail,ret=%d\n", ret);
+        goto test_err;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_TYPE, 0x01);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    ret = fts_test_write_reg(FACTORY_REG_DATA_SELECT, 0x0);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("set data type fail,ret=%d\n", ret);
+        goto restore_reg;
+    }
+
+    k = only_high ? 1 : 0;
+    for (; k < 2; k++) {
+
+        switch (k)
+            {
+            case 0:
+                if (thr->basic.low_freq_uniformity_check_en) {
+                    FTS_TEST_INFO("switch to low freq teste rawdata");
+                    rl_cnt = 0;
+                    rawdata_max = thr->low_freq_rawdata_max;
+                    rawdata_min = thr->low_freq_rawdata_min;
+
+                    tx_max = thr->low_freq_rawdata_tx_linearity_max;
+                    rx_max = thr->low_freq_rawdata_rx_linearity_max;
+
+                    tx_min = thr->low_freq_rawdata_tx_linearity_min;
+                    rx_min = thr->low_freq_rawdata_rx_linearity_min;
+                
+                    off_rawdata = rawdata_linearity;
+                    rawdata = off_rawdata;
+                    
+                    tx_check = thr->basic.low_freq_uniformity_check_tx;
+                    rx_check = thr->basic.low_freq_uniformity_check_rx;
+
+                    scan_freq_temp[0] = BYTE_OFF_8(thr->basic.low_scan_freq);
+                    scan_freq_temp[1] = BYTE_OFF_0(thr->basic.low_scan_freq);
+                    vamu_temp = thr->basic.low_va_vul;
+                    shift_temp = thr->basic.low_shift;
+                    count += scnprintf(log_buf + count, PAGE_SIZE, "Low Freq Rawdata Uniformity Test:\n");
+                } else {
+                    continue;
+                }
+                break;
+            case 1:
+                FTS_TEST_INFO("switch to high freq teste rawdata");
+                if (thr->basic.high_freq_uniformity_check_en) {
+                    rl_cnt = 0;
+                    rawdata_max = thr->high_freq_rawdata_max;
+                    rawdata_min = thr->high_freq_rawdata_min;
+
+                    tx_max = thr->high_freq_rawdata_tx_linearity_max;
+                    rx_max = thr->high_freq_rawdata_rx_linearity_max;
+
+                    tx_min = thr->high_freq_rawdata_tx_linearity_min;
+                    rx_min = thr->high_freq_rawdata_rx_linearity_min;
+
+                    off_rawdata = rawdata_linearity + tdata->node.node_num * 3;
+                    rawdata = off_rawdata;
+                    tx_check = thr->basic.high_freq_uniformity_check_tx;
+                    rx_check = thr->basic.high_freq_uniformity_check_rx;
+
+                    scan_freq_temp[0] = BYTE_OFF_8(thr->basic.high_scan_freq);
+                    scan_freq_temp[1] = BYTE_OFF_0(thr->basic.high_scan_freq);
+                    vamu_temp = thr->basic.high_va_vul;
+                    shift_temp = thr->basic.high_shift;
+                    count += scnprintf(log_buf + count, PAGE_SIZE, "High Freq Rawdata Uniformity Test:\n");
+                } else {
+                    continue;
+                }
+                break;
+            }
+
+            fts_test_write(0x5E, scan_freq_temp, 2);
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto restore_reg;
+            }
+            fts_test_write_reg(0x23, shift_temp);
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto restore_reg;
+            }
+            fts_test_write_reg(0x22, vamu_temp);
+            ret = wait_state_update(TEST_RETVAL_AA);
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("wait state update fail\n");
+                goto restore_reg;
+            }
+
+        for (i = 0; i < 1; i++) {
+            /* lost 3 frames, in order to obtain stable data */
+            ret = start_scan();
+            if (ret < 0) {
+                FTS_TEST_SAVE_ERR("scan fail\n");
+                goto restore_reg;
+            }
+        }
+
+        /*********************GET RAWDATA*********************/
+        for (i = 0; i < 5; i++) {
+            /* lost 3 frames, in order to obtain stable data */
+            ret = get_rawdata(temp_rawdata);
+            for (j = 0; j < tdata->node.node_num; j++) {
+                rawdata[j] += temp_rawdata[j];
+            }
+        }
+        if (ret < 0) {
+            FTS_TEST_SAVE_ERR("get rawdata fail,ret=%d\n", ret);
+            goto restore_reg;
+        }
+
+        for (j = 0; j < tdata->node.node_num; j++) {
+            rawdata[j] = (rawdata[j] / 5);
+        }
+
+        //show_data(rawdata, false);
+
+        FTS_TEST_SAVE_INFO("%s Freq Rawdata:\n", k == 0? "Low":"High");
+        count += scnprintf(log_buf + count, PAGE_SIZE, "%s Freq Rawdata:\n", k == 0? "Low":"High");
+        row_start = count;
+
+        for (i = 0; i < tdata->node.node_num; i++) {
+            if ((i + 1) % rx_num) {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", rawdata[i]);
+            } else {
+                count += scnprintf(log_buf + count, PAGE_SIZE, "%d,\n", rawdata[i]);
+
+                FTS_TEST_SAVE_INFO("%s", &log_buf[row_start]);
+                row_start = count;
+            }
+        }
+        /* compare */
+        result1 = compare_array(rawdata,
+                               rawdata_min,
+                               rawdata_max,
+                               false);
+
+        FTS_TEST_SAVE_INFO("%s Freq Rawdata Test %s\n",k == 0? "Low":"High", result1? "PASS" : "NG");
+        count += scnprintf(log_buf + count, PAGE_SIZE, "%s Freq Rawdata Test %s\n",k == 0? "Low":"High", result1? "PASS" : "NG");
+
+        rl_cnt += tdata->node.node_num;
+        result2 = true;
+        if (tx_check) {
+            //FTS_TEST_SAVE_INFO("Check Tx Linearity\n");
+            tdata->csv_item_cnt++;
+            rl_tmp = off_rawdata + rl_cnt;
+            for (row = 0; row < tx_num; row++) {
+                for (col = 1; col <  rx_num; col++) {
+                    offset = row * rx_num + col;
+                    offset2 = row * rx_num + col - 1;
+                    deviation = abs( rawdata[offset] - rawdata[offset2]);
+                    max = max(rawdata[offset], rawdata[offset2]);
+                    max = max ? max : 1;
+                    rl_tmp[offset] = 100 * deviation / max;
+                }
+            }
+            /*show data in result.txt*/
+            //FTS_TEST_SAVE_INFO(" Tx Linearity:\n");
+            //show_data(rl_tmp, false);
+            //FTS_TEST_SAVE_INFO("\n" );
+
+            FTS_TEST_SAVE_INFO("%s Freq Rawdata Uniformity TX:\n", k == 0? "Low":"High");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "%s Freq Rawdata Uniformity TX:\n", k == 0? "Low":"High");
+            row_start = count;
+
+            for (i = 0; i < tdata->node.node_num; i++) {
+                if ((i + 1) % rx_num) {
+                    count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", off_rawdata[i + rl_cnt]);
+                } else {
+                    count += scnprintf(log_buf + count, PAGE_SIZE, "%d,\n", off_rawdata[i + rl_cnt]);
+                    FTS_TEST_SAVE_INFO("%s", &log_buf[row_start]);
+                    row_start = count;
+                }
+            }
+            /* compare */
+            result2 = compare_array(rl_tmp,
+                                   tx_min,
+                                   tx_max,
+                                   false);
+
+            FTS_TEST_SAVE_INFO("%s Freq Rawdata Uniformity TX %s\n",
+                k == 0? "Low":"High", result2? "PASS" : "NG");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "%s Freq Rawdata Uniformity TX %s\n",
+                k == 0? "Low":"High", result2? "PASS" : "NG");
+
+            rl_cnt += tdata->node.node_num;
+        }
+
+        result3 = true;
+        if (rx_check) {
+            //FTS_TEST_SAVE_INFO("Check Rx Linearity\n");
+            tdata->csv_item_cnt++;
+            rl_tmp = off_rawdata + rl_cnt;
+            for (row = 1; row < tx_num; row++) {
+                for (col = 0; col < rx_num; col++) {
+                    offset = row * rx_num + col;
+                    offset2 = (row - 1) * rx_num + col;
+                    deviation = abs(rawdata[offset] - rawdata[offset2]);
+                    max = max(rawdata[offset], rawdata[offset2]);
+                    max = max ? max : 1;
+                    rl_tmp[offset] = 100 * deviation / max;
+                }
+            }
+
+            //FTS_TEST_SAVE_INFO("Rx Linearity:\n");
+            //show_data(rl_tmp, false);
+            //FTS_TEST_SAVE_INFO("\n");
+
+            FTS_TEST_SAVE_INFO("%s Freq Rawdata Uniformity RX:\n", k == 0? "Low":"High");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "%s Freq Rawdata Uniformity RX:\n", k == 0? "Low":"High");
+            row_start = count;
+
+            for (i = 0; i < tdata->node.node_num; i++) {
+                if ((i + 1) % rx_num) {
+                    count += scnprintf(log_buf + count, PAGE_SIZE, "%d,", off_rawdata[i + rl_cnt]);
+                } else {
+                    count += scnprintf(log_buf + count, PAGE_SIZE, "%d,\n", off_rawdata[i + rl_cnt]);
+
+                    FTS_TEST_SAVE_INFO("%s", &log_buf[row_start]);
+                    row_start = count;
+                }
+            }
+            /* compare */
+            result3 = compare_array(rl_tmp,
+                                    rx_min,
+                                    rx_max,
+                                    false);
+
+            FTS_TEST_SAVE_INFO("%s Freq Rawdata Uniformity RX %s\n",
+                               k == 0? "Low":"High", result3? "PASS" : "NG");
+            count += scnprintf(log_buf + count, PAGE_SIZE, "%s Freq Rawdata Uniformity RX %s\n",
+                k == 0? "Low":"High", result3? "PASS" : "NG");
+            rl_cnt += tdata->node.node_num;
+        }
+    }
+
+restore_reg:
+    /* set the origin value */
+    ret = fts_test_write(0x5E, scan_freq, 2);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x5E fail,ret=%d\n", ret);
+    }
+
+    msleep(18);
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(0x22, va_mu);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x22 fail,ret=%d\n", ret);
+    }
+
+    msleep(18);
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    ret = fts_test_write_reg(0x23, shift);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x23 fail,ret=%d\n", ret);
+    }
+
+    msleep(18);
+
+    /* wait fw state update */
+    ret = wait_state_update(TEST_RETVAL_AA);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("wait state update fail\n");
+    }
+
+    
+    ret = fts_test_write_reg(0x5B, 0x00);
+    if (ret < 0) {
+        FTS_TEST_SAVE_ERR("restore 0x5B fail,ret=%d\n", ret);
+    }
+
+    fts_reset_proc(200);
+
+test_err:
+    fts_free(temp_rawdata);
+    FTS_TEST_FUNC_EXIT();
+    return ret;
+}
+
+