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, ®_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(®_addr, 1, ®_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, ®_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(®, 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, ®_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, ®_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, ®88_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, ®88_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 = §ion->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 = §ion->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, ®_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, >i_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(®_addr, 1, ®_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, ®_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(®, 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, ®_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, ®_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, ®88_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, ®88_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 = §ion->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 = §ion->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, ®1a_val); + if (ret) { + FTS_TEST_SAVE_ERR("read reg1a fail,ret=%d\n", ret); + goto test_err; + } + + ret = fts_test_read_reg(0x1B, ®1b_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; +} + +