diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index ba3c306c510d..c4d426889a53 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1349,4 +1349,6 @@ source "drivers/input/touchscreen/gt9916/Kconfig" source "drivers/input/touchscreen/gt9897t/Kconfig" +source "drivers/input/touchscreen/synaptics_s3908p/Kconfig" + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 305d2e45e771..ca7e6124759a 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -123,3 +123,4 @@ obj-$(CONFIG_TOUCHSCREEN_ST_FTS_V521_SPI) += fts_spi/ obj-$(CONFIG_TOUCHSCREEN_ST_FTS_V521_DUAL) += fts_dual/ obj-$(CONFIG_TOUCHSCREEN_GOODIX_BRL_9916) += gt9916/ obj-$(CONFIG_TOUCHSCREEN_GOODIX_BRL) += gt9897t/ +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_S3908P) += synaptics_s3908p/ diff --git a/drivers/input/touchscreen/synaptics_s3908p/Kconfig b/drivers/input/touchscreen/synaptics_s3908p/Kconfig new file mode 100755 index 000000000000..d8e803050434 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/Kconfig @@ -0,0 +1,80 @@ +# +# Synaptics TCM touchscreen driver configuration +# +config TOUCHSCREEN_SYNAPTICS_TCM_S3908P + tristate "synaptics s3908 Touchscreen" + default n + depends on SPI_MASTER + help + Say Y here if you have synaptics series SPI touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here. + + +config TOUCHSCREEN_SYNAPTICS_TCM_SPI + tristate "synaptics spi support" + default n + depends on TOUCHSCREEN_SYNAPTICS_TCM_S3908P + help + Say Y here to enable debug tools + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_SYNAPTICS_TCM_CORE + tristate "Synaptics TCM core module" + depends on TOUCHSCREEN_SYNAPTICS_TCM_S3908P + help + Say Y here to enable core functionality. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_tcm_core. + +config TOUCHSCREEN_SYNAPTICS_TCM_REFLASH + tristate "Synaptics reflash tools support" + default n + depends on TOUCHSCREEN_SYNAPTICS_TCM_S3908P + help + Say Y here to enable debug tools + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_SYNAPTICS_TCM_TESTING + tristate "Synaptics test tools support" + default n + depends on TOUCHSCREEN_SYNAPTICS_TCM_S3908P + help + Say Y here to enable debug tools + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_SYNAPTICS_TCM_DEVICE + tristate "Synaptics device support" + default n + depends on TOUCHSCREEN_SYNAPTICS_TCM_S3908P + help + Say Y here to enable debug tools + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_SYNAPTICS_TCM_DIAGNOSTIC + tristate "Synaptics diagnostic tools support" + default n + depends on TOUCHSCREEN_SYNAPTICS_TCM_S3908P + help + Say Y here to enable debug tools + + If unsure, say N. + + To compile this driver as a module, choose M here. diff --git a/drivers/input/touchscreen/synaptics_s3908p/Makefile b/drivers/input/touchscreen/synaptics_s3908p/Makefile new file mode 100755 index 000000000000..c9e5033ffb2e --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/Makefile @@ -0,0 +1,36 @@ +# +# Makefile for the Synaptics TCM touchscreen driver. +# + +# Each configuration option enables a list of files. +# +# Makefile for the Synaptics TCM touchscreen driver. +# + +# Each configuration option enables a list of files. +ifneq ($(KERNELRELEASE),) + obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_SPI) += synaptics_tcm_spi.o + obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE) += synaptics_tcm_core_module.o + synaptics_tcm_core_module-y := synaptics_tcm_core.o synaptics_tcm_touch.o + obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_REFLASH) += synaptics_tcm_reflash.o + obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_DEVICE) += synaptics_tcm_device.o + obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TESTING) += synaptics_tcm_testing.o + obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_DIAGNOSTIC) += synaptics_tcm_diagnostics.o + +else + KDIR = $(OUT)/obj/KERNEL_OBJ + CROSS_COMPILE = $(ANDROID_TOOLCHAIN)/aarch64-linux-android- + CLANG = $(ANDROID_BUILD_TOP)/prebuilts/clang/host/linux-x86/clang-r370808 + REAL_CC = $(CLANG)/bin/clang + AR = $(CLANG)/bin/llvm-ar + LLVM_NM = $(CLANG)/bin/llvm-nm + LD = $(CLANG)/bin/ld.lld + +.PHONY: clean + +default: + $(MAKE) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) REAL_CC=$(REAL_CC) CLANG_TRIPLE=aarch64-linux-gnu- AR=$(AR) LLVM_NM=$(LLVM_NM) LD=$(LD) -C $(KDIR) M=$(PWD) modules +clean: + @rm -rf *.o* *.order *.symvers *.mod* .*.o.cmd .*.mod.o.cmd .*.ko.cmd .tmp_versions *.ko + @find -name *.o | xargs rm -f +endif diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_core.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_core.c new file mode 100755 index 000000000000..4bd8ae98cd8a --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_core.c @@ -0,0 +1,6602 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include +#include +#include +#include "synaptics_tcm_core.h" + +/* #define RESET_ON_RESUME */ + +/* #define RESUME_EARLY_UNBLANK */ + +#define RESET_ON_RESUME_DELAY_MS 50 + +#define PREDICTIVE_READING + +#define MIN_READ_LENGTH 9 + +/* #define FORCE_RUN_APPLICATION_FIRMWARE */ + +#define NOTIFIER_PRIORITY 2 + +#define RESPONSE_TIMEOUT_MS 3000 + +#define APP_STATUS_POLL_TIMEOUT_MS 1000 + +#define APP_STATUS_POLL_MS 100 + +#define ENABLE_IRQ_DELAY_MS 20 + +#define FALL_BACK_ON_POLLING + +#define POLLING_DELAY_MS 5 + +#define MODE_SWITCH_DELAY_MS 100 + +#define READ_RETRY_US_MIN 5000 + +#define READ_RETRY_US_MAX 10000 + +#define WRITE_DELAY_US_MIN 500 + +#define WRITE_DELAY_US_MAX 1000 + +#define DYNAMIC_CONFIG_SYSFS_DIR_NAME "dynamic_config" + +#define ROMBOOT_DOWNLOAD_UNIT 16 + +#define PDT_END_ADDR 0x00ee + +#define RMI_UBL_FN_NUMBER 0x35 + +/* #define GRIP_MODE_DEBUG */ + +#define SYNA_GAME_MODE_ARRAY "synaptics,game-mode-array" +#define SYNA_ACTIVE_MODE_ARRAY "synaptics,active-mode-array" +#define SYNA_UP_THRESHOLD_ARRAY "synaptics,up-threshold-array" +#define SYNA_TOLERANCE_ARRAY "synaptics,tolerance-array" +#define SYNA_EDGE_FILTER_ARRAY "synaptics,edge-filter-array" +#define SYNA_PANEL_ORIEN_ARRAY "synaptics,panel-orien-array" +#define SYNA_REPORT_RATE_ARRAY "synaptics,report-rate-array" +#define SYNA_CORNER_FILTER_AREA_STEP_ARRAY "synaptics,cornerfilter-area-step-array" +#define SYNA_CORNER_ZONE_FILTER_HOR1_ARRAY "synaptics,cornerzone-filter-hor1-array" +#define SYNA_CORNER_ZONE_FILTER_HOR2_ARRAY "synaptics,cornerzone-filter-hor2-array" +#define SYNA_CORNER_ZONE_FILTER_VER_ARRAY "synaptics,cornerzone-filter-ver-array" +#define SYNA_DEAD_ZONE_FILTER_HOR_ARRAY "synaptics,deadzone-filter-hor-array" +#define SYNA_DEAD_ZONE_FILTER_VER_ARRAY "synaptics,deadzone-filter-ver-array" +#define SYNA_EDGE_ZONE_FILTER_HOR_ARRAY "synaptics,edgezone-filter-hor-array" +#define SYNA_EDGE_ZONE_FILTER_VER_ARRAY "synaptics,edgezone-filter-ver-array" +#define SYNA_DISPLAY_RESOLUTION_ARRAY "synaptics,panel-display-resolution" + +enum syna_dts_index{ + SYNA_DTS_GET_MAX_INDEX = 0, + SYNA_DTS_GET_MIN_INDEX, + SYNA_DTS_GET_DEF_INDEX, + SYNA_DTS_SET_CUR_INDEX, + SYNA_DTS_GET_CUR_INDEX, +}; + +static struct syna_tcm_hcd *gloab_tcm_hcd; +static bool tp_probe_success; +static void syna_tcm_reinit_mode(void); +static int syna_tcm_palm_area_change_setting(int value); + +static const unsigned int touch_mode_dc_id_table[][2] = { +/* {Touch_Active_MODE, DC_NO_DOZE}, */ + {Touch_UP_THRESHOLD, DC_FAST_TAP_HYTERESIS}, + {Touch_Tolerance, DC_MOTION_TOLERANCE}, + {Touch_Report_Rate, DC_SET_REPORT_RATE}, +/* {Touch_Aim_Sensitivity DC_UNKNOWN}, */ + {Touch_Mode_NUM, DC_UNKNOWN} +}; + +static unsigned int syna_tcm_get_dc_id(unsigned int mode) +{ + unsigned int i = 0; + + while (touch_mode_dc_id_table[i][0] < Touch_Mode_NUM) { + if (touch_mode_dc_id_table[i][0] == mode) { + break; + } + + i++; + } + + return touch_mode_dc_id_table[i][1]; +} + +#if (USE_KOBJ_SYSFS) + +#define dynamic_config_sysfs(c_name, id) \ +static ssize_t syna_tcm_sysfs_##c_name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ +{ \ + int retval; \ + unsigned short value; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->get_dynamic_config(tcm_hcd, id, &value); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to get dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, "%u\n", value); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} \ +\ +static ssize_t syna_tcm_sysfs_##c_name##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, const char *buf, size_t count) \ +{ \ + int retval; \ + unsigned int input; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + if (sscanf(buf, "%u", &input) != 1) \ + return -EINVAL; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->set_dynamic_config(tcm_hcd, id, input); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to set dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = count; \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} + +#else + +#define dynamic_config_sysfs(c_name, id) \ +static ssize_t syna_tcm_sysfs_##c_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int retval; \ + unsigned short value; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->get_dynamic_config(tcm_hcd, id, &value); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to get dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, "%u\n", value); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} \ +\ +static ssize_t syna_tcm_sysfs_##c_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + int retval; \ + unsigned int input; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + if (sscanf(buf, "%u", &input) != 1) \ + return -EINVAL; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->set_dynamic_config(tcm_hcd, id, input); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to set dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = count; \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} +#endif + +DECLARE_COMPLETION(response_complete); + +static struct kobject *sysfs_dir; + +static struct syna_tcm_module_pool mod_pool; + +#if (USE_KOBJ_SYSFS) +KOBJ_SHOW_PROTOTYPE(syna_tcm, info) +KOBJ_SHOW_PROTOTYPE(syna_tcm, info_appfw) +KOBJ_STORE_PROTOTYPE(syna_tcm, irq_en) +KOBJ_STORE_PROTOTYPE(syna_tcm, reset) +KOBJ_STORE_PROTOTYPE(syna_tcm, cb_debug) +KOBJ_STORE_PROTOTYPE(syna_tcm, misc_debug) +#ifdef WATCHDOG_SW +KOBJ_STORE_PROTOTYPE(syna_tcm, watchdog) +#endif +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, no_doze) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, disable_noise_mitigation) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, inhibit_frequency_shift) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, requested_frequency) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, disable_hsync) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, rezero_on_exit_deep_sleep) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, charger_connected) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, no_baseline_relaxation) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, in_wakeup_gesture_mode) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, stimulus_fingers) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, grip_suppression_enabled) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, enable_thick_glove) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, enable_glove) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, enable_touch_and_hold) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, game_mode_ctrl) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, set_report_rate) +KOBJ_SHOW_STORE_PROTOTYPE(syna_tcm, enable_gesture_type) + +static struct kobj_attribute *attrs[] = { + KOBJ_ATTRIFY(info), + KOBJ_ATTRIFY(info_appfw), + KOBJ_ATTRIFY(irq_en), + KOBJ_ATTRIFY(reset), + KOBJ_ATTRIFY(cb_debug), + KOBJ_ATTRIFY(misc_debug), +#ifdef WATCHDOG_SW + KOBJ_ATTRIFY(watchdog), +#endif +}; + +static struct kobj_attribute *dynamic_config_attrs[] = { + KOBJ_ATTRIFY(no_doze), + KOBJ_ATTRIFY(disable_noise_mitigation), + KOBJ_ATTRIFY(inhibit_frequency_shift), + KOBJ_ATTRIFY(requested_frequency), + KOBJ_ATTRIFY(disable_hsync), + KOBJ_ATTRIFY(rezero_on_exit_deep_sleep), + KOBJ_ATTRIFY(charger_connected), + KOBJ_ATTRIFY(no_baseline_relaxation), + KOBJ_ATTRIFY(in_wakeup_gesture_mode), + KOBJ_ATTRIFY(stimulus_fingers), + KOBJ_ATTRIFY(grip_suppression_enabled), + KOBJ_ATTRIFY(enable_thick_glove), + KOBJ_ATTRIFY(enable_glove), + KOBJ_ATTRIFY(enable_touch_and_hold), + KOBJ_ATTRIFY(game_mode_ctrl), + KOBJ_ATTRIFY(set_report_rate), + KOBJ_ATTRIFY(enable_gesture_type), +}; + +#else /* apply device attribute declarations */ + +SHOW_PROTOTYPE(syna_tcm, info) +SHOW_PROTOTYPE(syna_tcm, info_appfw) +STORE_PROTOTYPE(syna_tcm, irq_en) +STORE_PROTOTYPE(syna_tcm, reset) +#ifdef WATCHDOG_SW +STORE_PROTOTYPE(syna_tcm, watchdog) +#endif +SHOW_STORE_PROTOTYPE(syna_tcm, no_doze) +SHOW_STORE_PROTOTYPE(syna_tcm, disable_noise_mitigation) +SHOW_STORE_PROTOTYPE(syna_tcm, inhibit_frequency_shift) +SHOW_STORE_PROTOTYPE(syna_tcm, requested_frequency) +SHOW_STORE_PROTOTYPE(syna_tcm, disable_hsync) +SHOW_STORE_PROTOTYPE(syna_tcm, rezero_on_exit_deep_sleep) +SHOW_STORE_PROTOTYPE(syna_tcm, charger_connected) +SHOW_STORE_PROTOTYPE(syna_tcm, no_baseline_relaxation) +SHOW_STORE_PROTOTYPE(syna_tcm, in_wakeup_gesture_mode) +SHOW_STORE_PROTOTYPE(syna_tcm, stimulus_fingers) +SHOW_STORE_PROTOTYPE(syna_tcm, grip_suppression_enabled) +SHOW_STORE_PROTOTYPE(syna_tcm, enable_thick_glove) +SHOW_STORE_PROTOTYPE(syna_tcm, enable_glove) +SHOW_STORE_PROTOTYPE(syna_tcm, enable_touch_and_hold) +SHOW_STORE_PROTOTYPE(syna_tcm, game_mode_ctrl) +SHOW_STORE_PROTOTYPE(syna_tcm, set_report_rate) +SHOW_STORE_PROTOTYPE(syna_tcm, enable_gesture_type) + +static struct device_attribute *attrs[] = { + ATTRIFY(info), + ATTRIFY(info_appfw), + ATTRIFY(irq_en), + ATTRIFY(reset), +#ifdef WATCHDOG_SW + ATTRIFY(watchdog), +#endif +}; + +static struct device_attribute *dynamic_config_attrs[] = { + ATTRIFY(no_doze), + ATTRIFY(disable_noise_mitigation), + ATTRIFY(inhibit_frequency_shift), + ATTRIFY(requested_frequency), + ATTRIFY(disable_hsync), + ATTRIFY(rezero_on_exit_deep_sleep), + ATTRIFY(charger_connected), + ATTRIFY(no_baseline_relaxation), + ATTRIFY(in_wakeup_gesture_mode), + ATTRIFY(stimulus_fingers), + ATTRIFY(grip_suppression_enabled), + ATTRIFY(enable_thick_glove), + ATTRIFY(enable_glove), + ATTRIFY(enable_touch_and_hold), + ATTRIFY(game_mode_ctrl), + ATTRIFY(set_report_rate), + ATTRIFY(enable_gesture_type), +}; +#endif /* END of #if USE_KOBJ_SYSFS*/ + +static int syna_tcm_get_app_info(struct syna_tcm_hcd *tcm_hcd); +static int syna_tcm_sensor_detection(struct syna_tcm_hcd *tcm_hcd); +static void syna_tcm_check_hdl(struct syna_tcm_hcd *tcm_hcd, + unsigned char id); + +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_info_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +#else +static ssize_t syna_tcm_sysfs_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +#endif +{ + int retval; + unsigned int count; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + LOGE(tcm_hcd->pdev->dev.parent, + "PAGE_SIZE = 0x%lx\n", + PAGE_SIZE); + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + count = 0; + retval = snprintf(buf, PAGE_SIZE - count, + "TouchComm version: %d\n", + tcm_hcd->id_info.version); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + if (SYNAPTICS_TCM_ID_SUBVERSION == 0) { + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d\n", + (unsigned char)(SYNAPTICS_TCM_ID_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_ID_VERSION); + } else { + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d.%d\n", + (unsigned char)(SYNAPTICS_TCM_ID_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_ID_VERSION, + SYNAPTICS_TCM_ID_SUBVERSION); + } + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + switch (tcm_hcd->id_info.mode) { + case MODE_APPLICATION_FIRMWARE: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Application Firmware\n"); + if (retval < 0) + goto exit; + break; + case MODE_HOSTDOWNLOAD_FIRMWARE: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Host Download Firmware\n"); + if (retval < 0) + goto exit; + break; + case MODE_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Bootloader\n"); + if (retval < 0) + goto exit; + break; + case MODE_TDDI_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: TDDI Bootloader\n"); + if (retval < 0) + goto exit; + break; + case MODE_TDDI_HOSTDOWNLOAD_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: TDDI Host Download Bootloader\n"); + if (retval < 0) + goto exit; + break; + case MODE_ROMBOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Rom Bootloader\n"); + if (retval < 0) + goto exit; + break; + default: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Unknown (%d)\n", + tcm_hcd->id_info.mode); + if (retval < 0) + goto exit; + break; + } + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Part number: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = secure_memcpy(buf, + PAGE_SIZE - count, + tcm_hcd->id_info.part_number, + sizeof(tcm_hcd->id_info.part_number), + sizeof(tcm_hcd->id_info.part_number)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy part number string\n"); + goto exit; + } + buf += sizeof(tcm_hcd->id_info.part_number); + count += sizeof(tcm_hcd->id_info.part_number); + + retval = snprintf(buf, PAGE_SIZE - count, + "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Packrat number: %d\n", + tcm_hcd->packrat_number); + if (retval < 0) + goto exit; + + count += retval; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_info_appfw_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +#else +static ssize_t syna_tcm_sysfs_info_appfw_show(struct device *dev, + struct device_attribute *attr, char *buf) +#endif +{ + int retval; + unsigned int count; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + int i; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = syna_tcm_get_app_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get app info\n"); + goto exit; + } + + count = 0; + + retval = snprintf(buf, PAGE_SIZE - count, + "app info version: %d\n", + le2_to_uint(tcm_hcd->app_info.version)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "app info status: %d\n", + le2_to_uint(tcm_hcd->app_info.status)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "static config size: %d\n", + le2_to_uint(tcm_hcd->app_info.static_config_size)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "dynamic config size: %d\n", + le2_to_uint(tcm_hcd->app_info.dynamic_config_size)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "app config block: %d\n", + le2_to_uint(tcm_hcd->app_info.app_config_start_write_block)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "app config size: %d\n", + le2_to_uint(tcm_hcd->app_info.app_config_size)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "touch report config max size: %d\n", + le2_to_uint(tcm_hcd->app_info.max_touch_report_config_size)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "touch report payload max size: %d\n", + le2_to_uint(tcm_hcd->app_info.max_touch_report_payload_size)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, "config id: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + for (i = 0; i < sizeof(tcm_hcd->app_info.customer_config_id); i++) { + retval = snprintf(buf, PAGE_SIZE - count, + "0x%2x ", tcm_hcd->app_info.customer_config_id[i]); + buf += retval; + count += retval; + } + + retval = snprintf(buf, PAGE_SIZE - count, "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "max x: %d\n", + le2_to_uint(tcm_hcd->app_info.max_x)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "max y: %d\n", + le2_to_uint(tcm_hcd->app_info.max_y)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "max objects: %d\n", + le2_to_uint(tcm_hcd->app_info.max_objects)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "num cols: %d\n", + le2_to_uint(tcm_hcd->app_info.num_of_image_cols)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "num rows: %d\n", + le2_to_uint(tcm_hcd->app_info.num_of_image_rows)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "num buttons: %d\n", + le2_to_uint(tcm_hcd->app_info.num_of_buttons)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "has profile: %d\n", + le2_to_uint(tcm_hcd->app_info.has_hybrid_data)); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "num force electrodes: %d\n", + le2_to_uint(tcm_hcd->app_info.num_of_force_elecs)); + if (retval < 0) + goto exit; + + count += retval; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + + +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_irq_en_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +#else +static ssize_t syna_tcm_sysfs_irq_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +#endif +{ + int retval; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (input == 0) { + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + goto exit; + } + } else if (input == 1) { + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto exit; + } + } else { + retval = -EINVAL; + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_reset_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +#else +static ssize_t syna_tcm_sysfs_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +#endif +{ + int retval; + bool hw_reset; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + hw_reset = false; + else if (input == 2) + hw_reset = true; + else + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->reset_n_reinit(tcm_hcd, hw_reset, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset and reinit\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int syna_tcm_set_cur_value(int mode, int val); +static void syna_tcm_esd_recovery(struct syna_tcm_hcd *tcm_hcd); + +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_cb_debug_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +#else +static ssize_t syna_tcm_sysfs_cb_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +#endif +{ + int retval; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + unsigned int mode, val; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOGN(tcm_hcd->pdev->dev.parent, + "debug get data 0x%02x\n",input); + + mode = input & 0xFF; + val = (input >> 8) & 0xFF; + syna_tcm_set_cur_value(mode, val); + + retval = count; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +/*static ssize_t syna_tcm_sysfs_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int value = 0; + + LOGI(gloab_tcm_hcd->pdev->dev.parent, + "%s,buf: %s,count: %zu\n", __func__, buf, count); + sscanf(buf, "%u", &value); + touch_fod_test(value); + return count; +} +static DEVICE_ATTR(fod_test, (S_IRUGO | S_IWUSR | S_IWGRP), NULL,\ +syna_tcm_sysfs_test_store); +*/ + +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_misc_debug_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +#else +static ssize_t syna_tcm_sysfs_misc_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +#endif +{ + int retval; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOGN(tcm_hcd->pdev->dev.parent, + "misc debug get data 0x%02x\n", input); + + /* 1 -- esd recovery */ + if (input == 1) + syna_tcm_esd_recovery(tcm_hcd); + + retval = count; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +#ifdef WATCHDOG_SW +#if (USE_KOBJ_SYSFS) +static ssize_t syna_tcm_sysfs_watchdog_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +#else +static ssize_t syna_tcm_sysfs_watchdog_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +#endif +{ + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input != 0 && input != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + tcm_hcd->watchdog.run = input; + tcm_hcd->update_watchdog(tcm_hcd, input); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return count; +} +#endif + +dynamic_config_sysfs(no_doze, DC_NO_DOZE) + +dynamic_config_sysfs(disable_noise_mitigation, DC_DISABLE_NOISE_MITIGATION) + +dynamic_config_sysfs(inhibit_frequency_shift, DC_INHIBIT_FREQUENCY_SHIFT) + +dynamic_config_sysfs(requested_frequency, DC_REQUESTED_FREQUENCY) + +dynamic_config_sysfs(disable_hsync, DC_DISABLE_HSYNC) + +dynamic_config_sysfs(rezero_on_exit_deep_sleep, DC_REZERO_ON_EXIT_DEEP_SLEEP) + +dynamic_config_sysfs(charger_connected, DC_CHARGER_CONNECTED) + +dynamic_config_sysfs(no_baseline_relaxation, DC_NO_BASELINE_RELAXATION) + +dynamic_config_sysfs(in_wakeup_gesture_mode, DC_IN_WAKEUP_GESTURE_MODE) + +dynamic_config_sysfs(stimulus_fingers, DC_STIMULUS_FINGERS) + +dynamic_config_sysfs(grip_suppression_enabled, DC_GRIP_SUPPRESSION_ENABLED) + +dynamic_config_sysfs(enable_thick_glove, DC_ENABLE_THICK_GLOVE) + +dynamic_config_sysfs(enable_glove, DC_ENABLE_GLOVE) + +dynamic_config_sysfs(enable_touch_and_hold, DC_ENABLE_TOUCH_AND_HOLD); + +dynamic_config_sysfs(game_mode_ctrl, DC_GAME_MODE_CTRL); + +dynamic_config_sysfs(set_report_rate, DC_SET_REPORT_RATE); + +dynamic_config_sysfs(enable_gesture_type, DC_GESTURE_TYPE_ENABLE); + +int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert) +{ + struct syna_tcm_module_handler *mod_handler; + + if (!mod_pool.initialized) { + mutex_init(&mod_pool.mutex); + INIT_LIST_HEAD(&mod_pool.list); + mod_pool.initialized = true; + } + + mutex_lock(&mod_pool.mutex); + + if (insert) { + mod_handler = kzalloc(sizeof(*mod_handler), GFP_KERNEL); + if (!mod_handler) { + pr_err("%s: Failed to allocate memory for mod_handler\n", + __func__); + mutex_unlock(&mod_pool.mutex); + return -ENOMEM; + } + mod_handler->mod_cb = mod_cb; + mod_handler->insert = true; + mod_handler->detach = false; + list_add_tail(&mod_handler->link, &mod_pool.list); + } else if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (mod_handler->mod_cb->type == mod_cb->type) { + mod_handler->insert = false; + mod_handler->detach = true; + goto exit; + } + } + } + +exit: + mutex_unlock(&mod_pool.mutex); + + if (mod_pool.queue_work) + queue_work(mod_pool.workqueue, &mod_pool.work); + + return 0; +} +EXPORT_SYMBOL(syna_tcm_add_module); + +static void syna_tcm_module_work(struct work_struct *work) +{ + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_module_handler *tmp_handler; + struct syna_tcm_hcd *tcm_hcd = mod_pool.tcm_hcd; + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry_safe(mod_handler, + tmp_handler, + &mod_pool.list, + link) { + if (mod_handler->insert) { + if (mod_handler->mod_cb->init) + mod_handler->mod_cb->init(tcm_hcd); + mod_handler->insert = false; + } + if (mod_handler->detach) { + if (mod_handler->mod_cb->remove) + mod_handler->mod_cb->remove(tcm_hcd); + list_del(&mod_handler->link); + kfree(mod_handler); + } + } + } + + mutex_unlock(&mod_pool.mutex); + + return; +} + + +#ifdef REPORT_NOTIFIER +/** + * syna_tcm_report_notifier() - notify occurrence of report received from device + * + * @data: handle of core module + * + * The occurrence of the report generated by the device is forwarded to the + * asynchronous inbox of each registered application module. + */ +static int syna_tcm_report_notifier(void *data) +{ + struct sched_param param = { .sched_priority = NOTIFIER_PRIORITY }; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = data; + + sched_setscheduler(current, SCHED_RR, ¶m); + + set_current_state(TASK_INTERRUPTIBLE); + + while (!kthread_should_stop()) { + schedule(); + + if (kthread_should_stop()) + break; + + set_current_state(TASK_RUNNING); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->asyncbox)) + mod_handler->mod_cb->asyncbox(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + set_current_state(TASK_INTERRUPTIBLE); + }; + + return 0; +} +#endif + +/** + * syna_tcm_dispatch_report() - dispatch report received from device + * + * @tcm_hcd: handle of core module + * + * The report generated by the device is forwarded to the synchronous inbox of + * each registered application module for further processing. In addition, the + * report notifier thread is woken up for asynchronous notification of the + * report occurrence. + */ +static void syna_tcm_dispatch_report(struct syna_tcm_hcd *tcm_hcd) +{ + struct syna_tcm_module_handler *mod_handler; + + LOCK_BUFFER(tcm_hcd->in); + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE]; + + tcm_hcd->report.buffer.buf_size = tcm_hcd->in.buf_size; + tcm_hcd->report.buffer.buf_size -= MESSAGE_HEADER_SIZE; + + tcm_hcd->report.buffer.data_length = tcm_hcd->payload_length; + + tcm_hcd->report.id = tcm_hcd->status_report_code; + + /* report directly if touch report is received */ + if (tcm_hcd->report.id == REPORT_TOUCH) { + if (tcm_hcd->report_touch) + tcm_hcd->report_touch(); + + } else { + + /* once an identify report is received, */ + /* reinitialize touch in case any changes */ + if ((tcm_hcd->report.id == REPORT_IDENTIFY) && + IS_FW_MODE(tcm_hcd->id_info.mode)) { + + if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_TOUCH_REINIT); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + } + } + + /* dispatch received report to the other modules */ + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->syncbox)) + mod_handler->mod_cb->syncbox(tcm_hcd); + } + } + + tcm_hcd->async_report_id = tcm_hcd->status_report_code; + + mutex_unlock(&mod_pool.mutex); + } + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + UNLOCK_BUFFER(tcm_hcd->in); + +#ifdef REPORT_NOTIFIER + wake_up_process(tcm_hcd->notifier_thread); +#endif + + return; +} + +/** + * syna_tcm_dispatch_response() - dispatch response received from device + * + * @tcm_hcd: handle of core module + * + * The response to a command is forwarded to the sender of the command. + */ +static void syna_tcm_dispatch_response(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (atomic_read(&tcm_hcd->command_status) != CMD_BUSY) + return; + + tcm_hcd->response_code = tcm_hcd->status_report_code; + + if (tcm_hcd->payload_length == 0) { + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + goto exit; + } + + LOCK_BUFFER(tcm_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->resp, + tcm_hcd->payload_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->resp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->resp); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + goto exit; + } + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy(tcm_hcd->resp.buf, + tcm_hcd->resp.buf_size, + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_hcd->payload_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->in); + UNLOCK_BUFFER(tcm_hcd->resp); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + goto exit; + } + + tcm_hcd->resp.data_length = tcm_hcd->payload_length; + + UNLOCK_BUFFER(tcm_hcd->in); + UNLOCK_BUFFER(tcm_hcd->resp); + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + +exit: + complete(&response_complete); + + return; +} + +/** + * syna_tcm_dispatch_message() - dispatch message received from device + * + * @tcm_hcd: handle of core module + * + * The information received in the message read in from the device is dispatched + * to the appropriate destination based on whether the information represents a + * report or a response to a command. + */ +static void syna_tcm_dispatch_message(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *build_id; + unsigned int payload_length; + unsigned int max_write_size; + + if (tcm_hcd->status_report_code == REPORT_IDENTIFY) { + payload_length = tcm_hcd->payload_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + MIN(sizeof(tcm_hcd->id_info), payload_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + build_id = tcm_hcd->id_info.build_id; + tcm_hcd->packrat_number = le4_to_uint(build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + + LOGN(tcm_hcd->pdev->dev.parent, + "Received identify report (firmware mode = 0x%02x)\n", + tcm_hcd->id_info.mode); + + if (atomic_read(&tcm_hcd->command_status) == CMD_BUSY) { + switch (tcm_hcd->command) { + case CMD_RESET: + case CMD_RUN_BOOTLOADER_FIRMWARE: + case CMD_RUN_APPLICATION_FIRMWARE: + case CMD_ENTER_PRODUCTION_TEST_MODE: + case CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE: + tcm_hcd->response_code = STATUS_OK; + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + complete(&response_complete); + break; + default: + LOGN(tcm_hcd->pdev->dev.parent, + "Device has been reset\n"); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + complete(&response_complete); + break; + } + } else { + + if ((tcm_hcd->id_info.mode == MODE_ROMBOOTLOADER) && + tcm_hcd->in_hdl_mode) { + + if (atomic_read(&tcm_hcd->helper.task) == + HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_SEND_ROMBOOT_HDL); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + } else { + LOGN(tcm_hcd->pdev->dev.parent, + "Helper thread is busy\n"); + } + return; + } + } + +#ifdef FORCE_RUN_APPLICATION_FIRMWARE + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) && + !mutex_is_locked(&tcm_hcd->reset_mutex)) { + + if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_RUN_APPLICATION_FIRMWARE); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + return; + } + } +#endif + + /* To avoid the identify report dispatching during the HDL. */ + if (atomic_read(&tcm_hcd->host_downloading)) { + LOGN(tcm_hcd->pdev->dev.parent, + "Switched to TCM mode and going to download the configs\n"); + return; + } + } + + + if (tcm_hcd->status_report_code >= REPORT_IDENTIFY) + syna_tcm_dispatch_report(tcm_hcd); + else + syna_tcm_dispatch_response(tcm_hcd); + + return; +} + +/** + * syna_tcm_continued_read() - retrieve entire payload from device + * + * @tcm_hcd: handle of core module + * + * Read transactions are carried out until the entire payload is retrieved from + * the device and stored in the handle of the core module. + */ +static int syna_tcm_continued_read(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char marker; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int total_length; + unsigned int remaining_length; + + total_length = MESSAGE_HEADER_SIZE + tcm_hcd->payload_length + 1; + + remaining_length = total_length - tcm_hcd->read_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_realloc_mem(tcm_hcd, + &tcm_hcd->in, + total_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reallocate memory for tcm_hcd->in.buf\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + /* available chunk space for payload = total chunk size minus header + * marker byte and header code byte */ + if (tcm_hcd->rd_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->rd_chunk_size - 2; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = tcm_hcd->read_length; + + LOCK_BUFFER(tcm_hcd->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + tcm_hcd->in.buf[offset] = MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->temp, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->temp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->temp.buf, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + marker = tcm_hcd->temp.buf[0]; + code = tcm_hcd->temp.buf[1]; + + if (marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header marker (0x%02x)\n", + marker); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return -EIO; + } + + if (code != STATUS_CONTINUED_READ) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + code); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return -EIO; + } + + retval = secure_memcpy(&tcm_hcd->in.buf[offset], + tcm_hcd->in.buf_size - offset, + &tcm_hcd->temp.buf[2], + tcm_hcd->temp.buf_size - 2, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + offset += xfer_length; + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + + return 0; +} + +/** + * syna_tcm_raw_read() - retrieve specific number of data bytes from device + * + * @tcm_hcd: handle of core module + * @in_buf: buffer for storing data retrieved from device + * @length: number of bytes to retrieve from device + * + * Read transactions are carried out until the specific number of data bytes are + * retrieved from the device and stored in in_buf. + */ +static int syna_tcm_raw_read(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length) +{ + int retval; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----length:%d\n", length); + + if (length < 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid length information\n"); + return -EINVAL; + } + + /* minus header marker byte and header code byte */ + remaining_length = length - 2; + + /* available chunk space for data = total chunk size minus header marker + * byte and header code byte */ + if (tcm_hcd->rd_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->rd_chunk_size - 2; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = 0; + + LOCK_BUFFER(tcm_hcd->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + in_buf[offset] = MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->temp, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->temp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->temp.buf, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + code = tcm_hcd->temp.buf[1]; + + if (idx == 0) { + retval = secure_memcpy(&in_buf[0], + length, + &tcm_hcd->temp.buf[0], + tcm_hcd->temp.buf_size, + xfer_length + 2); + } else { + if (code != STATUS_CONTINUED_READ) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + code); + UNLOCK_BUFFER(tcm_hcd->temp); + return -EIO; + } + + retval = secure_memcpy(&in_buf[offset], + length - offset, + &tcm_hcd->temp.buf[2], + tcm_hcd->temp.buf_size - 2, + xfer_length); + } + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + if (idx == 0) + offset += (xfer_length + 2); + else + offset += xfer_length; + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->temp); + + return 0; +} + +/** + * syna_tcm_raw_write() - write command/data to device without receiving + * response + * + * @tcm_hcd: handle of core module + * @command: command to send to device + * @data: data to send to device + * @length: length of data in bytes + * + * A command and its data, if any, are sent to the device. + */ +static int syna_tcm_raw_write(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + + remaining_length = length; + + /* available chunk space for data = total chunk size minus command + * byte */ + if (tcm_hcd->wr_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->wr_chunk_size - 1; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + LOCK_BUFFER(tcm_hcd->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->out, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->out.buf\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + + if (idx == 0) + tcm_hcd->out.buf[0] = command; + else + tcm_hcd->out.buf[0] = CMD_CONTINUE_WRITE; + + if (xfer_length) { + retval = secure_memcpy(&tcm_hcd->out.buf[1], + tcm_hcd->out.buf_size - 1, + &data[idx * chunk_space], + remaining_length, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + } + + retval = syna_tcm_write(tcm_hcd, + tcm_hcd->out.buf, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to device\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->out); + + return 0; +} + +/** + * syna_tcm_read_message() - read message from device + * + * @tcm_hcd: handle of core module + * @in_buf: buffer for storing data in raw read mode + * @length: length of data in bytes in raw read mode + * + * If in_buf is not NULL, raw read mode is used and syna_tcm_raw_read() is + * called. Otherwise, a message including its entire payload is retrieved from + * the device and dispatched to the appropriate destination. + */ +static int syna_tcm_read_message(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length) +{ + int retval; + bool retry; + unsigned int total_length; + struct syna_tcm_message_header *header; + int retry_cnt = 0; + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + if (in_buf != NULL) { + retval = syna_tcm_raw_read(tcm_hcd, in_buf, length); + goto exit; + } + + retry = true; + +retry: + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->in.buf, + tcm_hcd->read_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->in); + if (retry) { + usleep_range(READ_RETRY_US_MIN, READ_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + header = (struct syna_tcm_message_header *)tcm_hcd->in.buf; + + + if (header->marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header marker (0x%02x)\n", + header->marker); + UNLOCK_BUFFER(tcm_hcd->in); + retval = -ENXIO; + if (retry) { + usleep_range(READ_RETRY_US_MIN, READ_RETRY_US_MAX); + //retry = false; + if(retry_cnt < 20) + { + retry_cnt++; + }else{ + retry_cnt = 0; + retry = false; + } + goto retry; + } + goto exit; + } + + tcm_hcd->status_report_code = header->code; + + tcm_hcd->payload_length = le2_to_uint(header->length); + + LOGD(tcm_hcd->pdev->dev.parent, + "Status report code = 0x%02x\n", + tcm_hcd->status_report_code); + + LOGD(tcm_hcd->pdev->dev.parent, + "Payload length = %d\n", + tcm_hcd->payload_length); + + if (tcm_hcd->status_report_code <= STATUS_ERROR || + tcm_hcd->status_report_code == STATUS_INVALID) { + switch (tcm_hcd->status_report_code) { + case STATUS_OK: + break; + case STATUS_CONTINUED_READ: + LOGD(tcm_hcd->pdev->dev.parent, + "Out-of-sync continued read\n"); + case STATUS_IDLE: + case STATUS_BUSY: + tcm_hcd->payload_length = 0; + UNLOCK_BUFFER(tcm_hcd->in); + retval = 0; + goto exit; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect Status code (0x%02x)\n", + tcm_hcd->status_report_code); + if (tcm_hcd->status_report_code == STATUS_INVALID) { + if (retry) { + usleep_range(READ_RETRY_US_MIN, + READ_RETRY_US_MAX); + retry = false; + goto retry; + } else { + tcm_hcd->payload_length = 0; + } + } + } + } + + total_length = MESSAGE_HEADER_SIZE + tcm_hcd->payload_length + 1; + +#ifdef PREDICTIVE_READING + if (total_length <= tcm_hcd->read_length) { + goto check_padding; + } else if (total_length - 1 == tcm_hcd->read_length) { + tcm_hcd->in.buf[total_length - 1] = MESSAGE_PADDING; + goto check_padding; + } +#else + if (tcm_hcd->payload_length == 0) { + tcm_hcd->in.buf[total_length - 1] = MESSAGE_PADDING; + goto check_padding; + } +#endif + + UNLOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_continued_read(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do continued read\n"); + goto exit; + }; + + LOCK_BUFFER(tcm_hcd->in); + + tcm_hcd->in.buf[0] = MESSAGE_MARKER; + tcm_hcd->in.buf[1] = tcm_hcd->status_report_code; + tcm_hcd->in.buf[2] = (unsigned char)tcm_hcd->payload_length; + tcm_hcd->in.buf[3] = (unsigned char)(tcm_hcd->payload_length >> 8); + +check_padding: + if (tcm_hcd->in.buf[total_length - 1] != MESSAGE_PADDING) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect message padding byte (0x%02x)\n", + tcm_hcd->in.buf[total_length - 1]); + UNLOCK_BUFFER(tcm_hcd->in); + retval = -EIO; + goto exit; + } + + UNLOCK_BUFFER(tcm_hcd->in); + +#ifdef PREDICTIVE_READING + total_length = MAX(total_length, MIN_READ_LENGTH); + tcm_hcd->read_length = MIN(total_length, tcm_hcd->rd_chunk_size); + if (tcm_hcd->rd_chunk_size == 0) + tcm_hcd->read_length = total_length; +#endif + if (tcm_hcd->is_detected) + syna_tcm_dispatch_message(tcm_hcd); + + retval = 0; + +exit: + if (retval < 0) { + if (atomic_read(&tcm_hcd->command_status) == CMD_BUSY) { + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + complete(&response_complete); + } + } + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + return retval; +} + +/** + * syna_tcm_write_message() - write message to device and receive response + * + * @tcm_hcd: handle of core module + * @command: command to send to device + * @payload: payload of command + * @length: length of payload in bytes + * @resp_buf: buffer for storing command response + * @resp_buf_size: size of response buffer in bytes + * @resp_length: length of command response in bytes + * @response_code: status code returned in command response + * @polling_delay_ms: delay time after sending command before resuming polling + * + * If resp_buf is NULL, raw write mode is used and syna_tcm_raw_write() is + * called. Otherwise, a command and its payload, if any, are sent to the device + * and the response to the command generated by the device is read in. + */ +static int syna_tcm_write_message(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *payload, + unsigned int length, unsigned char **resp_buf, + unsigned int *resp_buf_size, unsigned int *resp_length, + unsigned char *response_code, unsigned int polling_delay_ms) +{ + int retval; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int command_status; + bool is_romboot_hdl = (command == CMD_ROMBOOT_DOWNLOAD) ? true : false; + bool is_hdl_reset = (command == CMD_RESET) && (tcm_hcd->in_hdl_mode); + + if (response_code != NULL) + *response_code = STATUS_INVALID; + + if (!tcm_hcd->do_polling && current->pid == tcm_hcd->isr_pid) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid execution context\n"); + return -EINVAL; + } + + mutex_lock(&tcm_hcd->command_mutex); + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + if (resp_buf == NULL) { + retval = syna_tcm_raw_write(tcm_hcd, command, payload, length); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + if (tcm_hcd->do_polling && polling_delay_ms) { + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + } + + atomic_set(&tcm_hcd->command_status, CMD_BUSY); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + reinit_completion(&response_complete); +#else + INIT_COMPLETION(response_complete); +#endif + + tcm_hcd->command = command; + + LOCK_BUFFER(tcm_hcd->resp); + + tcm_hcd->resp.buf = *resp_buf; + tcm_hcd->resp.buf_size = *resp_buf_size; + tcm_hcd->resp.data_length = 0; + + UNLOCK_BUFFER(tcm_hcd->resp); + + /* adding two length bytes as part of payload */ + remaining_length = length + 2; + + /* available chunk space for payload = total chunk size minus command + * byte */ + if (tcm_hcd->wr_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->wr_chunk_size - 1; + + if (is_romboot_hdl) { + if (WR_CHUNK_SIZE) { + chunk_space = WR_CHUNK_SIZE - 1; + chunk_space = chunk_space - + (chunk_space % ROMBOOT_DOWNLOAD_UNIT); + } else { + chunk_space = remaining_length; + } + } + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + LOGD(tcm_hcd->pdev->dev.parent, + "Command = 0x%02x\n", + command); + + LOCK_BUFFER(tcm_hcd->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->out, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->out.buf\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + if (idx == 0) { + tcm_hcd->out.buf[0] = command; + tcm_hcd->out.buf[1] = (unsigned char)length; + tcm_hcd->out.buf[2] = (unsigned char)(length >> 8); + + if (xfer_length > 2) { + retval = secure_memcpy(&tcm_hcd->out.buf[3], + tcm_hcd->out.buf_size - 3, + payload, + remaining_length - 2, + xfer_length - 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + } + } else { + tcm_hcd->out.buf[0] = CMD_CONTINUE_WRITE; + + retval = secure_memcpy(&tcm_hcd->out.buf[1], + tcm_hcd->out.buf_size - 1, + &payload[idx * chunk_space - 2], + remaining_length, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + } + +/* LOGE(tcm_hcd->pdev->dev.parent, + "Before cmd = 0x%02x\n", command); */ + retval = syna_tcm_write(tcm_hcd, + tcm_hcd->out.buf, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to device\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } +/* LOGE(tcm_hcd->pdev->dev.parent, + "After cmd = 0x%02x\n", command); */ + remaining_length -= xfer_length; + + if (chunks > 1) + usleep_range(WRITE_DELAY_US_MIN, WRITE_DELAY_US_MAX); + } + + UNLOCK_BUFFER(tcm_hcd->out); + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + if (is_hdl_reset) + goto exit; + + if (tcm_hcd->do_polling && polling_delay_ms) { + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(polling_delay_ms)); + } + + retval = wait_for_completion_timeout(&response_complete, + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for response (command 0x%02x), irq: %s, ATTN: %d\n", + tcm_hcd->command, + (tcm_hcd->irq_enabled) ? STR(TRUE) : STR(FALSE), + gpio_get_value(tcm_hcd->hw_if->bdata->irq_gpio)); + retval = -ETIME; + goto exit; + } + + command_status = atomic_read(&tcm_hcd->command_status); + if (command_status != CMD_IDLE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get valid response (command 0x%02x)\n", + tcm_hcd->command); + retval = -EIO; + goto exit; + } + + LOCK_BUFFER(tcm_hcd->resp); + + if (tcm_hcd->response_code != STATUS_OK) { + if (tcm_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Error code = 0x%02x (command 0x%02x)\n", + tcm_hcd->resp.buf[0], tcm_hcd->command); + } + retval = -EIO; + } else { + retval = 0; + } + + *resp_buf = tcm_hcd->resp.buf; + *resp_buf_size = tcm_hcd->resp.buf_size; + *resp_length = tcm_hcd->resp.data_length; + + if (response_code != NULL) + *response_code = tcm_hcd->response_code; + + UNLOCK_BUFFER(tcm_hcd->resp); + +exit: + tcm_hcd->command = CMD_NONE; + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + + mutex_unlock(&tcm_hcd->command_mutex); + + return retval; +} + +static int syna_tcm_wait_hdl(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + msleep(HOST_DOWNLOAD_WAIT_MS); + + if (!atomic_read(&tcm_hcd->host_downloading)) + return 0; + + retval = wait_event_interruptible_timeout(tcm_hcd->hdl_wq, + !atomic_read(&tcm_hcd->host_downloading), + msecs_to_jiffies(HOST_DOWNLOAD_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for completion of host download\n"); + atomic_set(&tcm_hcd->host_downloading, 0); + retval = -EIO; + } else { + retval = 0; + } + + return retval; +} + +static void syna_tcm_check_hdl(struct syna_tcm_hcd *tcm_hcd, unsigned char id) +{ + struct syna_tcm_module_handler *mod_handler; + + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = NULL; + tcm_hcd->report.buffer.buf_size = 0; + tcm_hcd->report.buffer.data_length = 0; + tcm_hcd->report.id = id; + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->syncbox)) + mod_handler->mod_cb->syncbox(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + return; +} + +#ifdef WATCHDOG_SW +static void syna_tcm_update_watchdog(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + + if (!tcm_hcd->watchdog.run) { + tcm_hcd->watchdog.count = 0; + return; + } + + if (en) { + queue_delayed_work(tcm_hcd->watchdog.workqueue, + &tcm_hcd->watchdog.work, + msecs_to_jiffies(WATCHDOG_DELAY_MS)); + } else { + tcm_hcd->watchdog.count = 0; + } + + return; +} + +static void syna_tcm_watchdog_work(struct work_struct *work) +{ + int retval; + unsigned char marker; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct syna_tcm_watchdog *watchdog = + container_of(delayed_work, struct syna_tcm_watchdog, + work); + struct syna_tcm_hcd *tcm_hcd = + container_of(watchdog, struct syna_tcm_hcd, watchdog); + + if (mutex_is_locked(&tcm_hcd->rw_ctrl_mutex)) + goto exit; + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + retval = syna_tcm_read(tcm_hcd, + &marker, + 1); + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + if (retval < 0 || marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + + tcm_hcd->watchdog.count++; + + if (tcm_hcd->watchdog.count >= WATCHDOG_TRIGGER_COUNT) { + retval = tcm_hcd->reset_n_reinit(tcm_hcd, true, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset and reinit\n"); + } + tcm_hcd->watchdog.count = 0; + } + } + +exit: + queue_delayed_work(tcm_hcd->watchdog.workqueue, + &tcm_hcd->watchdog.work, + msecs_to_jiffies(WATCHDOG_DELAY_MS)); + + return; +} +#endif + +static void syna_tcm_polling_work(struct work_struct *work) +{ + int retval; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct syna_tcm_hcd *tcm_hcd = + container_of(delayed_work, struct syna_tcm_hcd, + polling_work); + + if (!tcm_hcd->do_polling) + return; + + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + if (retval == -ENXIO && tcm_hcd->hw_if->bus_io->type == BUS_SPI) + syna_tcm_check_hdl(tcm_hcd, REPORT_HDL_F35); + } + + if (!(tcm_hcd->in_suspend && retval < 0)) { + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(POLLING_DELAY_MS)); + } + + return; +} + +static irqreturn_t syna_tcm_isr(int irq, void *data) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = data; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (unlikely(gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state)) + goto exit; + + tcm_hcd->isr_pid = current->pid; + + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + + if (retval < 0) { + if (tcm_hcd->sensor_type == TYPE_F35) + syna_tcm_check_hdl(tcm_hcd, REPORT_HDL_F35); + else + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + } + +exit: + return IRQ_HANDLED; +} + +static int syna_tcm_enable_irq(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + static bool irq_freed = true; + + mutex_lock(&tcm_hcd->irq_en_mutex); + + if (en) { + if (tcm_hcd->irq_enabled) { + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt already enabled\n"); + retval = 0; + goto exit; + } + + if (bdata->irq_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid IRQ GPIO\n"); + retval = -EINVAL; + goto queue_polling_work; + } + + if (irq_freed) { + retval = request_threaded_irq(tcm_hcd->irq, NULL, + syna_tcm_isr, bdata->irq_flags, + PLATFORM_DRIVER_NAME, tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create interrupt thread\n"); + } + } else { + enable_irq(tcm_hcd->irq); + retval = 0; + } + +queue_polling_work: + if (retval < 0) { +#ifdef FALL_BACK_ON_POLLING + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(POLLING_DELAY_MS)); + tcm_hcd->do_polling = true; + retval = 0; +#endif + } + + if (retval < 0) + goto exit; + else + msleep(ENABLE_IRQ_DELAY_MS); + } else { + if (!tcm_hcd->irq_enabled) { + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt already disabled\n"); + retval = 0; + goto exit; + } + + if (bdata->irq_gpio >= 0) { + if (ns) { + disable_irq_nosync(tcm_hcd->irq); + } else { + disable_irq(tcm_hcd->irq); + free_irq(tcm_hcd->irq, tcm_hcd); + } + irq_freed = !ns; + } + + if (ns) { + cancel_delayed_work(&tcm_hcd->polling_work); + } else { + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + } + + tcm_hcd->do_polling = false; + } + + retval = 0; + +exit: + if (retval == 0) + tcm_hcd->irq_enabled = en; + + mutex_unlock(&tcm_hcd->irq_en_mutex); + + return retval; +} + +static int syna_tcm_set_gpio(struct syna_tcm_hcd *tcm_hcd, int gpio, + bool config, int dir, int state) +{ + int retval; + char label[16]; + + if (config) { + retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set GPIO label\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, "label----%s:\n",label); + + retval = gpio_request(gpio, label); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to request GPIO %d\n", + gpio); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set GPIO %d direction\n", + gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return 0; +} + +static int syna_tcm_config_gpio(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->irq_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, + true, 0, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure interrupt GPIO\n"); + goto err_set_gpio_irq; + } + } + + if (bdata->power_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, + true, 1, !bdata->power_on_state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure power GPIO\n"); + goto err_set_gpio_power; + } + } + + if (bdata->reset_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, + true, 1, !bdata->reset_on_state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure reset GPIO\n"); + goto err_set_gpio_reset; + } + } + + if (bdata->power_gpio >= 0) { + gpio_set_value(bdata->power_gpio, bdata->power_on_state); + msleep(bdata->power_delay_ms); + } + + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + } + + return 0; + +err_set_gpio_reset: + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + +err_set_gpio_power: + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + +err_set_gpio_irq: + return retval; +} + +static int syna_tcm_enable_regulator(struct syna_tcm_hcd *tcm_hcd, bool on) +{ + int ret = 0; + LOGE(tcm_hcd->pdev->dev.parent, "syna_tcm_enable_regulator\n"); + + if (on) { + ret = regulator_enable(tcm_hcd->avdd); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to enable avdd:%d", ret); + return ret; + } + usleep_range(3000, 3100); + ret = regulator_enable(tcm_hcd->iovdd); + if (ret) { + regulator_disable(tcm_hcd->avdd); + LOGE(tcm_hcd->pdev->dev.parent, "Failed to enable iovdd:%d", ret); + return ret; + } + + return 0; + } + + /*power off process */ + ret = regulator_disable(tcm_hcd->iovdd); + if (ret) + LOGE(tcm_hcd->pdev->dev.parent, "Failed to disable iovdd:%d", ret); + + ret = regulator_disable(tcm_hcd->avdd); + if (!ret) + LOGE(tcm_hcd->pdev->dev.parent, "regulator disable SUCCESS"); + else + LOGE(tcm_hcd->pdev->dev.parent, "Failed to disable analog power:%d", ret); + + return ret; +} + +static int syna_tcm_regulator_init(struct syna_tcm_hcd *tcm_hcd) +{ + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + struct device *dev = tcm_hcd->pdev->dev.parent; + int ret = 0; + + + LOGN(tcm_hcd->pdev->dev.parent, "Power init"); + if (strlen(bdata->avdd_name)) { + tcm_hcd->avdd = devm_regulator_get(dev, + bdata->avdd_name); + if (IS_ERR_OR_NULL(tcm_hcd->avdd)) { + ret = PTR_ERR(tcm_hcd->avdd); + LOGE(tcm_hcd->pdev->dev.parent,"Failed to get regulator avdd:%d", ret); + tcm_hcd->avdd = NULL; + return ret; + } + } else { + LOGE(tcm_hcd->pdev->dev.parent, "Avdd name is NULL"); + } + ret = regulator_set_voltage(tcm_hcd->avdd, 3200000, 3200000); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, "regulator_set_voltage failed %d\n", ret); + return ret; + } + + if (strlen(bdata->iovdd_name)) { + tcm_hcd->iovdd = devm_regulator_get(dev, + bdata->iovdd_name); + if (IS_ERR_OR_NULL(tcm_hcd->iovdd)) { + ret = PTR_ERR(tcm_hcd->iovdd); + LOGE(tcm_hcd->pdev->dev.parent, "Failed to get regulator iovdd:%d", ret); + tcm_hcd->iovdd = NULL; + } + } else { + LOGE(tcm_hcd->pdev->dev.parent, "iovdd name is NULL"); + } + ret = regulator_set_voltage(tcm_hcd->iovdd, 1800000, 1800000); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, "regulator_set_voltage failed %d\n", ret); + return ret; + } + + return ret; +} + +static void syna_tcm_esd_recovery(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + LOGI(tcm_hcd->pdev->dev.parent, "syna_tcm_esd_recovery enter!\n"); + mutex_lock(&tcm_hcd->esd_recovery_mutex); + + /* power down */ + syna_tcm_enable_regulator(tcm_hcd, false); + + /* power up */ + retval = syna_tcm_enable_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable regulators, retval = %d\n", retval); + } + + /* hardware reset */ + retval = tcm_hcd->reset_n_reinit(tcm_hcd, true, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset and reinit, retval = %d\n", retval); + } + + mutex_unlock(&tcm_hcd->esd_recovery_mutex); + return; +} + +static int syna_tcm_get_app_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned int timeout; + + timeout = APP_STATUS_POLL_TIMEOUT_MS; + + resp_buf = NULL; + resp_buf_size = 0; + +get_app_info: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_APPLICATION_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_APPLICATION_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->app_info, + sizeof(tcm_hcd->app_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->app_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application info\n"); + goto exit; + } + + tcm_hcd->app_status = le2_to_uint(tcm_hcd->app_info.status); + + if (tcm_hcd->app_status == APP_STATUS_BOOTING || + tcm_hcd->app_status == APP_STATUS_UPDATING) { + if (timeout > 0) { + msleep(APP_STATUS_POLL_MS); + timeout -= APP_STATUS_POLL_MS; + goto get_app_info; + } + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_get_boot_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_BOOT_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_BOOT_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->boot_info, + sizeof(tcm_hcd->boot_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->boot_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy boot info\n"); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_get_romboot_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_ROMBOOT_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_ROMBOOT_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->romboot_info, + sizeof(tcm_hcd->romboot_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->romboot_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy boot info\n"); + goto exit; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "version = %d\n", tcm_hcd->romboot_info.version); + + LOGD(tcm_hcd->pdev->dev.parent, + "status = 0x%02x\n", tcm_hcd->romboot_info.status); + + LOGD(tcm_hcd->pdev->dev.parent, + "version = 0x%02x 0x%02x\n", + tcm_hcd->romboot_info.asic_id[0], + tcm_hcd->romboot_info.asic_id[1]); + + LOGD(tcm_hcd->pdev->dev.parent, + "write_block_size_words = %d\n", + tcm_hcd->romboot_info.write_block_size_words); + + LOGD(tcm_hcd->pdev->dev.parent, + "max_write_payload_size = %d\n", + tcm_hcd->romboot_info.max_write_payload_size[0] | + tcm_hcd->romboot_info.max_write_payload_size[1] << 8); + + LOGD(tcm_hcd->pdev->dev.parent, + "last_reset_reason = 0x%02x\n", + tcm_hcd->romboot_info.last_reset_reason); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_identify(struct syna_tcm_hcd *tcm_hcd, bool id) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned int max_write_size; + + resp_buf = NULL; + resp_buf_size = 0; + + mutex_lock(&tcm_hcd->identify_mutex); + + if (!id) + goto get_info; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_IDENTIFY, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_IDENTIFY)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->id_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + goto exit; + } + + tcm_hcd->packrat_number = le4_to_uint(tcm_hcd->id_info.build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + + LOGN(tcm_hcd->pdev->dev.parent, + "Firmware build id = %d\n", tcm_hcd->packrat_number); + +get_info: + switch (tcm_hcd->id_info.mode) { + case MODE_APPLICATION_FIRMWARE: + case MODE_HOSTDOWNLOAD_FIRMWARE: + + retval = syna_tcm_get_app_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get application info\n"); + goto exit; + } + break; + case MODE_BOOTLOADER: + case MODE_TDDI_BOOTLOADER: + + LOGD(tcm_hcd->pdev->dev.parent, + "In bootloader mode\n"); + + retval = syna_tcm_get_boot_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get boot info\n"); + goto exit; + } + break; + case MODE_ROMBOOTLOADER: + + LOGD(tcm_hcd->pdev->dev.parent, + "In rombootloader mode\n"); + + retval = syna_tcm_get_romboot_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get application info\n"); + goto exit; + } + break; + default: + break; + } + + retval = 0; + +exit: + mutex_unlock(&tcm_hcd->identify_mutex); + + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_production_test_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + bool retry; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + retry = true; + + resp_buf = NULL; + resp_buf_size = 0; + +retry: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ENTER_PRODUCTION_TEST_MODE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ENTER_PRODUCTION_TEST_MODE)); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_PRODUCTIONTEST_FIRMWARE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run production test firmware\n"); + if (retry) { + retry = false; + goto retry; + } + retval = -EINVAL; + goto exit; + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_application_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + bool retry; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + retry = true; + + resp_buf = NULL; + resp_buf_size = 0; + +retry: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_APPLICATION_FIRMWARE)); + goto exit; + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware (boot status = 0x%02x)\n", + tcm_hcd->boot_info.status); + if (retry) { + retry = false; + goto retry; + } + retval = -EINVAL; + goto exit; + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_bootloader_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned char command; + + resp_buf = NULL; + resp_buf_size = 0; + command = (tcm_hcd->id_info.mode == MODE_ROMBOOTLOADER) ? + CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE : + CMD_RUN_BOOTLOADER_FIRMWARE; + + retval = tcm_hcd->write_message(tcm_hcd, + command, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + if (tcm_hcd->id_info.mode == MODE_ROMBOOTLOADER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE)); + } else { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_BOOTLOADER_FIRMWARE)); + } + goto exit; + } + + if (command != CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE) { + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (IS_FW_MODE(tcm_hcd->id_info.mode)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + retval = -EINVAL; + goto exit; + } + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_switch_mode(struct syna_tcm_hcd *tcm_hcd, + enum firmware_mode mode) +{ + int retval; + + mutex_lock(&tcm_hcd->reset_mutex); + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + switch (mode) { + case FW_MODE_BOOTLOADER: + retval = syna_tcm_run_bootloader_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to bootloader mode\n"); + goto exit; + } + break; + case FW_MODE_APPLICATION: + retval = syna_tcm_run_application_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to application mode\n"); + goto exit; + } + break; + case FW_MODE_PRODUCTION_TEST: + retval = syna_tcm_run_production_test_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to production test mode\n"); + goto exit; + } + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid firmware mode\n"); + retval = -EINVAL; + goto exit; + } + + retval = 0; + +exit: +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + mutex_unlock(&tcm_hcd->reset_mutex); + + return retval; +} + +static int syna_tcm_get_dynamic_config(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short *value) +{ + int retval; + unsigned char out_buf; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + out_buf = (unsigned char)id; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_DYNAMIC_CONFIG, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s, id = %d\n", + STR(CMD_GET_DYNAMIC_CONFIG), id); + goto exit; + } + + if (resp_length < 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto exit; + } + + *value = (unsigned short)le2_to_uint(resp_buf); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_set_dynamic_config(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short value) +{ + int retval; + unsigned char out_buf[3]; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + out_buf[0] = (unsigned char)id; + out_buf[1] = (unsigned char)value; + out_buf[2] = (unsigned char)(value >> 8); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SET_DYNAMIC_CONFIG, + out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_SET_DYNAMIC_CONFIG)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static void syna_tcm_set_charge_status(void) +{ + int retval = 0; + unsigned short val = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + val = tcm_hcd->charger_connected & 0x01; /* Default Value: 0, disconnected: 1, connected */ + if (tcm_hcd->in_sleep) { + LOGI(tcm_hcd->pdev->dev.parent,"in sleep, don't set charge bit [%d]\n", val); + goto exit; + } + + /* set dynamic config */ + LOGI(tcm_hcd->pdev->dev.parent,"set charger_connected, value = 0x%02x\n", val); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_CHARGER_CONNECTED, val); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "set charger_connected failed\n"); + } + +exit: + return; +} + +static int syna_tcm_get_data_location(struct syna_tcm_hcd *tcm_hcd, + enum flash_area area, unsigned int *addr, unsigned int *length) +{ + int retval; + unsigned char out_buf; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + switch (area) { + case CUSTOM_LCM: + out_buf = LCM_DATA; + break; + case CUSTOM_OEM: + out_buf = OEM_DATA; + break; + case PPDT: + out_buf = PPDT_DATA; + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid flash area\n"); + return -EINVAL; + } + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_DATA_LOCATION, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_DATA_LOCATION)); + goto exit; + } + + if (resp_length != 4) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto exit; + } + + *addr = le2_to_uint(&resp_buf[0]); + *length = le2_to_uint(&resp_buf[2]); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_sleep(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + int retval; + unsigned char command; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + command = en ? CMD_ENTER_DEEP_SLEEP : CMD_EXIT_DEEP_SLEEP; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + command, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + en ? + STR(CMD_ENTER_DEEP_SLEEP) : + STR(CMD_EXIT_DEEP_SLEEP)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RESET, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + bdata->reset_delay_ms); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RESET)); + } + + return retval; +} + +static int syna_tcm_reset_and_reinit(struct syna_tcm_hcd *tcm_hcd, + bool hw, bool update_wd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + struct syna_tcm_module_handler *mod_handler; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + resp_buf = NULL; + resp_buf_size = 0; + + mutex_lock(&tcm_hcd->reset_mutex); + +#ifdef WATCHDOG_SW + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + if (hw) { + if (bdata->reset_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Hardware reset unavailable\n"); + retval = -EINVAL; + goto exit; + } + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + } else { + retval = syna_tcm_reset(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + goto exit; + } + } + + /* for hdl, the remaining re-init process will be done */ + /* in the helper thread, so wait for the completion here */ + if (tcm_hcd->in_hdl_mode) { + mutex_unlock(&tcm_hcd->reset_mutex); + kfree(resp_buf); + + retval = syna_tcm_wait_hdl(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for completion of host download\n"); + return retval; + } + +#ifdef WATCHDOG_SW + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + return 0; + } + + msleep(bdata->reset_delay_ms); + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (IS_FW_MODE(tcm_hcd->id_info.mode)) + goto get_features; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGN(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_APPLICATION_FIRMWARE)); + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + +get_features: + LOGN(tcm_hcd->pdev->dev.parent, + "Firmware mode = 0x%02x\n", + tcm_hcd->id_info.mode); + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode)) { + LOGN(tcm_hcd->pdev->dev.parent, + "Boot status = 0x%02x\n", + tcm_hcd->boot_info.status); + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode)) + goto dispatch_reinit; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_FEATURES, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGN(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_FEATURES)); + } else { + retval = secure_memcpy((unsigned char *)&tcm_hcd->features, + sizeof(tcm_hcd->features), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->features), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy feature description\n"); + } + } + +dispatch_reinit: + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->reinit)) + mod_handler->mod_cb->reinit(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + retval = 0; + +exit: +#ifdef WATCHDOG_SW + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + mutex_unlock(&tcm_hcd->reset_mutex); + + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_rezero(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_REZERO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_REZERO)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static void syna_tcm_helper_work(struct work_struct *work) +{ + int retval; + unsigned char task; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_helper *helper = + container_of(work, struct syna_tcm_helper, work); + struct syna_tcm_hcd *tcm_hcd = + container_of(helper, struct syna_tcm_hcd, helper); + + task = atomic_read(&helper->task); + + switch (task) { + + /* this helper can help to run the application firmware */ + case HELP_RUN_APPLICATION_FIRMWARE: + mutex_lock(&tcm_hcd->reset_mutex); + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + retval = syna_tcm_run_application_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to application mode\n"); + } +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + mutex_unlock(&tcm_hcd->reset_mutex); + break; + + /* the reinit helper is used to notify all installed modules to */ + /* do the re-initialization process, since the HDL is completed */ + case HELP_SEND_REINIT_NOTIFICATION: + mutex_lock(&tcm_hcd->reset_mutex); + + /* do identify to ensure application firmware is running */ + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application firmware is not running\n"); + mutex_unlock(&tcm_hcd->reset_mutex); + break; + } + + /* init the touch reporting here */ + /* since the HDL is completed */ + retval = touch_reinit(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to initialze touch reporting\n"); + break; + } + + mutex_lock(&mod_pool.mutex); + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->reinit)) + mod_handler->mod_cb->reinit(tcm_hcd); + } + } + mutex_unlock(&mod_pool.mutex); + mutex_unlock(&tcm_hcd->reset_mutex); + wake_up_interruptible(&tcm_hcd->hdl_wq); + break; + + /* this helper is used to reinit the touch reporting */ + case HELP_TOUCH_REINIT: + retval = touch_reinit(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to re-initialze touch reporting\n"); + } +#ifdef CONFIG_FACTORY_BUILD + syna_tcm_set_cur_value(Touch_Fod_Enable, FOD_STATUS_UNLOCKING); +#endif + + /* resend the charger bit after reset */ + syna_tcm_set_charge_status(); + + /* reinit mode parameters after IC reset, to avoid losting mode setting in FW */ + syna_tcm_reinit_mode(); + break; + + /* this helper is used to trigger a romboot hdl */ + case HELP_SEND_ROMBOOT_HDL: + syna_tcm_check_hdl(tcm_hcd, REPORT_HDL_ROMBOOT); + break; + default: + break; + } + + atomic_set(&helper->task, HELP_NONE); + + return; +} + +#if defined(CONFIG_PM) || defined(CONFIG_FB) +static int syna_tcm_pm_resume(struct device *dev) +{ + int retval; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + if (!tcm_hcd->in_suspend){ + LOGN(tcm_hcd->pdev->dev.parent, "tp is in resume state,-----exit-----%s\n", __func__); + return 0; + } + + tcm_hcd->in_suspending = false; + + if (tcm_hcd->in_hdl_mode) { + if (!tcm_hcd->wakeup_gesture_enabled) { + tcm_hcd->enable_irq(tcm_hcd, true, NULL); + retval = syna_tcm_wait_hdl(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for completion of host download\n"); + goto exit; + } + goto mod_resume; + } + } else { + if (!tcm_hcd->wakeup_gesture_enabled || tcm_hcd->in_sleep) + tcm_hcd->enable_irq(tcm_hcd, true, NULL); + +#ifdef RESET_ON_RESUME + msleep(RESET_ON_RESUME_DELAY_MS); + goto do_reset; +#endif + } + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Identifying mode = 0x%02x\n", + tcm_hcd->id_info.mode); + goto do_reset; + } + + retval = tcm_hcd->sleep(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to exit deep sleep\n"); + goto exit; + } + + if ((tcm_hcd->fod_enabled) && (tcm_hcd->fod_finger)) + goto mod_resume; + + retval = syna_tcm_rezero(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to rezero\n"); + goto exit; + } + + goto mod_resume; + +do_reset: + retval = tcm_hcd->reset_n_reinit(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset and reinit\n"); + goto exit; + } + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Identifying mode = 0x%02x\n", + tcm_hcd->id_info.mode); + retval = 0; + goto exit; + } + +mod_resume: + touch_resume(tcm_hcd); + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->resume)) + mod_handler->mod_cb->resume(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + retval = 0; + +exit: + tcm_hcd->in_sleep = false; + tcm_hcd->in_suspend = false; + + return retval; +} + +static int syna_tcm_pm_suspend(struct device *dev) +{ + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + if (tcm_hcd->in_suspend){ + LOGN(tcm_hcd->pdev->dev.parent, "tp is in suspend state-----exit-----%s\n", __func__); + return 0; + } + + touch_suspend(tcm_hcd); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->suspend)) + mod_handler->mod_cb->suspend(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + if (!tcm_hcd->wakeup_gesture_enabled) + tcm_hcd->enable_irq(tcm_hcd, false, true); + + tcm_hcd->in_suspend = true; + touch_free_objects(tcm_hcd); + + /* Workaround to disappear ghost pointer */ + /* touch_flush_slots(tcm_hcd); */ + + tcm_hcd->in_suspending = false; + + return 0; +} +#endif + +static int syna_tcm_early_suspend(struct device *dev) +{ + int retval; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + if (tcm_hcd->in_suspend){ + LOGE(tcm_hcd->pdev->dev.parent,"enter:syna_tcm_early_suspend------1\n"); + return 0; + } + + tcm_hcd->in_suspending = true; + + LOGN(tcm_hcd->pdev->dev.parent,"enter:syna_tcm_early_suspend 2\n"); + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Identifying mode = 0x%02x\n", + tcm_hcd->id_info.mode); + return 0; + } + + if (tcm_hcd->fod_enabled == true || + tcm_hcd->aod_enable == true || + tcm_hcd->doubletap_enable == true) { + tcm_hcd->wakeup_gesture_enabled = WAKEUP_GESTURE; + } else + tcm_hcd->wakeup_gesture_enabled = false; + + if (!tcm_hcd->wakeup_gesture_enabled || tcm_hcd->nonui_status == 2) { + if (!tcm_hcd->in_sleep) { + retval = tcm_hcd->sleep(tcm_hcd, true); + if (retval < 0) { + tcm_hcd->in_sleep = false; + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter deep sleep\n"); + return retval; + } + tcm_hcd->in_sleep = true; + } + } + + if (tcm_hcd->palm_sensor_enable) { + tcm_hcd->palm_enable_status = 0; + update_palm_sensor_value(tcm_hcd->palm_enable_status); + } + + touch_early_suspend(tcm_hcd); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->early_suspend)) { + LOGE(tcm_hcd->pdev->dev.parent,"enter:syna_tcm_early_suspend------3\n"); + mod_handler->mod_cb->early_suspend(tcm_hcd); + } + } + } + + mutex_unlock(&mod_pool.mutex); + LOGN(tcm_hcd->pdev->dev.parent, "-----exit-----%s\n", __func__); + return 0; +} + +static void syna_tcm_resume_work(struct work_struct *work) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = + container_of(work, struct syna_tcm_hcd, resume_work); + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + retval = syna_tcm_pm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; + + /* resend the charger bit after resume */ + syna_tcm_set_charge_status(); + + LOGN(tcm_hcd->pdev->dev.parent, "-----exit-----%s\n", __func__); + + return; +} + +static void syna_tcm_early_suspend_work(struct work_struct *work) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = + container_of(work, struct syna_tcm_hcd, early_suspend_work); + + syna_tcm_palm_area_change_setting(1); + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev); + + LOGN(tcm_hcd->pdev->dev.parent, "-----exit-----%s\n", __func__); + + return; +} + +static void syna_tcm_suspend_work(struct work_struct *work) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = + container_of(work, struct syna_tcm_hcd, suspend_work); + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + retval = syna_tcm_pm_suspend(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready = 0; + + LOGN(tcm_hcd->pdev->dev.parent, "-----exit-----%s\n", __func__); + + return; +} + +int syna_tcm_fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + int retval; + int transition; + struct mi_disp_notifier *evdata = data; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + LOGD(tcm_hcd->pdev->dev.parent, + "enter: syna_tcm_fb_notifier_cb, tcm_hcd = %p\n", tcm_hcd); + + retval = 0; + if (evdata && evdata->data && tcm_hcd) { + transition = *(int *)(evdata->data); + if (atomic_read(&tcm_hcd->firmware_flashing) && + transition == MI_DISP_DPMS_POWERDOWN) { + + retval = wait_event_interruptible_timeout( + tcm_hcd->reflash_wq, + !atomic_read(&tcm_hcd->firmware_flashing), + msecs_to_jiffies(RESPONSE_TIMEOUT_MS) + ); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for completion of flashing firmware\n"); + atomic_set(&tcm_hcd->firmware_flashing, 0); + return -EIO; + } else { + retval = 0; + } + } + + flush_workqueue(tcm_hcd->event_wq); + if (action == MI_DISP_DPMS_EARLY_EVENT && (transition == MI_DISP_DPMS_POWERDOWN)) { + LOGN(tcm_hcd->pdev->dev.parent, "touch early_suspend by 0x%04x\n", transition); + queue_work(tcm_hcd->event_wq, &tcm_hcd->early_suspend_work); + } else if (action == MI_DISP_DPMS_EVENT) { + if (transition == MI_DISP_DPMS_POWERDOWN) { + LOGN(tcm_hcd->pdev->dev.parent, + "touch suspend by 0x%04x\n", transition); + queue_work(tcm_hcd->event_wq, &tcm_hcd->suspend_work); + } else if (transition == MI_DISP_DPMS_ON) { + LOGN(tcm_hcd->pdev->dev.parent, + "touch resume\n"); + queue_work(tcm_hcd->event_wq, &tcm_hcd->resume_work); + } + } + } + return 0; +} + +static struct notifier_block syna_tcm_noti_block = { + .notifier_call = syna_tcm_fb_notifier_cb, +}; + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE +static struct xiaomi_touch_interface *p_xiaomi_touch_interfaces = NULL; +static int syna_tcm_xiaomi_touchfeature_exit(struct syna_tcm_hcd *tcm_hcd) +{ + cancel_work_sync(&tcm_hcd->cmd_update_work); + cancel_work_sync(&tcm_hcd->grip_mode_work); + flush_workqueue(tcm_hcd->game_wq); + destroy_workqueue(tcm_hcd->game_wq); + + kfree(p_xiaomi_touch_interfaces); + p_xiaomi_touch_interfaces = NULL; + + return 0; +} + +/* TODO: this function can be called to set the report rate to FW */ +static void syna_tcm_set_report_rate_work(struct work_struct *work) +{ + int retval = 0; + unsigned short val = 0; + struct syna_tcm_hcd *tcm_hcd = + container_of(work, struct syna_tcm_hcd, set_report_rate_work); + + /* set dynamic config */ + val = tcm_hcd->report_rate_mode & 0x03; /* 0:default(180Hz), 1:240Hz 2: 180Hz,3:120Hz */ + LOGI(tcm_hcd->pdev->dev.parent,"set report rate, value = 0x%02x\n", val); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_SET_REPORT_RATE, val); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "set report rate failed\n"); + } + + return; +} + +static void syna_tcm_fod_work(struct work_struct *work) +{ + int retval = 0; + unsigned short val = 0; + struct syna_tcm_hcd *tcm_hcd = + container_of(work, struct syna_tcm_hcd, fod_work); + + val = (tcm_hcd->fod_enabled) ? 3 : 0; + LOGI(tcm_hcd->pdev->dev.parent,"set touch and hold enable control, value = 0x%02x\n", val); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_ENABLE_TOUCH_AND_HOLD, val); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "set touch and hold enable control failed\n"); + } + + return; +} + +static void syna_tcm_set_grip_rect(int *buf, struct syna_grip_zone *zone) +{ + int offset = 0; + unsigned char *out_buf = NULL; + unsigned int type = 0, pos = 0; + int x_start = 0, y_start = 0, x_end = 0, y_end = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + /* For grip mode, the format from framework is : + * mode: grip mode or other + * len: The num of the commond, rect_num * parameters_num_for_each_rect + * grip_type: dead grip, or edge grip or cornero grip + * grip_pos: which corner or which edge + * x start + * y start + * x end + * y end + * time + * node num + */ + type = *buf; + pos = *(buf + 1); + x_start = *(buf + 2); + y_start = *(buf + 3); + x_end = *(buf + 4); + y_end = *(buf + 5); + LOGD(tcm_hcd->pdev->dev.parent, + "grip_type: %d, grip_pos: %d, x_start: %d, y_start: %d, x_end: %d, y_end: %d\n", + type, pos, x_start, y_start, x_end, y_end); + + if ((type > 2) || (pos > 3)){ + LOGE(tcm_hcd->pdev->dev.parent, + "incorrect value, type:%d, pos:%d\n", type, pos); + goto exit; + } + + switch(type) { + case DEAD_ZONE: + out_buf = (unsigned char *)(&zone->dead_zone.x0[0]); + break; + case EDGE_ZONE: + out_buf = (unsigned char *)(&zone->edge_zone.x0[0]); + break; + case CORNER_ZONE: + out_buf = (unsigned char *)(&zone->corner_zone.x0[0]); + break; + default: + out_buf = NULL; + break; + } + + if (!out_buf) + goto exit; + + offset = pos*8; + + out_buf[offset + 0] = (unsigned char)(x_start & 0xff); + out_buf[offset + 1] = (unsigned char)((x_start >> 8) & 0xff); + out_buf[offset + 2] = (unsigned char)(y_start & 0xff); + out_buf[offset + 3] = (unsigned char)((y_start >> 8) & 0xff); + out_buf[offset + 4] = (unsigned char)(x_end & 0xff); + out_buf[offset + 5] = (unsigned char)((x_end >> 8) & 0xff); + out_buf[offset + 6] = (unsigned char)(y_end & 0xff); + out_buf[offset + 7] = (unsigned char)((y_end >> 8) & 0xff); + +exit: + + return; +} + +static void syna_tcm_deadzone_rejection(bool on, int direction, struct syna_grip_zone *zone) +{ + int i = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + const struct syna_tcm_xiaomi_board_data *bdata = &tcm_hcd->xiaomi_board_data; +#ifdef GRIP_MODE_DEBUG + int *pVal; + + pVal = (int *)&(bdata->deadzone_filter_hor[0]); + LOGI(tcm_hcd->pdev->dev.parent, "deadzone hor %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); + pVal = (int *)&(bdata->deadzone_filter_ver[0]); + LOGI(tcm_hcd->pdev->dev.parent, "deadzone ver %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + if (direction) { + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) { + syna_tcm_set_grip_rect((int *)&(bdata->deadzone_filter_hor[i]), zone); + } + } else { + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) { + syna_tcm_set_grip_rect((int *)&(bdata->deadzone_filter_ver[i]), zone); + } + } + + return; +} + +static void syna_tcm_edge_rejection(bool on, int direction, struct syna_grip_zone *zone) +{ + int i = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + const struct syna_tcm_xiaomi_board_data *bdata = &tcm_hcd->xiaomi_board_data; +#ifdef GRIP_MODE_DEBUG + int *pVal; + + pVal = (int *)&(bdata->edgezone_filter_hor[0]); + LOGI(tcm_hcd->pdev->dev.parent, "Edge hor %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); + + pVal = (int *)&(bdata->edgezone_filter_ver[0]); + LOGI(tcm_hcd->pdev->dev.parent, "Edge ver %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + + if (direction) { + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) { + syna_tcm_set_grip_rect((int *)&(bdata->edgezone_filter_hor[i]), zone); + } + } else { + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) { + syna_tcm_set_grip_rect((int *)&(bdata->edgezone_filter_ver[i]), zone); + } + } + + return; +} + +static void syna_tcm_corner_rejection(bool on, int direction, struct syna_grip_zone *zone) +{ + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + struct syna_tcm_xiaomi_board_data *bdata = &tcm_hcd->xiaomi_board_data; + int filter_value = 0, i = 0; + int corner_filter[GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3] = {0}; +#ifdef GRIP_MODE_DEBUG + int *pVal; +#endif + +#ifdef GRIP_MODE_DEBUG + LOGI(tcm_hcd->pdev->dev.parent, "xiaomi x_max %d, y_max %d\n", bdata->x_max, bdata->y_max); + + pVal = (int *)&(bdata->cornerzone_filter_hor1[0]); + LOGI(tcm_hcd->pdev->dev.parent, "Corner hor1 %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); + + pVal = (int *)&(bdata->cornerzone_filter_hor2[0]); + LOGI(tcm_hcd->pdev->dev.parent, "Corner hor1 %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); + + pVal = (int *)&(bdata->cornerzone_filter_ver[0]); + LOGI(tcm_hcd->pdev->dev.parent, "cornerzone_ver %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + + switch (p_xiaomi_touch_interfaces->touch_mode[Touch_Edge_Filter][SET_CUR_VALUE]) { + case 0: + filter_value = 0; + break; + case 1: + filter_value = bdata->cornerfilter_area_step1; + break; + case 2: + filter_value = bdata->cornerfilter_area_step2; + break; + case 3: + filter_value = bdata->cornerfilter_area_step3; + break; + default: + filter_value = bdata->cornerfilter_area_step2; + LOGI(tcm_hcd->pdev->dev.parent, "%s: no support value use default filter x/y value\n", __func__); + break; + } + + if (filter_value == 0 && direction != 0) { + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) { + corner_filter[i] = 0; + corner_filter[i + 1] = i / GRIP_PARAMETER_NUM; + syna_tcm_set_grip_rect(&corner_filter[i], zone); + } + return; + } + + if (direction == 1) { + bdata->cornerzone_filter_hor1[4] = filter_value; + bdata->cornerzone_filter_hor1[5] = filter_value; + bdata->cornerzone_filter_hor1[GRIP_PARAMETER_NUM * 2 + 3] = bdata->y_max - filter_value - 1; + bdata->cornerzone_filter_hor1[GRIP_PARAMETER_NUM * 2 + 4] = filter_value; + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) + syna_tcm_set_grip_rect((int *)&(bdata->cornerzone_filter_hor1[i]), zone); +#ifdef GRIP_MODE_DEBUG + pVal = (int *)&(bdata->cornerzone_filter_hor1[0]); + LOGI(tcm_hcd->pdev->dev.parent, "cornerzone_hor1 %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + } else if (direction == 3) { + bdata->cornerzone_filter_hor2[GRIP_PARAMETER_NUM + 2] = bdata->x_max - filter_value - 1; + bdata->cornerzone_filter_hor2[GRIP_PARAMETER_NUM + 5] = filter_value; + bdata->cornerzone_filter_hor2[GRIP_PARAMETER_NUM * 3 + 2] = bdata->x_max - filter_value - 1; + bdata->cornerzone_filter_hor2[GRIP_PARAMETER_NUM * 3 + 3] = bdata->y_max - filter_value - 1; + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) + syna_tcm_set_grip_rect((int *)&(bdata->cornerzone_filter_hor2[i]), zone); +#ifdef GRIP_MODE_DEBUG + pVal = (int *)&(bdata->cornerzone_filter_hor2[0]); + LOGI(tcm_hcd->pdev->dev.parent, "cornerzone_hor2 %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + } else if (direction == 0) { + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM / 3; i += GRIP_PARAMETER_NUM) + syna_tcm_set_grip_rect((int *)&(bdata->cornerzone_filter_ver[i]), zone); +#ifdef GRIP_MODE_DEBUG + pVal = (int *)&(bdata->cornerzone_filter_ver[0]); + LOGI(tcm_hcd->pdev->dev.parent, "cornerzone_ver %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + } + + return; +} + +static void syna_tcm_update_grip_mode(void) +{ + bool gamemode_on = p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][GET_CUR_VALUE]; + int direction = p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][SET_CUR_VALUE]; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + int retval, i; + unsigned int length_out; + struct syna_grip_zone *grip_zone = NULL; + unsigned char *out_buf; + unsigned char *resp_buf = NULL; + unsigned int resp_buf_size = 0; + unsigned int resp_length; + unsigned int offset; + unsigned short checksum = 0; + + /* + * out_buf[0]: sub_cmd_id + * out_buf[(length_out-2):1]: struct syna_grip_zone + * out_buf[length_out-1]: 8bit-checksum + */ + length_out = 1 + sizeof(struct syna_grip_zone) + 1; + out_buf = (unsigned char *)kzalloc(length_out, GFP_KERNEL); + if (!out_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out_buf\n"); + goto exit1; + } + + out_buf[0] = C7_SUB_CMD_SET_GRIP_ZONE; + grip_zone = (struct syna_grip_zone*)&out_buf[1]; + grip_zone->type = (gamemode_on ? (1 << 4) : 0) | (direction & 0x0F); + + if (!gamemode_on) { + mutex_lock(&tcm_hcd->long_mode_value_mutex); + for (i = 0; i < GRIP_RECT_NUM * GRIP_PARAMETER_NUM; i += GRIP_PARAMETER_NUM) { + syna_tcm_set_grip_rect(&(p_xiaomi_touch_interfaces->long_mode_value[i]), grip_zone); + } + mutex_unlock(&tcm_hcd->long_mode_value_mutex); + goto send_cmd; + } + + syna_tcm_deadzone_rejection(gamemode_on, direction, grip_zone); + syna_tcm_edge_rejection(gamemode_on, direction, grip_zone); + syna_tcm_corner_rejection(gamemode_on, direction, grip_zone); + +send_cmd: + for (i = 1; i < (length_out - 1); i++) { + checksum += out_buf[i]; + } + out_buf[length_out - 1] = (unsigned char)((checksum & 0xFF) + ((checksum>>8) & 0xFF)); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_MultiFunction, + out_buf, + length_out, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", STR(CMD_MultiFunction)); + } + + offset = 0; + LOGI(tcm_hcd->pdev->dev.parent, "length_out:%d, SUBID %d, gamemode|panel_orientation: %02x %02x, crc8:%d checksum: %d\n", + length_out, out_buf[0], out_buf[1], out_buf[2], out_buf[length_out - 1], checksum); + //offset = 3; + for (i = 0; i < 3 * 4; i++) { + offset = 3 + i * 8; +#ifdef GRIP_MODE_DEBUG + LOGI(tcm_hcd->pdev->dev.parent, "%s corner[%d]:%4d %4d %4d %4d\n", + __func__, i%4, le2_to_uint(&out_buf[offset+0]), le2_to_uint(&out_buf[offset+2]), + le2_to_uint(&out_buf[offset+4]), le2_to_uint(&out_buf[offset+6])); +#endif + } + + LOGN(tcm_hcd->pdev->dev.parent, "Set grip zone done\n"); + +exit1: + kfree(out_buf); + kfree(resp_buf); + return; +} + +static void syna_tcm_get_grip_mode(unsigned int *gamemode, unsigned int *panel_orientation) +{ + int retval; + int i; + unsigned char out_buf; + unsigned char *resp_buf = NULL; + unsigned int resp_buf_size = 0; + unsigned int resp_length; + unsigned int len, offset; + unsigned short checksum = 0; + unsigned char crc8 = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + out_buf = (unsigned char)C7_SUB_CMD_GET_GRIP_ZONE; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_MultiFunction, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s to get grip setting\n", STR(CMD_MultiFunction)); + goto exit; + } + + len = sizeof(struct syna_grip_zone) + 1; + if (resp_length < len) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length: resp_length = %d, len = %d\n", resp_length, len); + retval = -EINVAL; + goto exit; + } + + /* + * resp_buf[(len-2):0]: struct syna_grip_zone + * resp_buf[len-1]: 8bit-checksum + */ + for (i = 0; i < (len - 1); i++) { + checksum += resp_buf[i]; + } + crc8 = (unsigned char)((checksum & 0xFF) + ((checksum>>8) & 0xFF)); + if (resp_buf[len - 1] != crc8) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid checksum: crc8 = %d, checksum = %d, resp_checksum = %d\n", + crc8, checksum, resp_buf[len - 1]); + retval = -EINVAL; + goto exit; + } + + *gamemode = (resp_buf[0]&(1<<4)) ? 1 : 0; + *panel_orientation = resp_buf[0] & 0x0F; + + offset = 0; + LOGN(tcm_hcd->pdev->dev.parent, "length:%d, gamemode|panel_orientation: %d %d, checksum: %d\n", + resp_length, resp_buf[0], resp_buf[1], resp_buf[len - 1]); + for (i = 0; i < 3*4; i++) { + offset = 2 + i*8; +#ifdef GRIP_MODE_DEBUG + LOGI(tcm_hcd->pdev->dev.parent, "%s, corner[%d]:%4d %4d %4d %4d\n", + __func__, i%4, le2_to_uint(&resp_buf[offset+0]), le2_to_uint(&resp_buf[offset+2]), + le2_to_uint(&resp_buf[offset+4]), le2_to_uint(&resp_buf[offset+6])); +#endif + } + + LOGN(tcm_hcd->pdev->dev.parent, "Get grip zone done\n"); + +exit: + kfree(resp_buf); + return; +} + +static void syna_tcm_update_touchmode_data(void) +{ + int i; + int retval = 0; + unsigned int mode_update_flag = 0; + unsigned int dc_id = 0; + unsigned short temp_value = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + unsigned int gamemode, panel_orientation; + + mutex_lock(&tcm_hcd->cmd_update_mutex); + + for (i = 0; i < Touch_Mode_NUM; i++) { + if (p_xiaomi_touch_interfaces->touch_mode[i][GET_CUR_VALUE] != p_xiaomi_touch_interfaces->touch_mode[i][SET_CUR_VALUE]) { + p_xiaomi_touch_interfaces->touch_mode[i][GET_CUR_VALUE] = p_xiaomi_touch_interfaces->touch_mode[i][SET_CUR_VALUE]; + temp_value = p_xiaomi_touch_interfaces->touch_mode[i][SET_CUR_VALUE]; + mode_update_flag |= 1 << i; + + dc_id = syna_tcm_get_dc_id(i); + if (dc_id == DC_UNKNOWN) { + continue; + } + /* set value to FW for dc_id in touch_mode_dc_id_table[] */ + retval = tcm_hcd->set_dynamic_config(tcm_hcd, dc_id, temp_value); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "set dynamic config id:0x%02x failed\n", dc_id); + } + + // debug: read to get the current value in FW + retval = tcm_hcd->get_dynamic_config(tcm_hcd, dc_id, &temp_value); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to get dynamic config id: 0x%02x\n", dc_id); + } else { + LOGI(tcm_hcd->pdev->dev.parent, "get dynamic config id: 0x%02x, value: %d\n", dc_id, temp_value); + } + } + } + + if ((mode_update_flag & (1 << Touch_Game_Mode)) || + (tcm_hcd->gamemode_enable && (mode_update_flag & (1 << Touch_Panel_Orientation))) || + (tcm_hcd->gamemode_enable && (mode_update_flag & (1 << Touch_Edge_Filter)))) { + LOGI(tcm_hcd->pdev->dev.parent, "gamemode_enabled: %d, mode_update_flag: %d\n",tcm_hcd->gamemode_enable, mode_update_flag); + syna_tcm_update_grip_mode(); + + /* debug to get the grip mode setting */ + syna_tcm_get_grip_mode(&gamemode, &panel_orientation); + } + + mutex_unlock(&tcm_hcd->cmd_update_mutex); + return; +} + + +static void syna_tcm_cmd_update_work(struct work_struct *work) +{ + syna_tcm_update_touchmode_data(); + + return; +} + +static void syna_tcm_grip_mode_work(struct work_struct *work) +{ + struct syna_tcm_hcd *tcm_hcd = + container_of(work, struct syna_tcm_hcd, grip_mode_work); + int long_mode_len; + + mutex_lock(&tcm_hcd->long_mode_value_mutex); + long_mode_len = p_xiaomi_touch_interfaces->long_mode_len; + mutex_unlock(&tcm_hcd->long_mode_value_mutex); + + if (long_mode_len != GRIP_RECT_NUM * GRIP_PARAMETER_NUM) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s len is invalid\n", __func__); + return; + } + + mutex_lock(&tcm_hcd->cmd_update_mutex); + if (tcm_hcd->gamemode_enable) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s is in gamemode, don't set rect!\n", __func__); + mutex_unlock(&tcm_hcd->cmd_update_mutex); + return; + } + + syna_tcm_update_grip_mode(); + mutex_unlock(&tcm_hcd->cmd_update_mutex); +} + +static void syna_tcm_power_status_handle(struct syna_tcm_hcd *tcm_hcd) +{ + flush_workqueue(tcm_hcd->event_wq);; + + LOGI(tcm_hcd->pdev->dev.parent, + "power_status_handle, 0x%02x\n", tcm_hcd->power_status); + if (tcm_hcd->power_status) { + queue_work(tcm_hcd->event_wq, &tcm_hcd->resume_work); + } else { + queue_work(tcm_hcd->event_wq, &tcm_hcd->suspend_work); + } + + return; +} + +#define FLAG_FOD_DISABLE 0 +#define FLAG_FOD_ENABLE 1 + +/* Get mode & value from TP_IC */ +static int syna_tcm_read_touchmode_data(void) +{ + int retval = 0; + unsigned int dc_id = 0; + unsigned short get_value[Touch_Mode_NUM] = {0}; + int i; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + for (i = 0; i < Touch_Mode_NUM; i++) { + dc_id = syna_tcm_get_dc_id(i); + if (dc_id == DC_UNKNOWN) { + continue; + } + + retval = tcm_hcd->get_dynamic_config(tcm_hcd, dc_id, &get_value[i]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to get dynamic config id: 0x%02x\n", dc_id); + goto exit; + } + p_xiaomi_touch_interfaces->touch_mode[i][GET_CUR_VALUE] = get_value[i]; + } + + LOGI(tcm_hcd->pdev->dev.parent, + "%s: game_mode:%d, active_mode:%d, up_threshold:%d, tolerance:%d, panel orientation:%d, Report Rate:%d\n", + __func__, get_value[Touch_Game_Mode], get_value[Touch_Active_MODE], get_value[Touch_UP_THRESHOLD], + get_value[Touch_Tolerance], get_value[Touch_Panel_Orientation], get_value[Touch_Report_Rate]); + +exit: + return retval; +} + +static int syna_tcm_palm_area_change_setting(int value) +{ + int retval = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + LOGI(tcm_hcd->pdev->dev.parent, + "palm area change setting, 0x%02x\n", value); + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_PALM_AREA_CHANGE, value); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to palm area change setting\n"); + goto exit; + } + +exit: + return retval; +} + +int syna_tcm_palm_sensor_write(int value) +{ + int retval = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + LOGI(tcm_hcd->pdev->dev.parent, + "palm_sensor_write, 0x%02x\n", value); + tcm_hcd->palm_sensor_enable = value; + + if (!tcm_hcd->palm_sensor_enable) { + tcm_hcd->palm_enable_status = 0; + if (!tcm_hcd->in_suspend) + syna_tcm_palm_area_change_setting(0); + update_palm_sensor_value(tcm_hcd->palm_enable_status); + } + return retval; +} + +static int syna_tcm_set_cur_value(int mode, int val) +{ + int retval = 0; + int maxVal, minVal; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!p_xiaomi_touch_interfaces) { + LOGE(tcm_hcd->pdev->dev.parent, + "p_xiaomi_touch_interfaces is NULL\n"); + retval = -ENOMEM; + goto exit; + } + + if (mode >= Touch_Mode_NUM) { + LOGE(tcm_hcd->pdev->dev.parent, "error mode[%d]\n", mode); + retval = -EINVAL; + goto exit; + } + + if (val < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "error value [%d]\n", val); + retval = -EINVAL; + goto exit; + } + + switch (mode) { + case Touch_Doubletap_Mode: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Doubletap_Mode value [%d]\n", val); + /* Bit0: Double Tap; Bit13: Single Tap */ + tcm_hcd->doubletap_enable = val > 0 ? true : false; + break; + case Touch_Aod_Enable: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Aod_Enable value [%d]\n", val); + tcm_hcd->aod_enable = val > 0 ? true : false; + break; + case Touch_Fod_Enable: + tcm_hcd->finger_unlock_status = -1; + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Fod_Enable value [%d]\n", val); + if (val == FOD_STATUS_INVALID || val == FOD_STATUS_DELETED) { + val = FLAG_FOD_DISABLE; + } else if (val == FOD_STATUS_UNLOCKED || val == FOD_STATUS_UNLOCKING || + val == FOD_STATUS_INPUT_FINGERPRINT || val == FOD_STATUS_UNLOCK_FAILED) { + tcm_hcd->finger_unlock_status = val; + val = FLAG_FOD_ENABLE; + } + + tcm_hcd->fod_enabled = val; + queue_work(tcm_hcd->event_wq, &tcm_hcd->fod_work); + break; + case Touch_Power_Status: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Power_Status value [%d]\n", val); + tcm_hcd->power_status = val; + syna_tcm_power_status_handle(tcm_hcd); + break; + case Touch_FodIcon_Enable: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_FodIcon_Enable value [%d]\n", val); + tcm_hcd->fod_display_enabled = false; + tcm_hcd->fod_icon_status = val > 0 ? true : false; + break; + case Touch_Nonui_Mode: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Nonui_Mode value [%d]\n", val); + tcm_hcd->nonui_status = val; + switch (tcm_hcd->nonui_status) { + case 0: + if (tcm_hcd->in_sleep && tcm_hcd->in_suspend) { + LOGI(tcm_hcd->pdev->dev.parent, "Exit sleep mode!\n"); + /* enable irq */ + tcm_hcd->enable_irq(tcm_hcd, true, NULL); + retval = tcm_hcd->sleep(tcm_hcd, false); + if (retval < 0) { + tcm_hcd->in_sleep = true; + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to exit deep sleep\n"); + goto exit; + } + tcm_hcd->in_sleep = false; + } + + if (tcm_hcd->fod_icon_status || tcm_hcd->aod_enable) { + LOGI(tcm_hcd->pdev->dev.parent, "Enable single tap!\n"); + tcm_hcd->gesture_type |= (0x0001 << 13); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_GESTURE_TYPE_ENABLE, tcm_hcd->gesture_type); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable gesture type\n"); + goto exit; + } + } + break; + case 1: + if (tcm_hcd->fod_icon_status && !tcm_hcd->aod_enable && tcm_hcd->in_suspend) { + LOGI(tcm_hcd->pdev->dev.parent, "Disable single tap!\n"); + tcm_hcd->gesture_type &= ~(0x0001<<13); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_GESTURE_TYPE_ENABLE, tcm_hcd->gesture_type); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable single tap\n"); + goto exit; + } + } + break; + case 2: + if (!tcm_hcd->in_sleep && tcm_hcd->wakeup_gesture_enabled && tcm_hcd->in_suspend) { + LOGI(tcm_hcd->pdev->dev.parent, "Enter sleep mode!\n"); + retval = tcm_hcd->sleep(tcm_hcd, true); + if (retval < 0) { + tcm_hcd->in_sleep = false; + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter deep sleep\n"); + goto exit; + } + /* disable irq */ + tcm_hcd->enable_irq(tcm_hcd, false, true); + tcm_hcd->in_sleep = true; + } + break; + } + break; + case Touch_Game_Mode: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Game_Mode value [%d]\n", val); + tcm_hcd->gamemode_enable = val > 0 ? true : false; + break; + case Touch_Panel_Orientation: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Panel_Orientation value [%d]\n", val); + break; + case Touch_Edge_Filter: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Edge_Filter value [%d]\n", val); + break; + case Touch_UP_THRESHOLD: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_UP_THRESHOLD value [%d]\n", val); + break; + case Touch_Tolerance: + LOGI(tcm_hcd->pdev->dev.parent, "Touch_Tolerance value [%d]\n", val); + break; + default: + LOGI(tcm_hcd->pdev->dev.parent, "other mode type mode [%d], value [%d]\n", mode, val); + break; + } + + maxVal = p_xiaomi_touch_interfaces->touch_mode[mode][GET_MAX_VALUE]; + minVal = p_xiaomi_touch_interfaces->touch_mode[mode][GET_MIN_VALUE]; + p_xiaomi_touch_interfaces->touch_mode[mode][SET_CUR_VALUE] = + (val >= maxVal) ? maxVal : ((val <= minVal) ? minVal : val); + + if (mode == Touch_Game_Mode || mode == Touch_Panel_Orientation || + mode == Touch_UP_THRESHOLD || mode == Touch_Tolerance || + mode == Touch_Edge_Filter) { + queue_work(tcm_hcd->game_wq, &tcm_hcd->cmd_update_work); + } + +exit: + return retval; +} + +/* Normal mode grip mode setup */ +static int syna_tcm_set_mode_long_value(int mode, int len, int *buf) +{ + int i = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (len <= 0) + return -EIO; + + LOGE(tcm_hcd->pdev->dev.parent, "%s, mode: %d, len: %d\n", + __func__, mode, len); + + mutex_lock(&tcm_hcd->long_mode_value_mutex); + p_xiaomi_touch_interfaces->long_mode_len = len; + for (i = 0; i < len; i++) { + p_xiaomi_touch_interfaces->long_mode_value[i] = buf[i]; + } +#ifdef GRIP_MODE_DEBUG + for (i = 0; i < len; i = i + 8) { + LOGI(tcm_hcd->pdev->dev.parent, + "long_mode_value[0~7] = %d, %d, %d, %d, %d, %d, %d, %d\n", + p_xiaomi_touch_interfaces->long_mode_value[i], p_xiaomi_touch_interfaces->long_mode_value[i + 1], p_xiaomi_touch_interfaces->long_mode_value[i + 2], + p_xiaomi_touch_interfaces->long_mode_value[i + 3], p_xiaomi_touch_interfaces->long_mode_value[i + 4], p_xiaomi_touch_interfaces->long_mode_value[i + 5], + p_xiaomi_touch_interfaces->long_mode_value[i + 6], p_xiaomi_touch_interfaces->long_mode_value[i + 7]); + } +#endif + mutex_unlock(&tcm_hcd->long_mode_value_mutex); + + if (mode == Touch_Grip_Mode) { + if (tcm_hcd->gamemode_enable) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s in gamemode, can't write parameters to touch ic\n", + __func__); + return 0; + } else { + schedule_work(&tcm_hcd->grip_mode_work); + } + } + + return 0; +} + +static int syna_tcm_get_mode_value(int mode, int val_type) +{ + int val = -1; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!p_xiaomi_touch_interfaces) { + LOGE(tcm_hcd->pdev->dev.parent, + "p_xiaomi_touch_interfaces is NULL\n"); + return val; + } + + if ((mode < Touch_Mode_NUM) && (mode >= 0)) { + val = p_xiaomi_touch_interfaces->touch_mode[mode][val_type]; + } + else + LOGE(tcm_hcd->pdev->dev.parent, "error mode[%d]\n", mode); + + return val; +} + +static int syna_tcm_get_mode_all(int mode, int *val) +{ + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!p_xiaomi_touch_interfaces) { + LOGE(tcm_hcd->pdev->dev.parent,"p_xiaomi_touch_interfaces is NULL\n"); + return (-ENOMEM); + } + + if ((mode < Touch_Mode_NUM) && (mode >= 0)) { + val[0] = p_xiaomi_touch_interfaces->touch_mode[mode][GET_CUR_VALUE]; + val[1] = p_xiaomi_touch_interfaces->touch_mode[mode][GET_DEF_VALUE]; + val[2] = p_xiaomi_touch_interfaces->touch_mode[mode][GET_MIN_VALUE]; + val[3] = p_xiaomi_touch_interfaces->touch_mode[mode][GET_MAX_VALUE]; + } + else { + LOGE(tcm_hcd->pdev->dev.parent, "error mode[%d]\n", mode); + } + + return 0; +} + +static int syna_tcm_reset_mode(int mode) +{ + int i = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!p_xiaomi_touch_interfaces) { + LOGE(tcm_hcd->pdev->dev.parent,"p_xiaomi_touch_interfaces is NULL\n"); + return -ENOMEM; + } + + if (mode < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "do not support this mode %d\n", mode); + return -EINVAL; + } + + LOGI(tcm_hcd->pdev->dev.parent,"reset mode %d\n", mode); + if (mode < Touch_Mode_NUM && mode > 0) { + p_xiaomi_touch_interfaces->touch_mode[mode][SET_CUR_VALUE] = + p_xiaomi_touch_interfaces->touch_mode[mode][GET_DEF_VALUE]; + + } else if (mode == 0) { + for (i = 0; i < Touch_Mode_NUM; i++) { + if (i == Touch_Panel_Orientation) { + p_xiaomi_touch_interfaces->touch_mode[i][SET_CUR_VALUE] = + p_xiaomi_touch_interfaces->touch_mode[i][GET_CUR_VALUE]; + } else { + p_xiaomi_touch_interfaces->touch_mode[mode][SET_CUR_VALUE] = + p_xiaomi_touch_interfaces->touch_mode[mode][GET_DEF_VALUE]; + p_xiaomi_touch_interfaces->touch_mode[mode][GET_CUR_VALUE] = + p_xiaomi_touch_interfaces->touch_mode[mode][SET_CUR_VALUE]; + } + } + tcm_hcd->gamemode_enable = false; + } + queue_work(tcm_hcd->game_wq, &tcm_hcd->cmd_update_work); + + return 0; +} + +static void syna_tcm_reinit_mode(void) +{ + unsigned int gamemode, panel_orientation; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + syna_tcm_get_grip_mode(&gamemode, &panel_orientation); + p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][GET_CUR_VALUE] = gamemode; + p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][GET_CUR_VALUE] = panel_orientation; + syna_tcm_read_touchmode_data(); + + queue_work(tcm_hcd->game_wq, &tcm_hcd->cmd_update_work); + + return; +} + +static int syna_tcm_parse_gamemode_param_dt(struct syna_tcm_xiaomi_board_data *bdata) +{ + int retval, i, j; + u32 temp_val[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int *pVal = NULL; + char *str_dts = NULL; + struct property *prop; + struct spi_device *spi; + struct device_node *np = NULL; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + /* parse dtsi */ + spi = to_spi_device(tcm_hcd->pdev->dev.parent); + np = spi->dev.of_node; + + /* parse the touch mode setting in dtsi */ + for (i = 0; i < Touch_Mode_NUM; i++) { + memset(temp_val, 0, sizeof(temp_val)); + switch (i) { + case Touch_Game_Mode: + pVal = bdata->game_mode; + str_dts = SYNA_GAME_MODE_ARRAY; + break; + case Touch_Active_MODE: + pVal = bdata->active_mode; + str_dts = SYNA_ACTIVE_MODE_ARRAY; + break; + case Touch_UP_THRESHOLD: + pVal = bdata->up_threshold; + str_dts = SYNA_UP_THRESHOLD_ARRAY; + break; + case Touch_Tolerance: + pVal = bdata->tolerance; + str_dts = SYNA_TOLERANCE_ARRAY; + break; + case Touch_Edge_Filter: + pVal = bdata->edge_filter; + str_dts = SYNA_EDGE_FILTER_ARRAY; + break; + case Touch_Panel_Orientation: + pVal = bdata->panel_orien; + str_dts = SYNA_PANEL_ORIEN_ARRAY; + break; + case Touch_Report_Rate: + pVal = bdata->report_rate; + str_dts = SYNA_REPORT_RATE_ARRAY; + break; + default: + pVal = NULL; + str_dts = NULL; + break; + } + + if (pVal == NULL || str_dts == NULL) + continue; + + prop = of_find_property(np, str_dts, NULL); + if (prop && prop->length) { + retval = of_property_read_u32_array(np, str_dts ,temp_val, SYNA_TOUCH_MODE_PARAMETERS_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to read synaptics, %s property\n", str_dts); + } + } + + /* if there is no correct setting in dts, set it to 0 */ + for (j = 0; j < SYNA_TOUCH_MODE_PARAMETERS_SIZE; j++) { + pVal[j] = temp_val[j]; + } + LOGD(tcm_hcd->pdev->dev.parent, "%s: %2d %2d %2d %2d %2d\n", + str_dts, pVal[0], pVal[1], pVal[2], pVal[3], pVal[4]); + } + + /* parse the grip conrnerfiler_area_step */ + memset(temp_val, 0, sizeof(temp_val)); + str_dts = SYNA_CORNER_FILTER_AREA_STEP_ARRAY; + prop = of_find_property(np, str_dts, NULL); + if (prop && prop->length) { + retval = of_property_read_u32_array(np, str_dts ,temp_val, SYNA_CORNERFILTER_AREA_STEP_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to read synaptics, %s property\n", str_dts); + } + } + bdata->cornerfilter_area_step0 = temp_val[0]; + bdata->cornerfilter_area_step1 = temp_val[1]; + bdata->cornerfilter_area_step2 = temp_val[2]; + bdata->cornerfilter_area_step3 = temp_val[3]; + LOGI(tcm_hcd->pdev->dev.parent, "%s: %2d %2d %2d %2d\n", + str_dts, temp_val[0], temp_val[1], temp_val[2], temp_val[3]); + + /* parse the grip cornerzone/deadzone/edgezone filter */ + for (i = 0; i < 7; i++) { + memset(temp_val, 0, sizeof(temp_val)); + switch (i) { + case 0: + pVal = bdata->cornerzone_filter_hor1; + str_dts = SYNA_CORNER_ZONE_FILTER_HOR1_ARRAY; + break; + case 1: + pVal = bdata->cornerzone_filter_hor2; + str_dts = SYNA_CORNER_ZONE_FILTER_HOR2_ARRAY; + break; + case 2: + pVal = bdata->cornerzone_filter_ver; + str_dts = SYNA_CORNER_ZONE_FILTER_VER_ARRAY; + break; + case 3: + pVal = bdata->deadzone_filter_hor; + str_dts = SYNA_DEAD_ZONE_FILTER_HOR_ARRAY; + break; + case 4: + pVal = bdata->deadzone_filter_ver; + str_dts = SYNA_DEAD_ZONE_FILTER_VER_ARRAY; + break; + case 5: + pVal = bdata->edgezone_filter_hor; + str_dts = SYNA_EDGE_ZONE_FILTER_HOR_ARRAY; + break; + case 6: + pVal = bdata->edgezone_filter_ver; + str_dts = SYNA_EDGE_ZONE_FILTER_VER_ARRAY; + break; + default: + pVal = NULL; + str_dts = NULL; + break; + } + + if (pVal == NULL || str_dts == NULL) + continue; + + prop = of_find_property(np, str_dts, NULL); + if (prop && prop->length) { + retval = of_property_read_u32_array(np, str_dts ,temp_val, SYNA_GRIP_PARAMETERS_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to read synaptics, %s property\n", str_dts); + } + } + + /* if there is no correct setting in dts, set it to 0 */ + for (j = 0; j < SYNA_GRIP_PARAMETERS_SIZE; j++) { + pVal[j] = temp_val[j]; + } +#ifdef GRIP_MODE_DEBUG + LOGI(tcm_hcd->pdev->dev.parent, "%s: %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d %2d\n", + str_dts, pVal[0], pVal[1], pVal[2], pVal[3], pVal[4], pVal[5], pVal[6], pVal[7], pVal[8], pVal[9], pVal[10], pVal[11], + pVal[12], pVal[13], pVal[14], pVal[15], pVal[16], pVal[17], pVal[18], pVal[19], pVal[20], pVal[21], pVal[22], pVal[23], + pVal[24], pVal[25], pVal[26], pVal[27], pVal[28], pVal[29], pVal[30], pVal[31]); +#endif + } + + /* parse the display resolution */ + memset(temp_val, 0, sizeof(temp_val)); + str_dts = SYNA_DISPLAY_RESOLUTION_ARRAY; + prop = of_find_property(np, str_dts, NULL); + if (prop && prop->length) { + retval = of_property_read_u32_array(np, str_dts ,temp_val, SYNA_DISPLAY_RESOLUTION_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to read synaptics, %s property\n", str_dts); + } + } + bdata->x_max = temp_val[0]; + bdata->y_max = temp_val[1]; + LOGI(tcm_hcd->pdev->dev.parent, "%s: x_max:%2d, y_max:%2d\n", + str_dts, bdata->x_max, bdata->y_max); + + return 0; +} + +static void syna_tcm_init_touchmode_data(void) +{ + int i = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + struct syna_tcm_xiaomi_board_data *bdata = &tcm_hcd->xiaomi_board_data; + + if (!p_xiaomi_touch_interfaces) { + LOGE(tcm_hcd->pdev->dev.parent,"p_xiaomi_touch_interfaces is NULL\n"); + return; + } + + /* parse the TOUCH_MODE value in dts */ + syna_tcm_parse_gamemode_param_dt(bdata); + + /* Touch Game Mode Switch */ + p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][GET_MAX_VALUE] = + bdata->game_mode[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][GET_MIN_VALUE] = + bdata->game_mode[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][GET_DEF_VALUE] = + bdata->game_mode[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][SET_CUR_VALUE] = + bdata->game_mode[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Game_Mode][GET_CUR_VALUE] = + bdata->game_mode[SYNA_DTS_GET_CUR_INDEX]; + + /* Active Mode */ + p_xiaomi_touch_interfaces->touch_mode[Touch_Active_MODE][GET_MAX_VALUE] = + bdata->active_mode[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Active_MODE][GET_MIN_VALUE] = + bdata->active_mode[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Active_MODE][GET_DEF_VALUE] = + bdata->active_mode[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Active_MODE][SET_CUR_VALUE] = + bdata->active_mode[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Active_MODE][GET_CUR_VALUE] = + bdata->active_mode[SYNA_DTS_GET_CUR_INDEX]; + + /* Finger Hysteresis */ + p_xiaomi_touch_interfaces->touch_mode[Touch_UP_THRESHOLD][GET_MAX_VALUE] = + bdata->up_threshold[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_UP_THRESHOLD][GET_MIN_VALUE] = + bdata->up_threshold[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_UP_THRESHOLD][GET_DEF_VALUE] = + bdata->up_threshold[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_UP_THRESHOLD][SET_CUR_VALUE] = + bdata->up_threshold[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_UP_THRESHOLD][GET_CUR_VALUE] = + bdata->up_threshold[SYNA_DTS_GET_CUR_INDEX]; + + /* Tolerance */ + p_xiaomi_touch_interfaces->touch_mode[Touch_Tolerance][GET_MAX_VALUE] = + bdata->tolerance[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Tolerance][GET_MIN_VALUE] = + bdata->tolerance[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Tolerance][GET_DEF_VALUE] = + bdata->tolerance[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Tolerance][SET_CUR_VALUE] = + bdata->tolerance[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Tolerance][GET_CUR_VALUE] = + bdata->tolerance[SYNA_DTS_GET_CUR_INDEX]; + + /* Edge Filter */ + p_xiaomi_touch_interfaces->touch_mode[Touch_Edge_Filter][GET_MAX_VALUE] = + bdata->edge_filter[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Edge_Filter][GET_MIN_VALUE] = + bdata->edge_filter[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Edge_Filter][GET_DEF_VALUE] = + bdata->edge_filter[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Edge_Filter][SET_CUR_VALUE] = + bdata->edge_filter[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Edge_Filter][GET_CUR_VALUE] = + bdata->edge_filter[SYNA_DTS_GET_CUR_INDEX]; + + /* Orientation */ + p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][GET_MAX_VALUE] = + bdata->panel_orien[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][GET_MIN_VALUE] = + bdata->panel_orien[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][GET_DEF_VALUE] = + bdata->panel_orien[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][SET_CUR_VALUE] = + bdata->panel_orien[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Panel_Orientation][GET_CUR_VALUE] = + bdata->panel_orien[SYNA_DTS_GET_CUR_INDEX]; + + /* Report Rate */ + p_xiaomi_touch_interfaces->touch_mode[Touch_Report_Rate][GET_MAX_VALUE] = + bdata->report_rate[SYNA_DTS_GET_MAX_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Report_Rate][GET_MIN_VALUE] = + bdata->report_rate[SYNA_DTS_GET_MIN_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Report_Rate][GET_DEF_VALUE] = + bdata->report_rate[SYNA_DTS_GET_DEF_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Report_Rate][SET_CUR_VALUE] = + bdata->report_rate[SYNA_DTS_SET_CUR_INDEX]; + p_xiaomi_touch_interfaces->touch_mode[Touch_Report_Rate][GET_CUR_VALUE] = + bdata->report_rate[SYNA_DTS_GET_CUR_INDEX]; + + i = 0; + +#ifdef GRIP_MODE_DEBUG + for (i = 0; i < Touch_Mode_NUM; i++) { + LOGI(tcm_hcd->pdev->dev.parent, + "%s, mode:%d, set cur:%d, get cur:%d, def:%d min:%d max:%d\n", + __func__, i, + p_xiaomi_touch_interfaces->touch_mode[i][SET_CUR_VALUE], + p_xiaomi_touch_interfaces->touch_mode[i][GET_CUR_VALUE], + p_xiaomi_touch_interfaces->touch_mode[i][GET_DEF_VALUE], + p_xiaomi_touch_interfaces->touch_mode[i][GET_MIN_VALUE], + p_xiaomi_touch_interfaces->touch_mode[i][GET_MAX_VALUE]); + } +#endif + return; +} +#endif + +static int syna_tcm_check_f35(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char fn_number; + int retry = 0; + const int retry_max = 10; + +F35_BOOT_RECHECK: + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_END_ADDR, + &fn_number, + sizeof(fn_number)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read F35 function number\n"); + tcm_hcd->is_detected = false; + return -ENODEV; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Found F$%02x\n", + fn_number); + + if (fn_number != RMI_UBL_FN_NUMBER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to find F$35, try_times = %d\n", + retry); + if (retry < retry_max) { + msleep(100); + retry++; + goto F35_BOOT_RECHECK; + } + tcm_hcd->is_detected = false; + return -ENODEV; + } + return 0; +} + +static int syna_tcm_sensor_detection(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *build_id; + unsigned int payload_length; + unsigned int max_write_size; + + tcm_hcd->in_hdl_mode = false; + tcm_hcd->sensor_type = TYPE_UNKNOWN; + + /* read sensor info for identification */ + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + + /* once the tcm communication interface is not ready, */ + /* check whether the device is in F35 mode */ + if (retval < 0) { + if (retval == -ENXIO && + tcm_hcd->hw_if->bus_io->type == BUS_SPI) { + + retval = syna_tcm_check_f35(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read TCM message\n"); + return retval; + } + tcm_hcd->in_hdl_mode = true; + tcm_hcd->sensor_type = TYPE_F35; + tcm_hcd->is_detected = true; + tcm_hcd->rd_chunk_size = HDL_RD_CHUNK_SIZE; + tcm_hcd->wr_chunk_size = HDL_WR_CHUNK_SIZE; + LOGN(tcm_hcd->pdev->dev.parent, "F35 mode\n"); + return retval; + } else { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read TCM message\n"); + return retval; + } + } + + /* expect to get an identify report after powering on */ + + if (tcm_hcd->status_report_code != REPORT_IDENTIFY) { + LOGE(tcm_hcd->pdev->dev.parent, + "Unexpected report code (0x%02x)\n", + tcm_hcd->status_report_code); + + return -ENODEV; + } + + tcm_hcd->is_detected = true; + payload_length = tcm_hcd->payload_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + MIN(sizeof(tcm_hcd->id_info), payload_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + build_id = tcm_hcd->id_info.build_id; + tcm_hcd->packrat_number = le4_to_uint(build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + + if (tcm_hcd->id_info.mode == MODE_ROMBOOTLOADER) { + tcm_hcd->in_hdl_mode = true; + tcm_hcd->sensor_type = TYPE_ROMBOOT; + tcm_hcd->rd_chunk_size = HDL_RD_CHUNK_SIZE; + tcm_hcd->wr_chunk_size = HDL_WR_CHUNK_SIZE; + LOGN(tcm_hcd->pdev->dev.parent, + "RomBoot mode\n"); + } else if (tcm_hcd->id_info.mode == MODE_APPLICATION_FIRMWARE) { + tcm_hcd->sensor_type = TYPE_FLASH; + LOGN(tcm_hcd->pdev->dev.parent, + "Application mode (build id = %d)\n", + tcm_hcd->packrat_number); + } else { + LOGW(tcm_hcd->pdev->dev.parent, + "TCM is detected, but mode is 0x%02x\n", + tcm_hcd->id_info.mode); + } + + return 0; +} + + +static ssize_t tp_irq_debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + int retval = 0; + char tmp[1]; + int ret; + + if (copy_from_user(tmp, buf, 1)) { + pr_err("%s: copy_from_user data fail\n", __func__); + retval = -EFAULT; + } + ret = (int)&tmp; + pr_err("%s: ret = %d\n", __func__, ret); + if(ret) + disable_irq(gloab_tcm_hcd->irq); + else + enable_irq(gloab_tcm_hcd->irq); + + return retval; + +} + +static const struct file_operations tp_irq_debug_ops = { + .write = tp_irq_debug_write, +}; + +#ifdef SYNAPTICS_DEBUGFS_ENABLE +static void syna_tcm_dbg_suspend(struct syna_tcm_hcd *tcm_hcd, bool enable) +{ + if (enable) { + queue_work(tcm_hcd->event_wq, &tcm_hcd->early_suspend_work); + queue_work(tcm_hcd->event_wq, &tcm_hcd->suspend_work); + } else + queue_work(tcm_hcd->event_wq, &tcm_hcd->resume_work); +} + +static int syna_tcm_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t syna_tcm_dbg_read(struct file *file, char __user *buf, size_t size, + loff_t *ppos) +{ + const char *str = "cmd support as below:\n \ + \necho \"irq-disable\" or \"irq-enable\" to ctrl irq\n \ + \necho \"tp-suspend-en\" or \"tp-suspend-off\" to ctrl panel in or off suspend status\n \ + \necho \"tp-sd-en\" or \"tp-sd-off\" to ctrl panel in or off sleep status\n"; + + loff_t pos = *ppos; + int len = strlen(str); + + if (pos < 0) + return -EINVAL; + if (pos >= len) + return 0; + + if (copy_to_user(buf, str, len)) + return -EFAULT; + + *ppos = pos + len; + + return len; +} + +static ssize_t syna_tcm_dbg_write(struct file *file, const char __user *buf, + size_t size, loff_t *ppos) +{ + char *cmd = kzalloc(size + 1, GFP_KERNEL); + int ret = size; + + if (!cmd) + return -ENOMEM; + + if (copy_from_user(cmd, buf, size)) { + ret = -EFAULT; + goto out; + } + + cmd[size] = '\0'; + + if (!strncmp(cmd, "irq-disable", 11)) { + LOGI(gloab_tcm_hcd->pdev->dev.parent, + "%s touch irq is disabled!\n", __func__); + gloab_tcm_hcd->enable_irq(gloab_tcm_hcd, false, true); + } else if (!strncmp(cmd, "irq-enable", 10)) { + LOGI(gloab_tcm_hcd->pdev->dev.parent, + "%s touch irq is enabled!\n", __func__); + gloab_tcm_hcd->enable_irq(gloab_tcm_hcd, true, NULL); + } else if (!strncmp(cmd, "tp-sd-en", 8)) + syna_tcm_dbg_suspend(gloab_tcm_hcd, true); + else if (!strncmp(cmd, "tp-sd-off", 9)) + syna_tcm_dbg_suspend(gloab_tcm_hcd, false); + else if (!strncmp(cmd, "tp-suspend-en", 13)) + syna_tcm_dbg_suspend(gloab_tcm_hcd, true); + else if (!strncmp(cmd, "tp-suspend-off", 14)) + syna_tcm_dbg_suspend(gloab_tcm_hcd, false); +out: + kfree(cmd); + + return ret; +} + +static int syna_tcm_dbg_release(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + + return 0; +} + +static void syna_tcm_debugfs_exit(void) +{ + debugfs_remove_recursive(gloab_tcm_hcd->debugfs); +} + + +static const struct file_operations tpdbg_operations = { + .owner = THIS_MODULE, + .open = syna_tcm_dbg_open, + .read = syna_tcm_dbg_read, + .write = syna_tcm_dbg_write, + .release = syna_tcm_dbg_release, +}; +#endif + +#ifdef SYNAPTICS_POWERSUPPLY_CB +static int syna_tcm_get_charging_status() +{ +#ifdef CONFIG_QGKI_SYSTEM + int is_charging = 0; + is_charging = !!power_supply_is_system_supplied(); + if (!is_charging) + return NOT_CHARGING; + else + return CHARGING; +#else + return NOT_CHARGING; +#endif +} + +static void syna_tcm_power_supply_work(struct work_struct *work) +{ + int charging_status; + + LOGI(gloab_tcm_hcd->pdev->dev.parent, "%s enter!\n", __func__); + if (!gloab_tcm_hcd || !tp_probe_success) { + LOGE(gloab_tcm_hcd->pdev->dev.parent, + "%s touch is not inited\n", __func__); + return; + } + + charging_status = syna_tcm_get_charging_status(); + + if (charging_status != gloab_tcm_hcd->charging_status || gloab_tcm_hcd->charging_status < 0) { + gloab_tcm_hcd->charging_status = charging_status; + gloab_tcm_hcd->charger_connected = (charging_status == CHARGING) ? 1 : 0; + if (gloab_tcm_hcd->in_suspend) { + LOGI(gloab_tcm_hcd->pdev->dev.parent, + "%s Can't write charge status\n", __func__); + return; + } + syna_tcm_set_charge_status(); + } +} + +static int syna_tcm_power_supply_event(struct notifier_block *nb, unsigned long event, void *ptr) +{ + if (!gloab_tcm_hcd) + return 0; + + schedule_delayed_work(&gloab_tcm_hcd->power_supply_work, msecs_to_jiffies(500)); + return 0; +} +#endif + + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE +static ssize_t syna_tcm_fw_version_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + int cnt = 0, ret = 0; + char tmp[TP_INFO_MAX_LENGTH]; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (*pos != 0) + return 0; + + if (!tcm_hcd) + return 0; + + if (tcm_hcd->in_suspend) + return 0; + + cnt = + snprintf(tmp, TP_INFO_MAX_LENGTH, "Firmware: %d Cfg: %02x %02x\n", + tcm_hcd->packrat_number, + tcm_hcd->app_info.customer_config_id[6] - 48, + tcm_hcd->app_info.customer_config_id[7] - 48); + ret = copy_to_user(buf, tmp, cnt); + + *pos += cnt; + if (ret != 0) + return 0; + else + return cnt; +} + +static const struct file_operations syna_tcm_fw_version_ops = { + .read = syna_tcm_fw_version_read, +}; + +static ssize_t syna_tcm_lockdown_info_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + int cnt = 0, ret = 0; + char tmp[TP_INFO_MAX_LENGTH]; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (tcm_hcd->in_suspend) + return 0; + + if (*pos != 0) + return 0; + + memset(tmp, 0, TP_INFO_MAX_LENGTH); + + if (!tcm_hcd->syna_tcm_lockdown_info) + return 0; + + ret = tcm_hcd->syna_tcm_lockdown_info(); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, "get lockdown info error\n"); + goto out; + } + + cnt = + snprintf(tmp, PAGE_SIZE, + "OEM_INFO: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + tcm_hcd->lockdown_info[0], tcm_hcd->lockdown_info[1], + tcm_hcd->lockdown_info[2], tcm_hcd->lockdown_info[3], + tcm_hcd->lockdown_info[4], tcm_hcd->lockdown_info[5], + tcm_hcd->lockdown_info[6], tcm_hcd->lockdown_info[7]); + ret = copy_to_user(buf, tmp, cnt); + +out: + *pos += cnt; + if (ret != 0) + return 0; + else + return cnt; +} + +static const struct file_operations syna_tcm_lockdown_info_ops = { + .read = syna_tcm_lockdown_info_read, +}; + +static int sum_size; +static int left_size; +static int transfer_size; +static char *tmp; +static ssize_t syna_tcm_datadump_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + int ret = 0, cnt1 = 0, cnt2 = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (tcm_hcd->in_suspend) + return 0; + + LOGD(tcm_hcd->pdev->dev.parent, "Before: transfer_size = %d, left_size = %d, pos = %d\n", transfer_size, left_size, *pos); + + if (*pos == 0) { + tmp = vmalloc(PAGE_SIZE * 2); + if (tmp == NULL) + return 0; + else + memset(tmp, 0, PAGE_SIZE * 2); + + if (!tcm_hcd->testing_xiaomi_report_data) { + LOGE(tcm_hcd->pdev->dev.parent, "tcm_hcd->testing_xiaomi_report_data = NULL\n"); + ret = -EINVAL; + goto err_out; + } + + cnt1 = tcm_hcd->testing_xiaomi_report_data(REPORT_DELTA, tmp); /* REPORT_DELTA = 0x12 */ + LOGD(tcm_hcd->pdev->dev.parent, "cnt1 = %d\n", cnt1); + if (cnt1 <= 0) { + LOGE(tcm_hcd->pdev->dev.parent, "REPORT_DELTA failed\n"); + ret = cnt1; + goto err_out; + } + + cnt2 = tcm_hcd->testing_xiaomi_report_data(REPORT_RID161, tmp + cnt1); /* REPORT_RID161 = 0xA1 */ + LOGD(tcm_hcd->pdev->dev.parent, "cnt2 = %d\n", cnt2); + if (cnt2 <= 0) { + LOGE(tcm_hcd->pdev->dev.parent, "REPORT_RID161 failed\n"); + ret = cnt2; + goto err_out; + } + sum_size = cnt1 + cnt2; + left_size = sum_size; + } else { + if (tmp && (*pos >= sum_size)) { + vfree(tmp); + tmp = NULL; + return 0; + } + } + + if (left_size < PAGE_SIZE) + transfer_size = left_size; + else { + transfer_size = PAGE_SIZE; + } + left_size -= transfer_size; + + LOGD(tcm_hcd->pdev->dev.parent, "1: sum_size = %d, left_size = %d, transfer_size = %d\n", sum_size, left_size, transfer_size); + + if (access_ok(buf, sum_size)) { + ret = copy_to_user(buf, (tmp + *pos), transfer_size); + if (ret != 0) { + LOGE(tcm_hcd->pdev->dev.parent, "copy to user failed, ret = %d\n", ret); + ret = -EFAULT; + goto err_out; + } else { + LOGD(tcm_hcd->pdev->dev.parent, "copy to user success, ret = %d\n", ret); + } + } + + *pos += transfer_size; + LOGD(tcm_hcd->pdev->dev.parent, "After: transfer_size = %d, left_size = %d, pos = %d\n", transfer_size, left_size, *pos); + return transfer_size; +err_out: + if (tmp) { + vfree(tmp); + tmp = NULL; + } + return ret; +} + +static const struct file_operations syna_tcm_datadump_ops = { + .read = syna_tcm_datadump_read, +}; + +static ssize_t syna_tcm_selftest_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + char tmp[5] = { 0 }; + int cnt; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (*pos != 0) + return 0; + + cnt = + snprintf(tmp, sizeof(tcm_hcd->maintenance_result), "%d\n", + tcm_hcd->maintenance_result); + if (copy_to_user(buf, tmp, strlen(tmp))) { + return -EFAULT; + } + *pos += cnt; + return cnt; +} + +static ssize_t syna_tcm_selftest_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + int retval = 0; + char *self_test_data = NULL; + char tmp[6]; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (copy_from_user(tmp, buf, count)) { + retval = -EFAULT; + goto out; + } + + if (!strncmp("short", tmp, 5) || !strncmp("open", tmp, 4)) { + if (!tcm_hcd && !tcm_hcd->testing_xiaomi_self_test) { + LOGE(tcm_hcd->pdev->dev.parent, "NULL Pointer!\n"); + retval = 1; + goto out; + } + + self_test_data = vmalloc(PAGE_SIZE * 3); + if (self_test_data == NULL) { + retval = 1; + goto out; + } + + retval = tcm_hcd->testing_xiaomi_self_test(self_test_data); + if (!retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "self test failed, retval = %d\n", retval); + retval = 1; + goto out; + } + retval = 2; + } else if (!strncmp("i2c", tmp, 3)) { + if (!tcm_hcd->testing_xiaomi_chip_id_read) { + retval = 1; + goto out; + } + + retval = tcm_hcd->testing_xiaomi_chip_id_read(); + /* (retval == 2) passed (retval == 1) failed */ + } +out: + tcm_hcd->maintenance_result = retval; + + if (self_test_data) { + vfree(self_test_data); + self_test_data = NULL; + } + + if (retval >= 0) + retval = count; + + return retval; +} + +static const struct file_operations syna_tcm_selftest_ops = { + .read = syna_tcm_selftest_read, + .write = syna_tcm_selftest_write, +}; + +static u8 syna_tcm_panel_vendor_read(void) +{ + int ret = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (tcm_hcd->in_suspend) + return 0; + + if (tp_probe_success && + tcm_hcd->syna_tcm_lockdown_info != NULL) { + ret = tcm_hcd->syna_tcm_lockdown_info(); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: get lockdown info error\n", __func__); + return 0; + } + return tcm_hcd->lockdown_info[0]; + } + + return 0; +} + +static u8 syna_tcm_panel_color_read(void) +{ + int ret = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (tcm_hcd->in_suspend) + return 0; + + if (tp_probe_success && + tcm_hcd->syna_tcm_lockdown_info != NULL) { + ret = tcm_hcd->syna_tcm_lockdown_info(); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: get lockdown info error\n", __func__); + return 0; + } + return tcm_hcd->lockdown_info[2]; + } + + return 0; +} + +static u8 syna_tcm_panel_display_read(void) +{ + int ret = 0; + struct syna_tcm_hcd *tcm_hcd = gloab_tcm_hcd; + + if (!tcm_hcd) + return 0; + + if (tcm_hcd->in_suspend) + return 0; + + if (tp_probe_success && + tcm_hcd->syna_tcm_lockdown_info != NULL) { + ret = tcm_hcd->syna_tcm_lockdown_info(); + if (ret) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: get lockdown info error\n", __func__); + return 0; + } + return tcm_hcd->lockdown_info[1]; + } + + return 0; +} + +static char syna_tcm_touch_vendor_read(void) +{ + return '5'; +} +#endif + +static int syna_tcm_probe(struct platform_device *pdev) +{ + int retval; + int idx; + struct syna_tcm_hcd *tcm_hcd; + const struct syna_tcm_board_data *bdata; + const struct syna_tcm_hw_interface *hw_if; + struct spi_device *spi; + + LOGI(&pdev->dev, "-----enter-----%s\n", __func__); + hw_if = pdev->dev.platform_data; + if (!hw_if) { + LOGE(&pdev->dev, + "Hardware interface not found\n"); + return -ENODEV; + } + + bdata = hw_if->bdata; + if (!bdata) { + LOGE(&pdev->dev, + "Board data not found\n"); + return -ENODEV; + } + + tcm_hcd = kzalloc(sizeof(*tcm_hcd), GFP_KERNEL); + if (!tcm_hcd) { + LOGE(&pdev->dev, + "Failed to allocate memory for tcm_hcd\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tcm_hcd); + gloab_tcm_hcd = tcm_hcd; + + tcm_hcd->pdev = pdev; + tcm_hcd->hw_if = hw_if; + tcm_hcd->reset = syna_tcm_reset; + tcm_hcd->reset_n_reinit = syna_tcm_reset_and_reinit; + tcm_hcd->sleep = syna_tcm_sleep; + tcm_hcd->identify = syna_tcm_identify; + tcm_hcd->enable_irq = syna_tcm_enable_irq; + tcm_hcd->switch_mode = syna_tcm_switch_mode; + tcm_hcd->read_message = syna_tcm_read_message; + tcm_hcd->write_message = syna_tcm_write_message; + tcm_hcd->get_dynamic_config = syna_tcm_get_dynamic_config; + tcm_hcd->set_dynamic_config = syna_tcm_set_dynamic_config; + tcm_hcd->get_data_location = syna_tcm_get_data_location; + + tcm_hcd->rd_chunk_size = RD_CHUNK_SIZE; + tcm_hcd->wr_chunk_size = WR_CHUNK_SIZE; + tcm_hcd->is_detected = false; + tcm_hcd->wakeup_gesture_enabled = WAKEUP_GESTURE; + tcm_hcd->fod_enabled = false; + tcm_hcd->in_suspending = false; + tcm_hcd->lockdown_info_ready = false; + +#ifdef PREDICTIVE_READING + tcm_hcd->read_length = MIN_READ_LENGTH; +#else + tcm_hcd->read_length = MESSAGE_HEADER_SIZE; +#endif + +#ifdef WATCHDOG_SW + tcm_hcd->watchdog.run = RUN_WATCHDOG; + tcm_hcd->update_watchdog = syna_tcm_update_watchdog; +#endif + + if (bdata->irq_gpio >= 0) + tcm_hcd->irq = gpio_to_irq(bdata->irq_gpio); + else + tcm_hcd->irq = bdata->irq_gpio; + + mutex_init(&tcm_hcd->extif_mutex); + mutex_init(&tcm_hcd->reset_mutex); + mutex_init(&tcm_hcd->irq_en_mutex); + mutex_init(&tcm_hcd->io_ctrl_mutex); + mutex_init(&tcm_hcd->rw_ctrl_mutex); + mutex_init(&tcm_hcd->command_mutex); + mutex_init(&tcm_hcd->identify_mutex); + mutex_init(&tcm_hcd->esd_recovery_mutex); + + INIT_BUFFER(tcm_hcd->in, false); + INIT_BUFFER(tcm_hcd->out, false); + INIT_BUFFER(tcm_hcd->resp, true); + INIT_BUFFER(tcm_hcd->temp, false); + INIT_BUFFER(tcm_hcd->config, false); + INIT_BUFFER(tcm_hcd->report.buffer, true); + + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->in, + tcm_hcd->read_length + 1); + if (retval < 0) { + LOGE(&pdev->dev, + "Failed to allocate memory for tcm_hcd->in.buf\n"); + UNLOCK_BUFFER(tcm_hcd->in); + goto err_alloc_mem; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + + atomic_set(&tcm_hcd->helper.task, HELP_NONE); + + device_init_wakeup(&pdev->dev, 1); + + init_waitqueue_head(&tcm_hcd->hdl_wq); + + init_waitqueue_head(&tcm_hcd->reflash_wq); + atomic_set(&tcm_hcd->firmware_flashing, 0); + + if (!mod_pool.initialized) { + mutex_init(&mod_pool.mutex); + INIT_LIST_HEAD(&mod_pool.list); + mod_pool.initialized = true; + } + + spi = to_spi_device(tcm_hcd->pdev->dev.parent); + tcm_hcd->pinctrl = devm_pinctrl_get(&spi->dev); + if (IS_ERR(tcm_hcd->pinctrl)) { + LOGE(&pdev->dev, "Cannot find default pinctrl, ret = %d!\n", + PTR_ERR(tcm_hcd->pinctrl)); + } else { + tcm_hcd->pins_default = + pinctrl_lookup_state(tcm_hcd->pinctrl, "default"); + if (IS_ERR(tcm_hcd->pins_default)) + LOGE(&pdev->dev,"Cannot find pinctrl default %d!\n", + PTR_ERR(tcm_hcd->pins_default)); + else + pinctrl_select_state(tcm_hcd->pinctrl, + tcm_hcd->pins_default); + } + + retval = syna_tcm_regulator_init(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get regulators\n"); + goto err_regulator_init; + } + + retval = syna_tcm_enable_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable regulators\n"); + goto err_enable_regulator; + } + + retval = syna_tcm_config_gpio(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure GPIO's\n"); + goto err_config_gpio; + } + + /* detect the type of touch controller */ + retval = syna_tcm_sensor_detection(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to detect the sensor\n"); + goto err_sysfs_create_dir; + } + + sysfs_dir = kobject_create_and_add(PLATFORM_DRIVER_NAME, + &pdev->dev.kobj); + if (!sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + tcm_hcd->sysfs_dir = sysfs_dir; + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(tcm_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + tcm_hcd->dynamnic_config_sysfs_dir = + kobject_create_and_add(DYNAMIC_CONFIG_SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!tcm_hcd->dynamnic_config_sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create dynamic config sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dynamic_config_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) { + retval = sysfs_create_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create dynamic config sysfs file\n"); + goto err_sysfs_create_dynamic_config_file; + } + } + + tcm_hcd->tp_irq_debug = + proc_create("tp_irq_debug", 0664, NULL, &tp_irq_debug_ops); + +#ifdef REPORT_NOTIFIER + tcm_hcd->notifier_thread = kthread_run(syna_tcm_report_notifier, + tcm_hcd, "syna_tcm_report_notifier"); + if (IS_ERR(tcm_hcd->notifier_thread)) { + retval = PTR_ERR(tcm_hcd->notifier_thread); + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create and run tcm_hcd->notifier_thread\n"); + goto err_create_run_kthread; + } +#endif + + tcm_hcd->helper.workqueue = + create_singlethread_workqueue("syna_tcm_helper"); + INIT_WORK(&tcm_hcd->helper.work, syna_tcm_helper_work); + +#ifdef WATCHDOG_SW + tcm_hcd->watchdog.workqueue = + create_singlethread_workqueue("syna_tcm_watchdog"); + INIT_DELAYED_WORK(&tcm_hcd->watchdog.work, syna_tcm_watchdog_work); +#endif + + tcm_hcd->polling_workqueue = + create_singlethread_workqueue("syna_tcm_polling"); + INIT_DELAYED_WORK(&tcm_hcd->polling_work, syna_tcm_polling_work); + + tcm_hcd->event_wq = alloc_workqueue("syna_tcm_event_queue", + WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); + if (!tcm_hcd->event_wq) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create event_wq\n"); + retval = -ENOMEM; + goto err_pm_event_wq; + } + + INIT_WORK(&tcm_hcd->resume_work, syna_tcm_resume_work); + INIT_WORK(&tcm_hcd->early_suspend_work, syna_tcm_early_suspend_work); + INIT_WORK(&tcm_hcd->suspend_work, syna_tcm_suspend_work); + INIT_WORK(&tcm_hcd->fod_work, syna_tcm_fod_work); + INIT_WORK(&tcm_hcd->set_report_rate_work, syna_tcm_set_report_rate_work); + + tcm_hcd->fb_notifier = syna_tcm_noti_block; + if (mi_disp_register_client(&tcm_hcd->fb_notifier) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "ERROR: register notifier failed!\n"); + } + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE + tcm_hcd->game_wq = alloc_workqueue("syna_tcm_game_queue", + WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); + if (!tcm_hcd->game_wq) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create game_wq\n"); + retval = -ENOMEM; + goto err_xiaomi_touchfeature; + } + + INIT_WORK(&tcm_hcd->cmd_update_work, syna_tcm_cmd_update_work); + INIT_WORK(&tcm_hcd->grip_mode_work, syna_tcm_grip_mode_work); + mutex_init(&tcm_hcd->cmd_update_mutex); + + if (!p_xiaomi_touch_interfaces) { + p_xiaomi_touch_interfaces = + kmalloc(sizeof(struct xiaomi_touch_interface), GFP_KERNEL); + if (!p_xiaomi_touch_interfaces) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for p_xiaomi_touch_interfaces\n"); + retval = -ENOMEM; + goto err_xiaomi_touchfeature; + } + } + + mutex_init(&tcm_hcd->long_mode_value_mutex); + memset(p_xiaomi_touch_interfaces, 0x00, sizeof(struct xiaomi_touch_interface)); + p_xiaomi_touch_interfaces->setModeValue = syna_tcm_set_cur_value; + p_xiaomi_touch_interfaces->setModeLongValue = syna_tcm_set_mode_long_value; + p_xiaomi_touch_interfaces->getModeValue = syna_tcm_get_mode_value; + p_xiaomi_touch_interfaces->getModeAll = syna_tcm_get_mode_all; + p_xiaomi_touch_interfaces->resetMode = syna_tcm_reset_mode; + p_xiaomi_touch_interfaces->palm_sensor_write = syna_tcm_palm_sensor_write; + p_xiaomi_touch_interfaces->get_touch_rx_num = NULL; + p_xiaomi_touch_interfaces->get_touch_tx_num = NULL; + p_xiaomi_touch_interfaces->get_touch_x_resolution = NULL; + p_xiaomi_touch_interfaces->get_touch_y_resolution = NULL; + p_xiaomi_touch_interfaces->enable_touch_raw = NULL; + p_xiaomi_touch_interfaces->enable_clicktouch_raw = NULL; + p_xiaomi_touch_interfaces->enable_touch_delta = NULL; + p_xiaomi_touch_interfaces->panel_vendor_read = syna_tcm_panel_vendor_read; + p_xiaomi_touch_interfaces->panel_color_read = syna_tcm_panel_color_read; + p_xiaomi_touch_interfaces->panel_display_read = syna_tcm_panel_display_read; + p_xiaomi_touch_interfaces->touch_vendor_read = syna_tcm_touch_vendor_read; + tcm_hcd->syna_tcm_class = get_xiaomi_touch_class(); +#else + tcm_hcd->syna_tcm_class = class_create(THIS_MODULE, "touch"); +#endif + + /*tcm_hcd->syna_tcm_dev = + device_create(tcm_hcd->syna_tcm_class, NULL, + tcm_hcd->tp_dev_num, tcm_hcd, "tp_dev"); + if (IS_ERR(tcm_hcd->syna_tcm_dev)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create device for the sysfs!\n"); + } + dev_set_drvdata(tcm_hcd->syna_tcm_dev, tcm_hcd); + + retval = sysfs_create_file(&tcm_hcd->syna_tcm_dev->kobj, + &dev_attr_fod_test.attr); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create fod_test sysfs group!\n"); + }*/ + +#ifdef SYNAPTICS_POWERSUPPLY_CB + INIT_DELAYED_WORK(&tcm_hcd->power_supply_work, syna_tcm_power_supply_work); + tcm_hcd->charging_status = -1; + tcm_hcd->power_supply_notifier.notifier_call = syna_tcm_power_supply_event; + retval = power_supply_reg_notifier(&tcm_hcd->power_supply_notifier); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "usb online notifier registration error. err:%d\n", retval); + } +#endif + +#ifdef SYNAPTICS_DEBUGFS_ENABLE + tcm_hcd->debugfs = debugfs_create_dir("tp_debug", NULL); + if (tcm_hcd->debugfs) { + debugfs_create_file("switch_state", 0660, tcm_hcd->debugfs, tcm_hcd, + &tpdbg_operations); + } +#endif + + init_completion(&tcm_hcd->pm_resume_completion); + tcm_hcd->tp_lockdown_info_proc = + proc_create("tp_lockdown_info", 0444, NULL, &syna_tcm_lockdown_info_ops); + tcm_hcd->tp_fw_version_proc = + proc_create("tp_fw_version", 0444, NULL, &syna_tcm_fw_version_ops); + tcm_hcd->tp_data_dump_proc = + proc_create("tp_data_dump", 0444, NULL, &syna_tcm_datadump_ops); + tcm_hcd->tp_selftest_proc = + proc_create("tp_selftest", 0644, NULL, &syna_tcm_selftest_ops); + + /* since the fw is not ready for hdl devices */ + if (tcm_hcd->in_hdl_mode) + goto prepare_modules; + + + /* register and enable the interrupt in probe */ + /* if this is not the hdl device */ + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto err_enable_irq; + } + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt is registered\n"); + + /* ensure the app firmware is running */ + retval = syna_tcm_identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application firmware is not running\n"); + goto err_enable_irq; + } + /* initialize the touch reporting */ + retval = touch_init(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to initialze touch reporting\n"); + goto err_enable_irq; + } + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE + if (syna_tcm_read_touchmode_data()) { + LOGE(tcm_hcd->pdev->dev.parent, + "read touchmode data from IC failed!\n"); + } + syna_tcm_init_touchmode_data(); + // xiaomitouch_register_modedata(0, p_xiaomi_touch_interfaces); +#endif + +prepare_modules: + + /* prepare to add other modules */ + mod_pool.workqueue = + create_singlethread_workqueue("syna_tcm_module"); + INIT_WORK(&mod_pool.work, syna_tcm_module_work); + mod_pool.tcm_hcd = tcm_hcd; + mod_pool.queue_work = true; + queue_work(mod_pool.workqueue, &mod_pool.work); + + tp_probe_success = true; + return 0; + +err_enable_irq: + power_supply_unreg_notifier(&tcm_hcd->power_supply_notifier); + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + destroy_workqueue(tcm_hcd->polling_workqueue); + +#ifdef WATCHDOG_SW + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + destroy_workqueue(tcm_hcd->watchdog.workqueue); +#endif + + cancel_work_sync(&tcm_hcd->helper.work); + flush_workqueue(tcm_hcd->helper.workqueue); + destroy_workqueue(tcm_hcd->helper.workqueue); + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE +err_xiaomi_touchfeature: + syna_tcm_xiaomi_touchfeature_exit(tcm_hcd); +#endif + + if (tcm_hcd->tp_lockdown_info_proc) + remove_proc_entry("tp_lockdown_info", NULL); + if (tcm_hcd->tp_fw_version_proc) + remove_proc_entry("tp_fw_version", NULL); + if (tcm_hcd->tp_data_dump_proc) + remove_proc_entry("tp_data_dump", NULL); + if (tcm_hcd->tp_selftest_proc) + remove_proc_entry("tp_selftest", NULL); + tcm_hcd->tp_lockdown_info_proc = NULL; + tcm_hcd->tp_fw_version_proc = NULL; + tcm_hcd->tp_data_dump_proc = NULL; + tcm_hcd->tp_selftest_proc = NULL; + +#ifdef SYNAPTICS_DEBUGFS_ENABLE + syna_tcm_debugfs_exit(); +#endif + +err_pm_event_wq: + /* cancel_work_sync(&tcm_hcd->early_suspend_work); + cancel_work_sync(&tcm_hcd->suspend_work); + cancel_work_sync(&tcm_hcd->resume_work); + flush_workqueue(tcm_hcd->event_wq); */ + destroy_workqueue(tcm_hcd->event_wq); + +#ifdef REPORT_NOTIFIER + kthread_stop(tcm_hcd->notifier_thread); +err_create_run_kthread: +#endif + + mi_disp_unregister_client(&tcm_hcd->fb_notifier); + +err_sysfs_create_dynamic_config_file: + for (idx--; idx >= 0; idx--) { + sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + } + + kobject_put(tcm_hcd->dynamnic_config_sysfs_dir); + + idx = ARRAY_SIZE(attrs); + +err_sysfs_create_dynamic_config_dir: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(tcm_hcd->sysfs_dir); + +err_sysfs_create_dir: + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0); + + syna_tcm_enable_regulator(tcm_hcd, false); + +err_config_gpio: +err_enable_regulator: + devm_regulator_put(tcm_hcd->avdd); + devm_regulator_put(tcm_hcd->iovdd); + tcm_hcd->avdd = NULL; + tcm_hcd->iovdd = NULL; + +err_regulator_init: + device_init_wakeup(&pdev->dev, 0); + +err_alloc_mem: + RELEASE_BUFFER(tcm_hcd->report.buffer); + RELEASE_BUFFER(tcm_hcd->config); + RELEASE_BUFFER(tcm_hcd->temp); + RELEASE_BUFFER(tcm_hcd->resp); + RELEASE_BUFFER(tcm_hcd->out); + RELEASE_BUFFER(tcm_hcd->in); + kfree(tcm_hcd); + tcm_hcd = NULL; + gloab_tcm_hcd = NULL; + + return retval; +} + +static int syna_tcm_remove(struct platform_device *pdev) +{ + int idx; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_module_handler *tmp_handler; + struct syna_tcm_hcd *tcm_hcd = platform_get_drvdata(pdev); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + LOGI(tcm_hcd->pdev->dev.parent, "syna_tcm_remove enter!\n"); + touch_remove(tcm_hcd); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry_safe(mod_handler, + tmp_handler, + &mod_pool.list, + link) { + if (mod_handler->mod_cb->remove) + mod_handler->mod_cb->remove(tcm_hcd); + list_del(&mod_handler->link); + kfree(mod_handler); + } + } + + mod_pool.queue_work = false; + cancel_work_sync(&mod_pool.work); + flush_workqueue(mod_pool.workqueue); + destroy_workqueue(mod_pool.workqueue); + + mutex_unlock(&mod_pool.mutex); + + if (tcm_hcd->irq_enabled && bdata->irq_gpio >= 0) { + disable_irq(tcm_hcd->irq); + free_irq(tcm_hcd->irq, tcm_hcd); + } + + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + destroy_workqueue(tcm_hcd->polling_workqueue); + +#ifdef WATCHDOG_SW + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + destroy_workqueue(tcm_hcd->watchdog.workqueue); +#endif + + cancel_work_sync(&tcm_hcd->helper.work); + flush_workqueue(tcm_hcd->helper.workqueue); + destroy_workqueue(tcm_hcd->helper.workqueue); + + cancel_work_sync(&tcm_hcd->early_suspend_work); + cancel_work_sync(&tcm_hcd->suspend_work); + cancel_work_sync(&tcm_hcd->resume_work); + cancel_work_sync(&tcm_hcd->set_report_rate_work); + flush_workqueue(tcm_hcd->event_wq); + destroy_workqueue(tcm_hcd->event_wq); + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE + syna_tcm_xiaomi_touchfeature_exit(tcm_hcd); +#endif + + power_supply_unreg_notifier(&tcm_hcd->power_supply_notifier); + +#ifdef REPORT_NOTIFIER + kthread_stop(tcm_hcd->notifier_thread); +#endif + + mi_disp_unregister_client(&tcm_hcd->fb_notifier); + + if (tcm_hcd->tp_lockdown_info_proc) + remove_proc_entry("tp_lockdown_info", NULL); + if (tcm_hcd->tp_fw_version_proc) + remove_proc_entry("tp_fw_version", NULL); + if (tcm_hcd->tp_data_dump_proc) + remove_proc_entry("tp_data_dump", NULL); + if (tcm_hcd->tp_selftest_proc) + remove_proc_entry("tp_selftest", NULL); + tcm_hcd->tp_lockdown_info_proc = NULL; + tcm_hcd->tp_fw_version_proc = NULL; + tcm_hcd->tp_data_dump_proc = NULL; + tcm_hcd->tp_selftest_proc = NULL; + + for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) { + sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + } + + kobject_put(tcm_hcd->dynamnic_config_sysfs_dir); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(tcm_hcd->sysfs_dir); + + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0); + + syna_tcm_enable_regulator(tcm_hcd, false); + + device_init_wakeup(&pdev->dev, 0); + + RELEASE_BUFFER(tcm_hcd->report.buffer); + RELEASE_BUFFER(tcm_hcd->config); + RELEASE_BUFFER(tcm_hcd->temp); + RELEASE_BUFFER(tcm_hcd->resp); + RELEASE_BUFFER(tcm_hcd->out); + RELEASE_BUFFER(tcm_hcd->in); + + kfree(tcm_hcd); + tcm_hcd = NULL; + gloab_tcm_hcd = NULL; + + return 0; +} + +static void syna_tcm_shutdown(struct platform_device *pdev) +{ + int retval; + + retval = syna_tcm_remove(pdev); +} + +#ifdef CONFIG_PM +static int syna_pm_suspend(struct device *dev) { + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + LOGN(tcm_hcd->pdev->dev.parent, "%s enter!\n", __func__); + + if (!tcm_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter syna_pm_suspend!\n"); + return -1; + } + tcm_hcd->tp_pm_suspend = true; + reinit_completion(&tcm_hcd->pm_resume_completion); + + return 0; +} + +static int syna_pm_resume(struct device *dev) { + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + LOGN(tcm_hcd->pdev->dev.parent, "%s enter!\n", __func__); + + if (!tcm_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter syna_pm_resume!\n"); + return -1; + } + + tcm_hcd->tp_pm_suspend = false; + complete(&tcm_hcd->pm_resume_completion); + + return 0; +} + +static const struct dev_pm_ops syna_tcm_dev_pm_ops = { + .suspend = syna_pm_suspend, + .resume = syna_pm_resume, +}; +#endif + +static struct platform_driver syna_tcm_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &syna_tcm_dev_pm_ops, +#endif + }, + .probe = syna_tcm_probe, + .remove = syna_tcm_remove, + .shutdown = syna_tcm_shutdown, +}; + +static int __init syna_tcm_module_init(void) +{ + int retval; + + retval = syna_tcm_bus_init(); + if (retval < 0) + return retval; + + return platform_driver_register(&syna_tcm_driver); +} + +static void __exit syna_tcm_module_exit(void) +{ + platform_driver_unregister(&syna_tcm_driver); + + syna_tcm_bus_exit(); + + return; +} + +module_init(syna_tcm_module_init); +module_exit(syna_tcm_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_core.h b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_core.h new file mode 100755 index 000000000000..63cfbb4a4dc0 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_core.h @@ -0,0 +1,938 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_CORE_H_ +#define _SYNAPTICS_TCM_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../xiaomi/xiaomi_touch.h" +#define GRIP_RECT_NUM 12 +#define GRIP_PARAMETER_NUM 8 +#define TP_INFO_MAX_LENGTH 50 + +#define SYNAPTICS_DEBUGFS_ENABLE +#define SYNAPTICS_POWERSUPPLY_CB +enum charge_status { + NOT_CHARGING, + CHARGING, + WIRED_CHARGING, + WIRELESS_CHARGING, +}; +extern int power_supply_is_system_supplied(void); + +#ifdef SYNAPTICS_DEBUGFS_ENABLE +#include +#endif + +#include "./synaptics_tcm_xiaomi_board_data.h" + +#define SYNAPTICS_TCM_ID_PRODUCT (1 << 0) +#define SYNAPTICS_TCM_ID_VERSION 0x0201 +#define SYNAPTICS_TCM_ID_SUBVERSION 0 + +#define PLATFORM_DRIVER_NAME "synaptics_tcm" + +#define TOUCH_INPUT_NAME "synaptics_tcm_touch" +#define TOUCH_INPUT_PHYS_PATH "synaptics_tcm/touch_input" + +#define WAKEUP_GESTURE (1) + +/* The chunk size RD_CHUNK_SIZE/WR_CHUNK_SIZE will not apply in HDL sensors */ +#define RD_CHUNK_SIZE 256 /* read length limit in bytes, 0 = unlimited */ +#define WR_CHUNK_SIZE 256 /* write length limit in bytes, 0 = unlimited */ +#define HDL_RD_CHUNK_SIZE 0 /* For HDL, 0 = unlimited */ +#define HDL_WR_CHUNK_SIZE 0 /* For HDL, 0 = unlimited */ + +#define MESSAGE_HEADER_SIZE 4 +#define MESSAGE_MARKER 0xa5 +#define MESSAGE_PADDING 0x5a + +/* +#define REPORT_NOTIFIER +*/ + +/* +#define WATCHDOG_SW +*/ +#ifdef WATCHDOG_SW +#define RUN_WATCHDOG false +#define WATCHDOG_TRIGGER_COUNT 2 +#define WATCHDOG_DELAY_MS 1000 +#endif + +#define HOST_DOWNLOAD_WAIT_MS 100 +#define HOST_DOWNLOAD_TIMEOUT_MS 5000 + +#define SYNA_TCM_XIAOMI_TOUCHFEATURE + +#define PANEL_ORIENTATION_DEGREE_0 0 /* normal portrait orientation */ +#define PANEL_ORIENTATION_DEGREE_90 1 /* anticlockwise 90 degrees */ +#define PANEL_ORIENTATION_DEGREE_180 2 /* anticlockwise 180 degrees */ +#define PANEL_ORIENTATION_DEGREE_270 3 /* anticlockwise 270 degrees */ + +#define USE_KOBJ_SYSFS (1) + + +#define LOGx(func, dev, log, ...) \ + func(dev, "%s info: " log, __func__, ##__VA_ARGS__) + +#define LOGy(func, dev, log, ...) \ + func(dev, "%s:(line %d) " log, __func__, __LINE__, ##__VA_ARGS__) + +#define LOGD(dev, log, ...) LOGx(dev_dbg, dev, log, ##__VA_ARGS__) +#define LOGI(dev, log, ...) LOGx(dev_info, dev, log, ##__VA_ARGS__) +#define LOGN(dev, log, ...) LOGx(dev_notice, dev, log, ##__VA_ARGS__) +#define LOGW(dev, log, ...) LOGy(dev_warn, dev, log, ##__VA_ARGS__) +#define LOGE(dev, log, ...) LOGy(dev_err, dev, log, ##__VA_ARGS__) + +#define INIT_BUFFER(buffer, is_clone) \ + mutex_init(&buffer.buf_mutex); \ + buffer.clone = is_clone + +#define LOCK_BUFFER(buffer) \ + mutex_lock(&buffer.buf_mutex) + +#define UNLOCK_BUFFER(buffer) \ + mutex_unlock(&buffer.buf_mutex) + +#define RELEASE_BUFFER(buffer) \ + do { \ + if (buffer.clone == false) { \ + kfree(buffer.buf); \ + buffer.buf_size = 0; \ + buffer.data_length = 0; \ + } \ + } while (0) + +#define MAX(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define MIN(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define STR(x) #x + +#define CONCAT(a, b) a##b + +#define IS_NOT_FW_MODE(mode) \ + ((mode != MODE_APPLICATION_FIRMWARE) && (mode != MODE_HOSTDOWNLOAD_FIRMWARE)) + +#define IS_FW_MODE(mode) \ + ((mode == MODE_APPLICATION_FIRMWARE) || (mode == MODE_HOSTDOWNLOAD_FIRMWARE)) + +#define SHOW_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \ + struct device_attribute *attr, char *buf); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, S_IRUGO, \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + syna_tcm_store_error); + +#define STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, (S_IWUSR | S_IWGRP), \ + syna_tcm_show_error, \ + CONCAT(m_name##_sysfs, _##a_name##_store)); + +#define SHOW_STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \ + struct device_attribute *attr, char *buf); \ +\ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, (S_IRUGO | S_IWUSR | S_IWGRP), \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + CONCAT(m_name##_sysfs, _##a_name##_store)); + +#define ATTRIFY(a_name) (&dev_attr_##a_name) + +#if (USE_KOBJ_SYSFS) + +#define KOBJ_SHOW_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf); \ +\ +static struct kobj_attribute kobj_attr_##a_name = \ + __ATTR(a_name, S_IRUGO, \ + CONCAT(m_name##_sysfs, _##a_name##_show), NULL); + +#define KOBJ_STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct kobject *kobj, \ + struct kobj_attribute *attr, const char *buf, size_t count); \ +\ +static struct kobj_attribute kobj_attr_##a_name = \ + __ATTR(a_name, (S_IWUSR | S_IWGRP), \ + NULL, CONCAT(m_name##_sysfs, _##a_name##_store)); + +#define KOBJ_SHOW_STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf); \ +\ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct kobject *kobj, \ + struct kobj_attribute *attr, const char *buf, size_t count); \ +\ +static struct kobj_attribute kobj_attr_##a_name = \ + __ATTR(a_name, (S_IRUGO | S_IWUSR | S_IWGRP), \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + CONCAT(m_name##_sysfs, _##a_name##_store)); + +#define KOBJ_ATTRIFY(a_name) (&kobj_attr_##a_name) + +#endif + +enum module_type { + TCM_ZEROFLASH = 0, + TCM_REFLASH = 1, + TCM_DEVICE = 2, + TCM_TESTING = 3, + TCM_RECOVERY = 4, + TCM_DIAGNOSTICS = 5, + TCM_LAST, +}; + +enum boot_mode { + MODE_APPLICATION_FIRMWARE = 0x01, + MODE_HOSTDOWNLOAD_FIRMWARE = 0x02, + MODE_ROMBOOTLOADER = 0x04, + MODE_BOOTLOADER = 0x0b, + MODE_TDDI_BOOTLOADER = 0x0c, + MODE_TDDI_HOSTDOWNLOAD_BOOTLOADER = 0x0d, + MODE_PRODUCTIONTEST_FIRMWARE = 0x0e, +}; + +enum sensor_types { + TYPE_UNKNOWN = 0, + TYPE_FLASH = 1, + TYPE_F35 = 2, + TYPE_ROMBOOT = 3, +}; + +enum boot_status { + BOOT_STATUS_OK = 0x00, + BOOT_STATUS_BOOTING = 0x01, + BOOT_STATUS_APP_BAD_DISPLAY_CRC = 0xfc, + BOOT_STATUS_BAD_DISPLAY_CONFIG = 0xfd, + BOOT_STATUS_BAD_APP_FIRMWARE = 0xfe, + BOOT_STATUS_WARM_BOOT = 0xff, +}; + +enum app_status { + APP_STATUS_OK = 0x00, + APP_STATUS_BOOTING = 0x01, + APP_STATUS_UPDATING = 0x02, + APP_STATUS_BAD_APP_CONFIG = 0xff, +}; + +enum firmware_mode { + FW_MODE_BOOTLOADER = 0, + FW_MODE_APPLICATION = 1, + FW_MODE_PRODUCTION_TEST = 2, +}; + +enum dynamic_config_id { + DC_UNKNOWN = 0x00, + DC_NO_DOZE, + DC_DISABLE_NOISE_MITIGATION, + DC_INHIBIT_FREQUENCY_SHIFT, + DC_REQUESTED_FREQUENCY, + DC_DISABLE_HSYNC, + DC_REZERO_ON_EXIT_DEEP_SLEEP, + DC_CHARGER_CONNECTED, + DC_NO_BASELINE_RELAXATION, + DC_IN_WAKEUP_GESTURE_MODE, + DC_STIMULUS_FINGERS, + DC_GRIP_SUPPRESSION_ENABLED, + DC_ENABLE_THICK_GLOVE, + DC_ENABLE_GLOVE, + DC_ENABLE_TOUCH_AND_HOLD = 0xD4, + DC_GAME_MODE_CTRL = 0xD5, + DC_SWIPE_IIRFILTER = 0xD8, + DC_FAST_TAP_HYTERESIS = 0xD9, /* UP_THRESHOLD */ + DC_MOTION_TOLERANCE = 0xDA, + DC_TAP_JITTER = 0xDB, + DC_PALM_AREA_CHANGE = 0xDC, + DC_SET_REPORT_RATE = 0xE6, + DC_GESTURE_TYPE_ENABLE = 0xFE, +}; + +enum command { + CMD_NONE = 0x00, + CMD_CONTINUE_WRITE = 0x01, + CMD_IDENTIFY = 0x02, + CMD_RESET = 0x04, + CMD_ENABLE_REPORT = 0x05, + CMD_DISABLE_REPORT = 0x06, + CMD_GET_BOOT_INFO = 0x10, + CMD_ERASE_FLASH = 0x11, + CMD_WRITE_FLASH = 0x12, + CMD_READ_FLASH = 0x13, + CMD_RUN_APPLICATION_FIRMWARE = 0x14, + CMD_SPI_MASTER_WRITE_THEN_READ = 0x15, + CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16, + CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f, + CMD_GET_APPLICATION_INFO = 0x20, + CMD_GET_STATIC_CONFIG = 0x21, + CMD_SET_STATIC_CONFIG = 0x22, + CMD_GET_DYNAMIC_CONFIG = 0x23, + CMD_SET_DYNAMIC_CONFIG = 0x24, + CMD_GET_TOUCH_REPORT_CONFIG = 0x25, + CMD_SET_TOUCH_REPORT_CONFIG = 0x26, + CMD_REZERO = 0x27, + CMD_COMMIT_CONFIG = 0x28, + CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29, + CMD_PRODUCTION_TEST = 0x2a, + CMD_SET_CONFIG_ID = 0x2b, + CMD_ENTER_DEEP_SLEEP = 0x2c, + CMD_EXIT_DEEP_SLEEP = 0x2d, + CMD_GET_TOUCH_INFO = 0x2e, + CMD_GET_DATA_LOCATION = 0x2f, + CMD_DOWNLOAD_CONFIG = 0x30, + CMD_ENTER_PRODUCTION_TEST_MODE = 0x31, + CMD_GET_FEATURES = 0x32, + CMD_GET_ROMBOOT_INFO = 0x40, + CMD_WRITE_PROGRAM_RAM = 0x41, + CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE = 0x42, + CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED = 0x43, + CMD_ENTER_IO_BRIDGE_MODE = 0x44, + CMD_ROMBOOT_DOWNLOAD = 0x45, + CMD_MultiFunction = 0xC7, +}; + +enum cmd_c7_sub_cmd { + C7_SUB_CMD_SET_GRIP_ZONE = 0x00, + C7_SUB_CMD_GET_GRIP_ZONE = 0x01, +}; + +enum status_code { + STATUS_IDLE = 0x00, + STATUS_OK = 0x01, + STATUS_BUSY = 0x02, + STATUS_CONTINUED_READ = 0x03, + STATUS_CRC_ERROR = 0x0A, + STATUS_NOT_EXECUTED_IN_DEEP_SLEEP = 0x0b, + STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c, + STATUS_PREVIOUS_COMMAND_PENDING = 0x0d, + STATUS_NOT_IMPLEMENTED = 0x0e, + STATUS_ERROR = 0x0f, + STATUS_INVALID = 0xff, +}; + +enum report_type { + REPORT_IDENTIFY = 0x10, + REPORT_TOUCH = 0x11, + REPORT_DELTA = 0x12, + REPORT_RAW = 0x13, + REPORT_STATUS = 0x1b, + REPORT_PRINTF = 0x82, + REPORT_RID161 = 0xA1, + REPORT_HDL_ROMBOOT = 0xfd, + REPORT_HDL_F35 = 0xfe, +}; + +enum command_status { + CMD_IDLE = 0, + CMD_BUSY = 1, + CMD_ERROR = -1, +}; + +enum flash_area { + BOOTLOADER = 0, + BOOT_CONFIG, + APP_FIRMWARE, + APP_CONFIG, + DISP_CONFIG, + CUSTOM_OTP, + CUSTOM_LCM, + CUSTOM_OEM, + PPDT, +}; + +enum flash_data { + LCM_DATA = 1, + OEM_DATA, + PPDT_DATA, +}; + +enum helper_task { + HELP_NONE = 0, + HELP_RUN_APPLICATION_FIRMWARE, + HELP_SEND_REINIT_NOTIFICATION, + HELP_TOUCH_REINIT, + HELP_SEND_ROMBOOT_HDL, +}; + +enum fod_status { + FOD_STATUS_INVALID = -1, + FOD_STATUS_UNLOCKED = 0, + FOD_STATUS_UNLOCKING = 1, + FOD_STATUS_INPUT_FINGERPRINT = 2, + FOD_STATUS_UNLOCK_FAILED = 3, + FOD_STATUS_DELETED = 100, +}; + +enum grip_zone_type { + CORNER_ZONE = 0x00, + EDGE_ZONE = 0x01, + DEAD_ZONE = 0x02, + CORNERCASE2_ZONE = 0x03, +}; + +struct syna_tcm_helper { + atomic_t task; + struct work_struct work; + struct workqueue_struct *workqueue; +}; + +struct syna_tcm_watchdog { + bool run; + unsigned char count; + struct delayed_work work; + struct workqueue_struct *workqueue; +}; + +struct syna_tcm_buffer { + bool clone; + unsigned char *buf; + unsigned int buf_size; + unsigned int data_length; + struct mutex buf_mutex; +}; + +struct syna_tcm_report { + unsigned char id; + struct syna_tcm_buffer buffer; +}; + +struct syna_tcm_identification { + unsigned char version; + unsigned char mode; + unsigned char part_number[16]; + unsigned char build_id[4]; + unsigned char max_write_size[2]; +}; + +struct syna_tcm_boot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char erase_page_size_words[2]; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; + unsigned char boot_config_start_block[2]; + unsigned char boot_config_size_blocks[2]; + unsigned char display_config_start_block[4]; + unsigned char display_config_length_blocks[2]; + unsigned char backup_display_config_start_block[4]; + unsigned char backup_display_config_length_blocks[2]; + unsigned char custom_otp_start_block[2]; + unsigned char custom_otp_length_blocks[2]; +}; + +struct syna_tcm_app_info { + unsigned char version[2]; + unsigned char status[2]; + unsigned char static_config_size[2]; + unsigned char dynamic_config_size[2]; + unsigned char app_config_start_write_block[2]; + unsigned char app_config_size[2]; + unsigned char max_touch_report_config_size[2]; + unsigned char max_touch_report_payload_size[2]; + unsigned char customer_config_id[16]; + unsigned char max_x[2]; + unsigned char max_y[2]; + unsigned char max_objects[2]; + unsigned char num_of_buttons[2]; + unsigned char num_of_image_rows[2]; + unsigned char num_of_image_cols[2]; + unsigned char has_hybrid_data[2]; + unsigned char num_of_force_elecs[2]; +}; + +struct syna_tcm_romboot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; +}; + +struct syna_tcm_touch_info { + unsigned char image_2d_scale_factor[4]; + unsigned char image_0d_scale_factor[4]; + unsigned char hybrid_x_scale_factor[4]; + unsigned char hybrid_y_scale_factor[4]; +}; + +struct syna_tcm_message_header { + unsigned char marker; + unsigned char code; + unsigned char length[2]; +}; + +struct syna_tcm_features { + unsigned char byte_0_reserved; + unsigned char byte_1_reserved; + unsigned char dual_firmware:1; + unsigned char byte_2_reserved:7; +} __packed; + +struct syna_zone { + unsigned char x0[2]; + unsigned char y0[2]; + unsigned char x1[2]; + unsigned char y1[2]; + unsigned char x2[2]; + unsigned char y2[2]; + unsigned char x3[2]; + unsigned char y3[2]; + unsigned char x4[2]; + unsigned char y4[2]; + unsigned char x5[2]; + unsigned char y5[2]; + unsigned char x6[2]; + unsigned char y6[2]; + unsigned char x7[2]; + unsigned char y7[2]; +}; + +struct syna_grip_zone { + unsigned char type; /* BIT[3:0]: Panel Orientaton; BIT?:Game Mode */ + unsigned char reserved; + struct syna_zone edge_zone; + struct syna_zone dead_zone; + struct syna_zone corner_zone; +}; + +struct syna_tcm_hcd { + struct regulator *avdd; + struct regulator *iovdd; + pid_t isr_pid; + atomic_t command_status; + atomic_t host_downloading; + atomic_t firmware_flashing; + wait_queue_head_t hdl_wq; + wait_queue_head_t reflash_wq; + int irq; + bool do_polling; + bool in_suspending; + bool in_suspend; + bool irq_enabled; + bool in_hdl_mode; + bool is_detected; + bool wakeup_gesture_enabled; + bool fod_enabled; + bool fod_finger; + bool in_sleep; + bool fod_display_enabled; + unsigned char sensor_type; + unsigned char fb_ready; + unsigned char command; + unsigned char async_report_id; + unsigned char status_report_code; + unsigned char response_code; + unsigned int read_length; + unsigned int payload_length; + unsigned int packrat_number; + unsigned int rd_chunk_size; + unsigned int wr_chunk_size; + unsigned int app_status; + struct platform_device *pdev; + struct regulator *pwr_reg; + struct regulator *bus_reg; + struct kobject *sysfs_dir; + struct kobject *dynamnic_config_sysfs_dir; + struct mutex extif_mutex; + struct mutex reset_mutex; + struct mutex irq_en_mutex; + struct mutex io_ctrl_mutex; + struct mutex rw_ctrl_mutex; + struct mutex command_mutex; + struct mutex identify_mutex; + struct delayed_work polling_work; + struct workqueue_struct *polling_workqueue; + struct task_struct *notifier_thread; + bool tp_pm_suspend; + struct completion pm_resume_completion; + + struct class *syna_tcm_class; + struct device *syna_tcm_dev; + dev_t tp_dev_num; + struct notifier_block fb_notifier; + struct notifier_block power_supply_notifier; + + int charging_status; + struct delayed_work power_supply_work; + + struct syna_tcm_buffer in; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer temp; + struct syna_tcm_buffer config; + struct syna_tcm_report report; + struct syna_tcm_app_info app_info; + struct syna_tcm_boot_info boot_info; + struct syna_tcm_romboot_info romboot_info; + struct syna_tcm_touch_info touch_info; + struct syna_tcm_identification id_info; + struct syna_tcm_helper helper; + struct syna_tcm_watchdog watchdog; + struct syna_tcm_features features; + const struct syna_tcm_hw_interface *hw_if; + int (*reset)(struct syna_tcm_hcd *tcm_hcd); + int (*reset_n_reinit)(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd); + int (*sleep)(struct syna_tcm_hcd *tcm_hcd, bool en); + int (*identify)(struct syna_tcm_hcd *tcm_hcd, bool id); + int (*enable_irq)(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns); + int (*switch_mode)(struct syna_tcm_hcd *tcm_hcd, + enum firmware_mode mode); + int (*read_message)(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length); + int (*write_message)(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *payload, + unsigned int length, unsigned char **resp_buf, + unsigned int *resp_buf_size, unsigned int *resp_length, + unsigned char *response_code, + unsigned int polling_delay_ms); + int (*get_dynamic_config)(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short *value); + int (*set_dynamic_config)(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short value); + int (*get_data_location)(struct syna_tcm_hcd *tcm_hcd, + enum flash_area area, unsigned int *addr, + unsigned int *length); + int (*read_flash_data)(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output); + int (*syna_tcm_lockdown_info)(void); + void (*report_touch)(void); + void (*update_watchdog)(struct syna_tcm_hcd *tcm_hcd, bool en); + struct proc_dir_entry *tp_irq_debug; + struct work_struct early_suspend_work; + struct work_struct suspend_work; + struct work_struct resume_work; + struct work_struct fod_work; + struct work_struct set_report_rate_work; + struct workqueue_struct *event_wq; + int gamemode_enable; + int fod_icon_status; + int non_ui_status; + int finger_unlock_status; + int palm_sensor_enable; + int palm_enable_status; + int nonui_status; + bool aod_enable; + int fod_status; + bool doubletap_enable; + int power_status; + int charger_connected; + int report_rate_mode; + unsigned int gesture_type; + int maintenance_result; + struct mutex cmd_update_mutex; + +#ifdef SYNA_TCM_XIAOMI_TOUCHFEATURE + struct work_struct cmd_update_work; + struct work_struct grip_mode_work; + struct workqueue_struct *game_wq; +#endif + +#ifdef SYNAPTICS_DEBUGFS_ENABLE + struct dentry *debugfs; +#endif + + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + + bool lockdown_info_ready; + char lockdown_info[8]; + struct proc_dir_entry *tp_selftest_proc; + struct proc_dir_entry *tp_data_dump_proc; + struct proc_dir_entry *tp_fw_version_proc; + struct proc_dir_entry *tp_lockdown_info_proc; + int (*testing_xiaomi_report_data)(int report_type, char *buf); + int (*testing_xiaomi_self_test)(char *buf); + int (*testing_xiaomi_chip_id_read)(void); + + struct syna_tcm_xiaomi_board_data xiaomi_board_data; + struct mutex long_mode_value_mutex; + struct mutex esd_recovery_mutex; +}; + +struct syna_tcm_module_cb { + enum module_type type; + int (*init)(struct syna_tcm_hcd *tcm_hcd); + int (*remove)(struct syna_tcm_hcd *tcm_hcd); + int (*syncbox)(struct syna_tcm_hcd *tcm_hcd); +#ifdef REPORT_NOTIFIER + int (*asyncbox)(struct syna_tcm_hcd *tcm_hcd); +#endif + int (*reinit)(struct syna_tcm_hcd *tcm_hcd); + int (*suspend)(struct syna_tcm_hcd *tcm_hcd); + int (*resume)(struct syna_tcm_hcd *tcm_hcd); + int (*early_suspend)(struct syna_tcm_hcd *tcm_hcd); +}; + +struct syna_tcm_module_handler { + bool insert; + bool detach; + struct list_head link; + struct syna_tcm_module_cb *mod_cb; +}; + +struct syna_tcm_module_pool { + bool initialized; + bool queue_work; + struct mutex mutex; + struct list_head list; + struct work_struct work; + struct workqueue_struct *workqueue; + struct syna_tcm_hcd *tcm_hcd; +}; + +struct syna_tcm_bus_io { + unsigned char type; + int (*rmi_read)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr, + unsigned char *data, unsigned int length); + int (*rmi_write)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr, + unsigned char *data, unsigned int length); + int (*read)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length); + int (*write)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length); +}; + +struct syna_tcm_hw_interface { + struct syna_tcm_board_data *bdata; + const struct syna_tcm_bus_io *bus_io; +}; + +int syna_tcm_bus_init(void); + +void syna_tcm_bus_exit(void); + +int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert); + +int touch_init(struct syna_tcm_hcd *tcm_hcd); +int touch_remove(struct syna_tcm_hcd *tcm_hcd); +int touch_reinit(struct syna_tcm_hcd *tcm_hcd); +int touch_early_suspend(struct syna_tcm_hcd *tcm_hcd); +int touch_suspend(struct syna_tcm_hcd *tcm_hcd); +int touch_resume(struct syna_tcm_hcd *tcm_hcd); + +static inline int syna_tcm_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->rmi_read(tcm_hcd, addr, data, length); +} + +static inline int syna_tcm_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->rmi_write(tcm_hcd, addr, data, length); +} + +static inline int syna_tcm_read(struct syna_tcm_hcd *tcm_hcd, + unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->read(tcm_hcd, data, length); +} + +static inline int syna_tcm_write(struct syna_tcm_hcd *tcm_hcd, + unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->write(tcm_hcd, data, length); +} + +static inline ssize_t syna_tcm_show_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_err("%s: Attribute not readable\n", + __func__); + + return -EPERM; +} + +static inline ssize_t syna_tcm_store_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + pr_err("%s: Attribute not writable\n", + __func__); + + return -EPERM; +} + +static inline int secure_memcpy(unsigned char *dest, unsigned int dest_size, + const unsigned char *src, unsigned int src_size, + unsigned int count) +{ + if (dest == NULL || src == NULL) + return -EINVAL; + + if (count > dest_size || count > src_size) { + pr_err("%s: src_size = %d, dest_size = %d, count = %d\n", + __func__, src_size, dest_size, count); + return -EINVAL; + } + + memcpy((void *)dest, (const void *)src, count); + + return 0; +} + +static inline int syna_tcm_realloc_mem(struct syna_tcm_hcd *tcm_hcd, + struct syna_tcm_buffer *buffer, unsigned int size) +{ + int retval; + unsigned char *temp; + + if (size > buffer->buf_size) { + temp = buffer->buf; + + buffer->buf = kmalloc(size, GFP_KERNEL); + if (!(buffer->buf)) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to allocate memory\n", + __func__); + kfree(temp); + buffer->buf_size = 0; + return -ENOMEM; + } + + retval = secure_memcpy(buffer->buf, + size, + temp, + buffer->buf_size, + buffer->buf_size); + if (retval < 0) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + kfree(temp); + kfree(buffer->buf); + buffer->buf_size = 0; + return retval; + } + + kfree(temp); + buffer->buf_size = size; + } + + return 0; +} + +static inline int syna_tcm_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + struct syna_tcm_buffer *buffer, unsigned int size) +{ + if (size > buffer->buf_size) { + kfree(buffer->buf); + buffer->buf = kmalloc(size, GFP_KERNEL); + if (!(buffer->buf)) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to allocate memory\n", + __func__); + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Allocation size = %d\n", + __func__, size); + buffer->buf_size = 0; + buffer->data_length = 0; + return -ENOMEM; + } + buffer->buf_size = size; + } + + memset(buffer->buf, 0x00, buffer->buf_size); + buffer->data_length = 0; + + return 0; +} + +static inline unsigned int le2_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100; +} + +static inline unsigned int le4_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100 + + (unsigned int)src[2] * 0x10000 + + (unsigned int)src[3] * 0x1000000; +} + +static inline unsigned int ceil_div(unsigned int dividend, unsigned divisor) +{ + return (dividend + divisor - 1) / divisor; +} + +int touch_free_objects(struct syna_tcm_hcd *tcm_hcd); + +int touch_flush_slots(struct syna_tcm_hcd *tcm_hcd); + +extern int mi_disp_set_fod_queue_work(u32 fod_btn, bool from_touch); + +void touch_fod_test(int value); + +#endif diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_device.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_device.c new file mode 100755 index 000000000000..41e1ae19ba63 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_device.c @@ -0,0 +1,724 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include +#include "synaptics_tcm_core.h" + +#define CHAR_DEVICE_NAME "tcm" + +#define CONCURRENT true + +#define DEVICE_IOC_MAGIC 's' +#define DEVICE_IOC_RESET _IO(DEVICE_IOC_MAGIC, 0) /* 0x00007300 */ +#define DEVICE_IOC_IRQ _IOW(DEVICE_IOC_MAGIC, 1, int) /* 0x40047301 */ +#define DEVICE_IOC_RAW _IOW(DEVICE_IOC_MAGIC, 2, int) /* 0x40047302 */ +#define DEVICE_IOC_CONCURRENT _IOW(DEVICE_IOC_MAGIC, 3, int) /* 0x40047303 */ + +struct device_hcd { + dev_t dev_num; + bool raw_mode; + bool concurrent; + unsigned int ref_count; + struct cdev char_dev; + struct class *class; + struct device *device; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer report; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(device_remove_complete); + +static struct device_hcd *device_hcd; + +static int rmidev_major_num; + +static void device_capture_touch_report(unsigned int count) +{ + int retval; + unsigned char id; + unsigned int idx; + unsigned int size; + unsigned char *data; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + static bool report; + static unsigned int offset; + static unsigned int remaining_size; + + if (count < 2) + return; + + data = &device_hcd->resp.buf[0]; + + if (data[0] != MESSAGE_MARKER) + return; + + id = data[1]; + + size = 0; + + LOCK_BUFFER(device_hcd->report); + + switch (id) { + case REPORT_TOUCH: + if (count >= 4) { + remaining_size = le2_to_uint(&data[2]); + } else { + report = false; + goto exit; + } + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->report, + remaining_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd->report.buf\n"); + report = false; + goto exit; + } + idx = 4; + size = count - idx; + offset = 0; + report = true; + break; + case STATUS_CONTINUED_READ: + if (report == false) + goto exit; + if (count >= 2) { + idx = 2; + size = count - idx; + } + break; + default: + goto exit; + } + + if (size) { + size = MIN(size, remaining_size); + retval = secure_memcpy(&device_hcd->report.buf[offset], + device_hcd->report.buf_size - offset, + &data[idx], + count - idx, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy touch report data\n"); + report = false; + goto exit; + } else { + offset += size; + remaining_size -= size; + device_hcd->report.data_length += size; + } + } + + if (remaining_size) + goto exit; + + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = device_hcd->report.buf; + tcm_hcd->report.buffer.buf_size = device_hcd->report.buf_size; + tcm_hcd->report.buffer.data_length = device_hcd->report.data_length; + + tcm_hcd->report_touch(); + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + + report = false; + +exit: + UNLOCK_BUFFER(device_hcd->report); + + return; +} + +static int device_capture_touch_report_config(unsigned int count) +{ + int retval; + unsigned int size; + unsigned int buf_size; + unsigned char *data; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (device_hcd->raw_mode) { + if (count < 3) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid write data\n"); + return -EINVAL; + } + + size = le2_to_uint(&device_hcd->out.buf[1]); + + if (count - 3 < size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incomplete write data\n"); + return -EINVAL; + } + + if (!size) + return 0; + + data = &device_hcd->out.buf[3]; + buf_size = device_hcd->out.buf_size - 3; + } else { + size = count - 1; + + if (!size) + return 0; + + data = &device_hcd->out.buf[1]; + buf_size = device_hcd->out.buf_size - 1; + } + + LOCK_BUFFER(tcm_hcd->config); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->config, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->config.buf\n"); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + retval = secure_memcpy(tcm_hcd->config.buf, + tcm_hcd->config.buf_size, + data, + buf_size, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy touch report config data\n"); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + tcm_hcd->config.data_length = size; + + UNLOCK_BUFFER(tcm_hcd->config); + + return 0; +} + +#ifdef HAVE_UNLOCKED_IOCTL +static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +#else +static int device_ioctl(struct inode *inp, struct file *filp, unsigned int cmd, + unsigned long arg) +#endif +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = 0; + + switch (cmd) { + case DEVICE_IOC_RESET: + retval = tcm_hcd->reset_n_reinit(tcm_hcd, false, true); + break; + case DEVICE_IOC_IRQ: + if (arg == 0) + retval = tcm_hcd->enable_irq(tcm_hcd, false, false); + else if (arg == 1) + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + break; + case DEVICE_IOC_RAW: + if (arg == 0) { + device_hcd->raw_mode = false; +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + } else if (arg == 1) { + device_hcd->raw_mode = true; +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + } + break; + case DEVICE_IOC_CONCURRENT: + if (arg == 0) + device_hcd->concurrent = false; + else if (arg == 1) + device_hcd->concurrent = true; + break; + default: + retval = -ENOTTY; + break; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static loff_t device_llseek(struct file *filp, loff_t off, int whence) +{ + return -EINVAL; +} + +static ssize_t device_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (count == 0) + return 0; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(device_hcd->resp); + + if (device_hcd->raw_mode) { + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->resp, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd->resp.buf\n"); + UNLOCK_BUFFER(device_hcd->resp); + goto exit; + } + + retval = tcm_hcd->read_message(tcm_hcd, + device_hcd->resp.buf, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + UNLOCK_BUFFER(device_hcd->resp); + goto exit; + } + } else { + if (count != device_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid length information\n"); + UNLOCK_BUFFER(device_hcd->resp); + retval = -EINVAL; + goto exit; + } + } + + if (copy_to_user(buf, device_hcd->resp.buf, count)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data to user space\n"); + UNLOCK_BUFFER(device_hcd->resp); + retval = -EINVAL; + goto exit; + } + + if (!device_hcd->concurrent) + goto skip_concurrent; + + if (tcm_hcd->report_touch == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Unable to report touch\n"); + device_hcd->concurrent = false; + } + + if (device_hcd->raw_mode) + device_capture_touch_report(count); + +skip_concurrent: + UNLOCK_BUFFER(device_hcd->resp); + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t device_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (count == 0) + return 0; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(device_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->out, + count == 1 ? count + 1 : count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd->out.buf\n"); + UNLOCK_BUFFER(device_hcd->out); + goto exit; + } + + if (copy_from_user(device_hcd->out.buf, buf, count)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data from user space\n"); + UNLOCK_BUFFER(device_hcd->out); + retval = -EINVAL; + goto exit; + } + + LOCK_BUFFER(device_hcd->resp); + + if (device_hcd->raw_mode) { + retval = tcm_hcd->write_message(tcm_hcd, + device_hcd->out.buf[0], + &device_hcd->out.buf[1], + count - 1, + NULL, + NULL, + NULL, + NULL, + 0); + } else { + mutex_lock(&tcm_hcd->reset_mutex); + retval = tcm_hcd->write_message(tcm_hcd, + device_hcd->out.buf[0], + &device_hcd->out.buf[1], + count - 1, + &device_hcd->resp.buf, + &device_hcd->resp.buf_size, + &device_hcd->resp.data_length, + NULL, + 0); + mutex_unlock(&tcm_hcd->reset_mutex); + } + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command 0x%02x\n", + device_hcd->out.buf[0]); + UNLOCK_BUFFER(device_hcd->resp); + UNLOCK_BUFFER(device_hcd->out); + goto exit; + } + + if (count && device_hcd->out.buf[0] == CMD_SET_TOUCH_REPORT_CONFIG) { + retval = device_capture_touch_report_config(count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to capture touch report config\n"); + } + } + + UNLOCK_BUFFER(device_hcd->out); + + if (device_hcd->raw_mode) + retval = count; + else + retval = device_hcd->resp.data_length; + + UNLOCK_BUFFER(device_hcd->resp); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int device_open(struct inode *inp, struct file *filp) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (device_hcd->ref_count < 1) { + device_hcd->ref_count++; + retval = 0; + } else { + retval = -EACCES; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int device_release(struct inode *inp, struct file *filp) +{ + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (device_hcd->ref_count) + device_hcd->ref_count--; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return 0; +} + +static char *device_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + *mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + return kasprintf(GFP_KERNEL, "%s/%s", PLATFORM_DRIVER_NAME, + dev_name(dev)); +} + +static int device_create_class(void) +{ + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (device_hcd->class != NULL) + return 0; + + device_hcd->class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME); + + if (IS_ERR(device_hcd->class)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create class\n"); + return -ENODEV; + } + + device_hcd->class->devnode = device_devnode; + + return 0; +} + +static const struct file_operations device_fops = { + .owner = THIS_MODULE, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = device_ioctl, +#ifdef HAVE_COMPAT_IOCTL + .compat_ioctl = device_ioctl, +#endif +#else + .ioctl = device_ioctl, +#endif + .llseek = device_llseek, + .read = device_read, + .write = device_write, + .open = device_open, + .release = device_release, +}; + +static int device_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + dev_t dev_num; + /* const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; */ + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + device_hcd = kzalloc(sizeof(*device_hcd), GFP_KERNEL); + if (!device_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd\n"); + return -ENOMEM; + } + + device_hcd->tcm_hcd = tcm_hcd; + + device_hcd->concurrent = CONCURRENT; + + INIT_BUFFER(device_hcd->out, false); + INIT_BUFFER(device_hcd->resp, false); + INIT_BUFFER(device_hcd->report, false); + + if (rmidev_major_num) { + dev_num = MKDEV(rmidev_major_num, 0); + retval = register_chrdev_region(dev_num, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register char device\n"); + goto err_register_chrdev_region; + } + } else { + retval = alloc_chrdev_region(&dev_num, 0, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate char device\n"); + goto err_alloc_chrdev_region; + } + + rmidev_major_num = MAJOR(dev_num); + } + + device_hcd->dev_num = dev_num; + tcm_hcd->tp_dev_num = dev_num; + + cdev_init(&device_hcd->char_dev, &device_fops); + + retval = cdev_add(&device_hcd->char_dev, dev_num, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add char device\n"); + goto err_add_chardev; + } + + retval = device_create_class(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create class\n"); + goto err_create_class; + } + + device_hcd->device = device_create(device_hcd->class, NULL, + device_hcd->dev_num, NULL, CHAR_DEVICE_NAME"%d", + MINOR(device_hcd->dev_num)); + if (IS_ERR(device_hcd->device)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create device\n"); + retval = -ENODEV; + goto err_create_device; + } + +/* LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s---irq_gpio = %d\n", __func__,bdata->irq_gpio); + if (bdata->irq_gpio >= 0) { + retval = gpio_export(bdata->irq_gpio, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to export GPIO\n"); + } else { + retval = gpio_export_link(&tcm_hcd->pdev->dev, + "attn", bdata->irq_gpio); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to export GPIO link\n"); + } + } + } */ + + return 0; + +err_create_device: + class_destroy(device_hcd->class); + +err_create_class: + cdev_del(&device_hcd->char_dev); + +err_add_chardev: + unregister_chrdev_region(dev_num, 1); + +err_alloc_chrdev_region: +err_register_chrdev_region: + RELEASE_BUFFER(device_hcd->report); + RELEASE_BUFFER(device_hcd->resp); + RELEASE_BUFFER(device_hcd->out); + + kfree(device_hcd); + device_hcd = NULL; + + return retval; +} + +static int device_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!device_hcd) + goto exit; + + device_destroy(device_hcd->class, device_hcd->dev_num); + + class_destroy(device_hcd->class); + + cdev_del(&device_hcd->char_dev); + + unregister_chrdev_region(device_hcd->dev_num, 1); + + RELEASE_BUFFER(device_hcd->report); + RELEASE_BUFFER(device_hcd->resp); + RELEASE_BUFFER(device_hcd->out); + + kfree(device_hcd); + device_hcd = NULL; + +exit: + complete(&device_remove_complete); + + return 0; +} + +static int device_reinit(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!device_hcd) { + retval = device_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb device_module = { + .type = TCM_DEVICE, + .init = device_init, + .remove = device_remove, + .syncbox = NULL, +#ifdef REPORT_NOTIFIER + .asyncbox = NULL, +#endif + .reinit = device_reinit, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init device_module_init(void) +{ + return syna_tcm_add_module(&device_module, true); +} + +static void __exit device_module_exit(void) +{ + syna_tcm_add_module(&device_module, false); + + wait_for_completion(&device_remove_complete); + + return; +} + +module_init(device_module_init); +module_exit(device_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Device Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_diagnostics.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_diagnostics.c new file mode 100755 index 000000000000..d5523c22f791 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_diagnostics.c @@ -0,0 +1,573 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include "synaptics_tcm_core.h" + +#define SYSFS_DIR_NAME "diagnostics" + +enum pingpong_state { + PING = 0, + PONG = 1, +}; + +struct diag_hcd { + pid_t pid; + unsigned char report_type; + enum pingpong_state state; + struct kobject *sysfs_dir; + struct kernel_siginfo sigio; + struct task_struct *task; + struct syna_tcm_buffer ping; + struct syna_tcm_buffer pong; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(diag_remove_complete); + +static struct diag_hcd *diag_hcd; + +STORE_PROTOTYPE(diag, pid) +SHOW_PROTOTYPE(diag, size) +STORE_PROTOTYPE(diag, type) +SHOW_PROTOTYPE(diag, rows) +SHOW_PROTOTYPE(diag, cols) +SHOW_PROTOTYPE(diag, hybrid) +SHOW_PROTOTYPE(diag, buttons) + +static struct device_attribute *attrs[] = { + ATTRIFY(pid), + ATTRIFY(size), + ATTRIFY(type), + ATTRIFY(rows), + ATTRIFY(cols), + ATTRIFY(hybrid), + ATTRIFY(buttons), +}; + +static ssize_t diag_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "data", + .mode = S_IRUGO, + }, + .size = 0, + .read = diag_sysfs_data_show, +}; + +static ssize_t diag_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + diag_hcd->pid = input; + + if (diag_hcd->pid) { + diag_hcd->task = pid_task(find_vpid(diag_hcd->pid), + PIDTYPE_PID); + if (!diag_hcd->task) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to locate task\n"); + retval = -EINVAL; + goto exit; + } + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (diag_hcd->state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + diag_hcd->ping.data_length); + + UNLOCK_BUFFER(diag_hcd->ping); + } else { + LOCK_BUFFER(diag_hcd->pong); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + diag_hcd->pong.data_length); + + UNLOCK_BUFFER(diag_hcd->pong); + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + diag_hcd->report_type = (unsigned char)input; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return count; +} + +static ssize_t diag_sysfs_rows_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int rows; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + rows = le2_to_uint(app_info->num_of_image_rows); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", rows); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_cols_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", cols); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_hybrid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int hybrid; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + hybrid = le2_to_uint(app_info->has_hybrid_data); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", hybrid); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_buttons_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int buttons; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + buttons = le2_to_uint(app_info->num_of_buttons); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", buttons); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = 0; + + if (diag_hcd->state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + if (diag_hcd->ping.data_length == 0) { + readlen = 0; + goto exit; + } + + readlen = MIN(count, diag_hcd->ping.data_length - pos); + + if (diag_hcd->ping.data_length) { + retval = secure_memcpy(buf, + count, + &diag_hcd->ping.buf[pos], + diag_hcd->ping.buf_size - pos, + readlen); + } + + UNLOCK_BUFFER(diag_hcd->ping); + } else { + LOCK_BUFFER(diag_hcd->pong); + + if (diag_hcd->pong.data_length == 0) { + readlen = 0; + goto exit; + } + + readlen = MIN(count, diag_hcd->pong.data_length - pos); + + if (diag_hcd->pong.data_length) { + retval = secure_memcpy(buf, + count, + &diag_hcd->pong.buf[pos], + diag_hcd->pong.buf_size - pos, + readlen); + } + + UNLOCK_BUFFER(diag_hcd->pong); + } + +exit: + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + } else { + retval = readlen; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static void diag_report(void) +{ + int retval; + static enum pingpong_state state = PING; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &diag_hcd->ping, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for diag_hcd->ping.buf\n"); + UNLOCK_BUFFER(diag_hcd->ping); + return; + } + + retval = secure_memcpy(diag_hcd->ping.buf, + diag_hcd->ping.buf_size, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(diag_hcd->ping); + return; + } + + diag_hcd->ping.data_length = tcm_hcd->report.buffer.data_length; + + UNLOCK_BUFFER(diag_hcd->ping); + + diag_hcd->state = state; + state = PONG; + } else { + LOCK_BUFFER(diag_hcd->pong); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &diag_hcd->pong, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for diag_hcd->pong.buf\n"); + UNLOCK_BUFFER(diag_hcd->pong); + return; + } + + retval = secure_memcpy(diag_hcd->pong.buf, + diag_hcd->pong.buf_size, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(diag_hcd->pong); + return; + } + + diag_hcd->pong.data_length = tcm_hcd->report.buffer.data_length; + + UNLOCK_BUFFER(diag_hcd->pong); + + diag_hcd->state = state; + state = PING; + } + + if (diag_hcd->pid) + send_sig_info(SIGIO, &diag_hcd->sigio, diag_hcd->task); + + return; +} + +static int diag_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + + diag_hcd = kzalloc(sizeof(*diag_hcd), GFP_KERNEL); + if (!diag_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for diag_hcd\n"); + return -ENOMEM; + } + + diag_hcd->tcm_hcd = tcm_hcd; + diag_hcd->state = PING; + + INIT_BUFFER(diag_hcd->ping, false); + INIT_BUFFER(diag_hcd->pong, false); + + memset(&diag_hcd->sigio, 0x00, sizeof(diag_hcd->sigio)); + diag_hcd->sigio.si_signo = SIGIO; + diag_hcd->sigio.si_code = SI_USER; + + diag_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!diag_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(diag_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(diag_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(diag_hcd->sysfs_dir); + +err_sysfs_create_dir: + RELEASE_BUFFER(diag_hcd->pong); + RELEASE_BUFFER(diag_hcd->ping); + + kfree(diag_hcd); + diag_hcd = NULL; + + return retval; +} + +static int diag_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!diag_hcd) + goto exit; + + sysfs_remove_bin_file(diag_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(diag_hcd->sysfs_dir); + + RELEASE_BUFFER(diag_hcd->pong); + RELEASE_BUFFER(diag_hcd->ping); + + kfree(diag_hcd); + diag_hcd = NULL; + +exit: + complete(&diag_remove_complete); + + return 0; +} + +static int diag_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!diag_hcd) + return 0; + + if (tcm_hcd->report.id == diag_hcd->report_type) + diag_report(); + + return 0; +} + +static int diag_reinit(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!diag_hcd) { + retval = diag_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb diag_module = { + .type = TCM_DIAGNOSTICS, + .init = diag_init, + .remove = diag_remove, + .syncbox = diag_syncbox, +#ifdef REPORT_NOTIFIER + .asyncbox = NULL, +#endif + .reinit = diag_reinit, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init diag_module_init(void) +{ + return syna_tcm_add_module(&diag_module, true); +} + +static void __exit diag_module_exit(void) +{ + syna_tcm_add_module(&diag_module, false); + + wait_for_completion(&diag_remove_complete); + + return; +} + +module_init(diag_module_init); +module_exit(diag_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Diagnostics Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_recovery.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_recovery.c new file mode 100755 index 000000000000..72550313905c --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_recovery.c @@ -0,0 +1,1300 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include "synaptics_tcm_core.h" +#define SET_UP_RECOVERY_MODE true +#define ENABLE_SYSFS_RECOVERY true +#define SYSFS_DIR_NAME "recovery" +#define IHEX_BUF_SIZE (2048 * 1024) +#define DATA_BUF_SIZE (512 * 1024) +#define IHEX_RECORD_SIZE 14 +#define PDT_START_ADDR 0x00e9 +#define UBL_FN_NUMBER 0x35 +#define F35_CHUNK_SIZE 16 +#define F35_CHUNK_SIZE_WORDS 8 +#define F35_ERASE_ALL_WAIT_MS 5000 +#define F35_ERASE_ALL_POLL_MS 100 +#define F35_DATA5_OFFSET 5 +#define F35_CTRL3_OFFSET 18 +#define F35_RESET_COMMAND 16 +#define F35_ERASE_ALL_COMMAND 3 +#define F35_WRITE_CHUNK_COMMAND 2 +#define F35_READ_FLASH_STATUS_COMMAND 1 +#define BINARY_FILE_MAGIC_VALUE 0xaa55 +#define FLASH_PAGE_SIZE 256 +#define STATUS_CHECK_US_MIN 5000 +#define STATUS_CHECK_US_MAX 10000 +#define STATUS_CHECK_RETRY 50 +#define DATA_ROMBOOT_BUF_SIZE (512 * 256) + +struct block_data { + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; +}; + +struct image_info { + unsigned int packrat_number; + struct block_data boot_config; + struct block_data app_firmware; + struct block_data app_config; + struct block_data disp_config; +}; + +struct rmi_pdt_entry { + unsigned char query_base_addr; + unsigned char command_base_addr; + unsigned char control_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count:3; + unsigned char reserved_1:2; + unsigned char fn_version:2; + unsigned char reserved_2:1; + unsigned char fn_number; +} __packed; + +struct rmi_addr { + unsigned short query_base; + unsigned short command_base; + unsigned short control_base; + unsigned short data_base; +}; + +struct recovery_hcd { + bool set_up_recovery_mode; + unsigned char chunk_buf[F35_CHUNK_SIZE + 3]; + unsigned char out_buf[3]; + unsigned char *ihex_buf; + unsigned char *data_buf; + unsigned int ihex_size; + unsigned int ihex_records; + unsigned int data_entries; + struct image_info image_info; + struct kobject *sysfs_dir; + struct rmi_addr f35_addr; + struct syna_tcm_hcd *tcm_hcd; +}; + +enum flash_command { + JEDEC_PAGE_PROGRAM = 0x02, + JEDEC_READ_STATUS = 0x05, + JEDEC_WRITE_ENABLE = 0x06, + JEDEC_CHIP_ERASE = 0xc7, +}; + +struct flash_param { + unsigned char spi_param; + unsigned char clk_div; + unsigned char mode; + unsigned short read_size; + unsigned char jedec_cmd; +} __packed; + +DECLARE_COMPLETION(recovery_remove_complete); + +static struct recovery_hcd *recovery_hcd; +static void recovery_do_romboot_recovery(struct syna_tcm_hcd *tcm_hcd); +static int recovery_do_f35_recovery(void); +static int recovery_add_romboot_data_entry(unsigned char data); + +STORE_PROTOTYPE(recovery, f35_recovery) +STORE_PROTOTYPE(recovery, romboot_recovery) + + +static struct device_attribute *attrs[] = { + ATTRIFY(f35_recovery), + ATTRIFY(romboot_recovery), +}; + +static ssize_t recovery_sysfs_ihex_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "ihex", + .mode = (S_IWUSR | S_IWGRP), + }, + .size = 0, + .write = recovery_sysfs_ihex_store, +}; + +static ssize_t recovery_sysfs_romboot_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input) + recovery_do_romboot_recovery(tcm_hcd); + + return retval; +} + +static int recovery_parse_romboot_ihex(void) +{ + int retval; + unsigned char colon; + unsigned char *buf; + unsigned int addr; + unsigned int type; + unsigned int addrl; + unsigned int addrh; + unsigned int data0; + unsigned int data1; + unsigned int count; + unsigned int words; + unsigned int offset; + unsigned int record; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + struct image_info *image_info = &recovery_hcd->image_info; + + if (!(recovery_hcd->ihex_buf)) { + LOGE(tcm_hcd->pdev->dev.parent, + "No ihex data\n"); + return -ENOBUFS; + } + + words = 0; + offset = 0; + buf = recovery_hcd->ihex_buf; + recovery_hcd->data_entries = 0; + recovery_hcd->ihex_records = recovery_hcd->ihex_size / IHEX_RECORD_SIZE; + memset(recovery_hcd->data_buf, 0xff, DATA_ROMBOOT_BUF_SIZE); + + for (record = 0; record < recovery_hcd->ihex_records; record++) { + buf[(record + 1) * IHEX_RECORD_SIZE - 1] = 0x00; + retval = sscanf(&buf[record * IHEX_RECORD_SIZE], + "%c%02x%02x%02x%02x%02x%02x", + &colon, + &count, + &addrh, + &addrl, + &type, + &data0, + &data1); + if (retval != 7) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read ihex record\n"); + return -EINVAL; + } + + if (type == 0x00) { + addr = (addrh << 8) + addrl; + addr += offset; + + recovery_hcd->data_entries = addr; + + retval = recovery_add_romboot_data_entry(data0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry for data0\n"); + return retval; + } + + retval = recovery_add_romboot_data_entry(data1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry for data1\n"); + return retval; + } + + words++; + } else if (type == 0x02) { + offset = (data0 << 8) + data1; + offset <<= 4; + + recovery_hcd->data_entries = offset; + } + } + + image_info->app_firmware.size = DATA_ROMBOOT_BUF_SIZE; + image_info->app_firmware.data = recovery_hcd->data_buf; + + return 0; +} + +static int recovery_flash_romboot_command(struct syna_tcm_hcd *tcm_hcd, + unsigned char flash_command, unsigned char *out, + unsigned int out_size, unsigned char *in, + unsigned int in_size) +{ + int retval; + unsigned char *payld_buf = NULL; + unsigned char *resp_buf = NULL; + unsigned int resp_buf_size; + unsigned int resp_length; + struct flash_param flash_param = {1, 0x19, 0, 0, 0}; + + flash_param.read_size = in_size; + + flash_param.jedec_cmd = flash_command; + + resp_buf = NULL; + resp_buf_size = 0; + + payld_buf = kzalloc(sizeof(flash_param) + out_size, + GFP_KERNEL); + + memcpy(payld_buf, &flash_param, sizeof(flash_param)); + + memcpy(payld_buf + sizeof(flash_param), out, out_size); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED, + payld_buf, + sizeof(flash_param) + out_size, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 20); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED"); + goto exit; + } + + if (in_size && (in_size <= resp_length)) + memcpy(in, resp_buf, in_size); + +exit: + kfree(payld_buf); + kfree(resp_buf); + return retval; +} + +static int recovery_flash_romboot_status(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + unsigned char status; + + for (idx = 0; idx < STATUS_CHECK_RETRY; idx++) { + retval = recovery_flash_romboot_command(tcm_hcd, + JEDEC_READ_STATUS, NULL, 0, + &status, sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write JEDEC_READ_STATUS"); + return retval; + } + + usleep_range(STATUS_CHECK_US_MIN, STATUS_CHECK_US_MAX); + + if (!status) + break; + } + + if (status) + retval = -EIO; + else + retval = status; + + return retval; +} + +static int recovery_flash_romboot_erase(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + LOGN(tcm_hcd->pdev->dev.parent, + "%s", __func__); + + retval = recovery_flash_romboot_command(tcm_hcd, JEDEC_WRITE_ENABLE, + NULL, 0, NULL, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write JEDEC_WRITE_ENABLE"); + return retval; + } + + retval = recovery_flash_romboot_command(tcm_hcd, JEDEC_CHIP_ERASE, + NULL, 0, NULL, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write JEDEC_CHIP_ERASE"); + return retval; + } + + retval = recovery_flash_romboot_status(tcm_hcd); + if (retval < 0) + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get correct status: %d", retval); + + return retval; +} + +static int recovery_flash_romboot_reprogram(struct syna_tcm_hcd *tcm_hcd, + unsigned char *image_buf, unsigned int image_size) +{ + int retval; + int idx; + unsigned short img_header; + unsigned int pages; + unsigned char buf[FLASH_PAGE_SIZE + 3]; + + img_header = image_buf[0] | image_buf[1] << 8; + if ((image_size % FLASH_PAGE_SIZE) || + (img_header != BINARY_FILE_MAGIC_VALUE)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Wrong image file"); + LOGE(tcm_hcd->pdev->dev.parent, + "image_size = %d, img_header = 0x%04x", + image_size, img_header); + return -EINVAL; + } + + pages = image_size / FLASH_PAGE_SIZE; + + LOGE(tcm_hcd->pdev->dev.parent, + "image_size = %d, img_header = 0x%04x, pages = %d", + image_size, img_header, pages); + + for (idx = 0; idx < pages; idx++) { + retval = recovery_flash_romboot_command(tcm_hcd, + JEDEC_WRITE_ENABLE, NULL, 0, NULL, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write JEDEC_WRITE_ENABLE"); + return retval; + } + + buf[0] = FLASH_PAGE_SIZE * idx >> 16; + buf[1] = FLASH_PAGE_SIZE * idx >> 8; + buf[2] = FLASH_PAGE_SIZE * idx; + + memcpy(buf + 3, image_buf + FLASH_PAGE_SIZE * idx, + FLASH_PAGE_SIZE); + + retval = recovery_flash_romboot_command(tcm_hcd, + JEDEC_PAGE_PROGRAM, buf, sizeof(buf), NULL, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write JEDEC_READ_STATUS"); + return retval; + } + + retval = recovery_flash_romboot_status(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get correct status: %d", + retval); + return retval; + } + + } + + return retval; +} + +static void recovery_do_romboot_recovery(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned int image_size; + unsigned char *out_buf = NULL; + + LOGN(tcm_hcd->pdev->dev.parent, + "%s\n", __func__); + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + if (tcm_hcd->id_info.mode != MODE_ROMBOOTLOADER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Not in romboot\n"); + goto do_program_exit; + } + + retval = recovery_parse_romboot_ihex(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse ihex data\n"); + goto do_program_exit; + } + + image_size = (unsigned int)recovery_hcd->image_info.app_firmware.size; + out_buf = kzalloc(image_size, GFP_KERNEL); + + retval = secure_memcpy(out_buf, + recovery_hcd->image_info.app_firmware.size, + recovery_hcd->image_info.app_firmware.data, + recovery_hcd->image_info.app_firmware.size, + recovery_hcd->image_info.app_firmware.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + goto do_program_exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "image_size = %d\n", + image_size); + + retval = recovery_flash_romboot_erase(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase chip"); + goto do_program_exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Do recovery_flash_romboot_reprogram"); + retval = recovery_flash_romboot_reprogram(tcm_hcd, out_buf, image_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to program chip"); + goto do_program_exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Do reset and re-init"); + retval = tcm_hcd->reset_n_reinit(tcm_hcd, true, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reset"); + } + +do_program_exit: + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + kfree(out_buf); + + return; +} + +static ssize_t recovery_sysfs_f35_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + recovery_hcd->set_up_recovery_mode = true; + else if (input == 2) + recovery_hcd->set_up_recovery_mode = false; + else + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (recovery_hcd->ihex_size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get ihex data\n"); + retval = -EINVAL; + goto exit; + } + + if (recovery_hcd->ihex_size % IHEX_RECORD_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid ihex data\n"); + retval = -EINVAL; + goto exit; + } + + recovery_hcd->ihex_records = recovery_hcd->ihex_size / IHEX_RECORD_SIZE; + + retval = recovery_do_f35_recovery(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do recovery\n"); + goto exit; + } + + retval = count; + +exit: + recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t recovery_sysfs_ihex_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = secure_memcpy(&recovery_hcd->ihex_buf[pos], + IHEX_BUF_SIZE - pos, + buf, + count, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy ihex data\n"); + recovery_hcd->ihex_size = 0; + goto exit; + } + + recovery_hcd->ihex_size = pos + count; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int recovery_device_reset(void) +{ + int retval; + unsigned char command; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + command = F35_RESET_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + msleep(bdata->reset_delay_ms); + + return 0; +} + +static int recovery_add_data_entry(unsigned char data) +{ + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (recovery_hcd->data_entries >= DATA_BUF_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Reached data buffer size limit\n"); + return -EINVAL; + } + + recovery_hcd->data_buf[recovery_hcd->data_entries++] = data; + + return 0; +} + +static int recovery_add_romboot_data_entry(unsigned char data) +{ + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (recovery_hcd->data_entries >= DATA_ROMBOOT_BUF_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Reached data buffer size limit\n"); + return -EINVAL; + } + + recovery_hcd->data_buf[recovery_hcd->data_entries++] = data; + + return 0; +} + +static int recovery_add_padding(unsigned int *words) +{ + int retval; + unsigned int padding; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + padding = (F35_CHUNK_SIZE_WORDS - *words % F35_CHUNK_SIZE_WORDS); + padding %= F35_CHUNK_SIZE_WORDS; + + while (padding) { + retval = recovery_add_data_entry(0xff); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(0xff); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + (*words)++; + padding--; + } + + return 0; +} + +static int recovery_parse_f35_ihex(void) +{ + int retval; + unsigned char colon; + unsigned char *buf; + unsigned int addr; + unsigned int type; + unsigned int addrl; + unsigned int addrh; + unsigned int data0; + unsigned int data1; + unsigned int count; + unsigned int words; + unsigned int offset; + unsigned int record; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + words = 0; + + offset = 0; + + buf = recovery_hcd->ihex_buf; + + recovery_hcd->data_entries = 0; + + for (record = 0; record < recovery_hcd->ihex_records; record++) { + buf[(record + 1) * IHEX_RECORD_SIZE - 1] = 0x00; + retval = sscanf(&buf[record * IHEX_RECORD_SIZE], + "%c%02x%02x%02x%02x%02x%02x", + &colon, + &count, + &addrh, + &addrl, + &type, + &data0, + &data1); + if (retval != 7) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read ihex record\n"); + return -EINVAL; + } + + if (type == 0x00) { + if ((words % F35_CHUNK_SIZE_WORDS) == 0) { + addr = (addrh << 8) + addrl; + addr += offset; + addr >>= 4; + + retval = recovery_add_data_entry(addr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(addr >> 8); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + } + + retval = recovery_add_data_entry(data0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(data1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + words++; + } else if (type == 0x02) { + retval = recovery_add_padding(&words); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add padding\n"); + return retval; + } + + offset = (data0 << 8) + data1; + offset <<= 4; + } + } + + retval = recovery_add_padding(&words); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add padding\n"); + return retval; + } + + return 0; +} + +static int recovery_check_status(void) +{ + int retval; + unsigned char status; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + retval = syna_tcm_rmi_read(tcm_hcd, + recovery_hcd->f35_addr.data_base, + &status, + sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read status\n"); + return retval; + } + + status = status & 0x1f; + + if (status != 0x00) { + LOGE(tcm_hcd->pdev->dev.parent, + "Recovery mode status = 0x%02x\n", + status); + return -EINVAL; + } + + return 0; +} + +static int recovery_write_flash(void) +{ + int retval; + unsigned char *data_ptr; + unsigned int chunk_buf_size; + unsigned int chunk_data_size; + unsigned int entries_written; + unsigned int entries_to_write; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + entries_written = 0; + + data_ptr = recovery_hcd->data_buf; + + chunk_buf_size = sizeof(recovery_hcd->chunk_buf); + + chunk_data_size = chunk_buf_size - 1; + + recovery_hcd->chunk_buf[chunk_buf_size - 1] = F35_WRITE_CHUNK_COMMAND; + + while (entries_written < recovery_hcd->data_entries) { + entries_to_write = F35_CHUNK_SIZE + 2; + + retval = secure_memcpy(recovery_hcd->chunk_buf, + chunk_buf_size - 1, + data_ptr, + recovery_hcd->data_entries - entries_written, + entries_to_write); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy chunk data\n"); + return retval; + } + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base, + recovery_hcd->chunk_buf, + chunk_buf_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write chunk data\n"); + return retval; + } + + data_ptr += entries_to_write; + entries_written += entries_to_write; + } + + retval = recovery_check_status(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get no error recovery mode status\n"); + return retval; + } + + return 0; +} + +static int recovery_poll_erase_completion(void) +{ + int retval; + unsigned char status; + unsigned char command; + unsigned char data_base; + unsigned int timeout; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + timeout = F35_ERASE_ALL_WAIT_MS; + + data_base = recovery_hcd->f35_addr.data_base; + + do { + command = F35_READ_FLASH_STATUS_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.command_base, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + do { + retval = syna_tcm_rmi_read(tcm_hcd, + recovery_hcd->f35_addr.command_base, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read command status\n"); + return retval; + } + + if (command == 0x00) + break; + + if (timeout == 0) + break; + + msleep(F35_ERASE_ALL_POLL_MS); + timeout -= F35_ERASE_ALL_POLL_MS; + } while (true); + + if (command != 0 && timeout == 0) { + retval = -EINVAL; + goto exit; + } + + retval = syna_tcm_rmi_read(tcm_hcd, + data_base + F35_DATA5_OFFSET, + &status, + sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read flash status\n"); + return retval; + } + + if ((status & 0x01) == 0x00) + break; + + if (timeout == 0) { + retval = -EINVAL; + goto exit; + } + + msleep(F35_ERASE_ALL_POLL_MS); + timeout -= F35_ERASE_ALL_POLL_MS; + } while (true); + + retval = 0; + +exit: + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get erase completion\n"); + } + + return retval; +} + +static int recovery_erase_flash(void) +{ + int retval; + unsigned char command; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + command = F35_ERASE_ALL_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + if (recovery_hcd->f35_addr.command_base) { + retval = recovery_poll_erase_completion(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for erase completion\n"); + return retval; + } + } else { + msleep(F35_ERASE_ALL_WAIT_MS); + } + + retval = recovery_check_status(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get no error recovery mode status\n"); + return retval; + } + + return 0; +} + +static int recovery_set_up_recovery_mode(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION_FIRMWARE) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } + + retval = tcm_hcd->write_message(tcm_hcd, + recovery_hcd->out_buf[0], + &recovery_hcd->out_buf[1], + 2, + NULL, + NULL, + NULL, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_REBOOT_TO_ROM_BOOTLOADER)); + return retval; + } + + msleep(bdata->reset_delay_ms); + + return 0; +} + +static int recovery_do_f35_recovery(void) +{ + int retval; + struct rmi_pdt_entry p_entry; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + retval = recovery_parse_f35_ihex(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse ihex data\n"); + return retval; + } + + if (recovery_hcd->set_up_recovery_mode) { + retval = recovery_set_up_recovery_mode(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up recovery mode\n"); + return retval; + } + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_START_ADDR, + (unsigned char *)&p_entry, + sizeof(p_entry)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read PDT entry\n"); + return retval; + } + + if (p_entry.fn_number != UBL_FN_NUMBER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to find F$35\n"); + return -ENODEV; + } + + recovery_hcd->f35_addr.query_base = p_entry.query_base_addr; + recovery_hcd->f35_addr.command_base = p_entry.command_base_addr; + recovery_hcd->f35_addr.control_base = p_entry.control_base_addr; + recovery_hcd->f35_addr.data_base = p_entry.data_base_addr; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of recovery\n"); + + retval = recovery_erase_flash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Flash erased\n"); + + retval = recovery_write_flash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Flash written\n"); + + retval = recovery_device_reset(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "End of recovery\n"); + + if (recovery_hcd->set_up_recovery_mode) + return 0; + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + return retval; + } + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode)) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + return retval; + } + } + + return 0; +} + +static int recovery_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + int idx; + + recovery_hcd = kzalloc(sizeof(*recovery_hcd), GFP_KERNEL); + if (!recovery_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for recovery_hcd\n"); + return -ENOMEM; + } + + recovery_hcd->ihex_buf = kzalloc(IHEX_BUF_SIZE, GFP_KERNEL); + if (!recovery_hcd->ihex_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for recovery_hcd->ihex_buf\n"); + goto err_allocate_ihex_buf; + } + + recovery_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL); + if (!recovery_hcd->data_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for recovery_hcd->data_buf\n"); + goto err_allocate_data_buf; + } + + recovery_hcd->tcm_hcd = tcm_hcd; + + recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE; + + recovery_hcd->out_buf[0] = CMD_REBOOT_TO_ROM_BOOTLOADER; + recovery_hcd->out_buf[1] = 0; + recovery_hcd->out_buf[2] = 0; + + if (ENABLE_SYSFS_RECOVERY == false) + goto init_finished; + + recovery_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!recovery_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(recovery_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(recovery_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + +init_finished: + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(recovery_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(recovery_hcd->sysfs_dir); + +err_sysfs_create_dir: + kfree(recovery_hcd->data_buf); +err_allocate_data_buf: + kfree(recovery_hcd->ihex_buf); +err_allocate_ihex_buf: + kfree(recovery_hcd); + recovery_hcd = NULL; + + return retval; +} + +static int recovery_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!recovery_hcd) + goto exit; + + if (ENABLE_SYSFS_RECOVERY == true) { + sysfs_remove_bin_file(recovery_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + sysfs_remove_file(recovery_hcd->sysfs_dir, + &(*attrs[idx]).attr); + } + + kobject_put(recovery_hcd->sysfs_dir); + } + + kfree(recovery_hcd->data_buf); + kfree(recovery_hcd->ihex_buf); + kfree(recovery_hcd); + recovery_hcd = NULL; + +exit: + complete(&recovery_remove_complete); + + return 0; +} + +static int recovery_reinit(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!recovery_hcd) { + retval = recovery_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb recovery_module = { + .type = TCM_RECOVERY, + .init = recovery_init, + .remove = recovery_remove, + .syncbox = NULL, +#ifdef REPORT_NOTIFIER + .asyncbox = NULL, +#endif + .reinit = recovery_reinit, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init recovery_module_init(void) +{ + return syna_tcm_add_module(&recovery_module, true); +} + +static void __exit recovery_module_exit(void) +{ + syna_tcm_add_module(&recovery_module, false); + + wait_for_completion(&recovery_remove_complete); + + return; +} + +module_init(recovery_module_init); +module_exit(recovery_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Recovery Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_reflash.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_reflash.c new file mode 100755 index 000000000000..6b4d5b072698 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_reflash.c @@ -0,0 +1,2689 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include "synaptics_tcm_core.h" + +#define STARTUP_REFLASH +#define FORCE_REFLASH false +#define ENABLE_SYS_REFLASH true +#define SYSFS_DIR_NAME "reflash" +#define CUSTOM_DIR_NAME "custom" +#define FW_IMAGE_NAME "s3908p_xiaomi_l9_spi.img" +#define FW_IMAGE_NAME_MANUAL "s3908p_xiaomi_l9_spi.img" +#define BOOT_CONFIG_ID "BOOT_CONFIG" +#define APP_CODE_ID "APP_CODE" +#define PROD_TEST_ID "APP_PROD_TEST" +#define APP_CONFIG_ID "APP_CONFIG" +#define DISP_CONFIG_ID "DISPLAY" +#define FB_READY_COUNT 2 +#define FB_READY_WAIT_MS 100 +#define FB_READY_TIMEOUT_S 30 +#define IMAGE_FILE_MAGIC_VALUE 0x4818472b +#define FLASH_AREA_MAGIC_VALUE 0x7c05e516 +#define BOOT_CONFIG_SIZE 8 +#define BOOT_CONFIG_SLOTS 30 +#define IMAGE_BUF_SIZE (512 * 1024) +#define ERASE_FLASH_DELAY_MS 500 +#define WRITE_FLASH_DELAY_MS 20 +#define REFLASH (1 << 0) +#define FORCE_UPDATE (1 << 1) +#define APP_CFG_UPDATE (1 << 2) +#define DISP_CFG_UPDATE (1 << 3) +#define BOOT_CFG_UPDATE (1 << 4) +#define BOOT_CFG_LOCKDOWN (1 << 5) + +#define reflash_show_data() \ +{ \ + LOCK_BUFFER(reflash_hcd->read); \ +\ + readlen = MIN(count, reflash_hcd->read.data_length - pos); \ +\ + retval = secure_memcpy(buf, \ + count, \ + &reflash_hcd->read.buf[pos], \ + reflash_hcd->read.buf_size - pos, \ + readlen); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to copy read data\n"); \ + } else { \ + retval = readlen; \ + } \ +\ + UNLOCK_BUFFER(reflash_hcd->read); \ +} + +enum update_area { + NONE = 0, + FIRMWARE_CONFIG, + CONFIG_ONLY, +}; + +struct app_config_header { + unsigned short magic_value[4]; + unsigned char checksum[4]; + unsigned char length[2]; + unsigned char build_id[4]; + unsigned char customer_config_id[16]; +}; + +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; + +struct block_data { + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; +}; + +struct image_info { + struct block_data boot_config; + struct block_data app_firmware; + struct block_data prod_test_firmware; + struct block_data app_config; + struct block_data disp_config; +}; + +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; + +struct boot_config { + union { + unsigned char i2c_address; + struct { + unsigned char cpha:1; + unsigned char cpol:1; + unsigned char word0_b2__7:6; + } __packed; + }; + unsigned char attn_polarity:1; + unsigned char attn_drive:2; + unsigned char attn_pullup:1; + unsigned char word0_b12__14:3; + unsigned char used:1; + unsigned short customer_part_id; + unsigned short boot_timeout; + unsigned short continue_on_reset:1; + unsigned short word3_b1__15:15; +} __packed; + +struct reflash_hcd { + bool force_update; + bool disp_cfg_update; + bool reflash_by_manual; + const unsigned char *image; + unsigned char *image_buf; + unsigned int image_size; + unsigned int page_size; + unsigned int write_block_size; + unsigned int max_write_payload_size; + const struct firmware *fw_entry; + struct mutex reflash_mutex; + struct kobject *sysfs_dir; + struct kobject *custom_dir; + struct work_struct work; + struct workqueue_struct *workqueue; + struct image_info image_info; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer read; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(reflash_remove_complete); + +static struct reflash_hcd *reflash_hcd; +static int reflash_get_fw_image(void); + +static int reflash_read_data(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output); + +static int reflash_update_custom_otp(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_custom_lcm(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_custom_oem(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_boot_config(bool lock); +static int reflash_update_app_config(bool reset); +static int reflash_update_disp_config(bool reset); +static int reflash_do_reflash(void); + +#if (USE_KOBJ_SYSFS) +KOBJ_SHOW_PROTOTYPE(reflash, oem_info) +KOBJ_STORE_PROTOTYPE(reflash, reflash) + +static struct kobj_attribute *attrs[] = { + KOBJ_ATTRIFY(oem_info), + KOBJ_ATTRIFY(reflash), +}; +#else +SHOW_PROTOTYPE(reflash, oem_info) +STORE_PROTOTYPE(reflash, reflash) + +static struct device_attribute *attrs[] = { + ATTRIFY(oem_info), + ATTRIFY(reflash), +}; +#endif + +static ssize_t reflash_sysfs_image_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lockdown_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lockdown_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lcm_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lcm_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_oem_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_oem_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_cs_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attrs[] = { + { + .attr = { + .name = "image", + .mode = (S_IWUSR | S_IWGRP), + }, + .size = 0, + .write = reflash_sysfs_image_store, + }, + { + .attr = { + .name = "lockdown", + .mode = (S_IRUGO | S_IWUSR | S_IWGRP), + }, + .size = 0, + .read = reflash_sysfs_lockdown_show, + .write = reflash_sysfs_lockdown_store, + }, + { + .attr = { + .name = "lcm", + .mode = (S_IRUGO | S_IWUSR | S_IWGRP), + }, + .size = 0, + .read = reflash_sysfs_lcm_show, + .write = reflash_sysfs_lcm_store, + }, + { + .attr = { + .name = "oem", + .mode = (S_IRUGO | S_IWUSR | S_IWGRP), + }, + .size = 0, + .read = reflash_sysfs_oem_show, + .write = reflash_sysfs_oem_store, + }, + { + .attr = { + .name = "customer_serialization", + .mode = (S_IRUGO | S_IRUSR | S_IRGRP), + }, + .size = 0, + .read = reflash_sysfs_cs_show, + }, +}; + +static int syna_tcm_lockdown_info(void) +{ + int retval = 0; + int i = 0; + int boot_config_size = 0; + unsigned char data; + unsigned char *p; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + if (tcm_hcd->lockdown_info_ready) { + LOGI(tcm_hcd->pdev->dev.parent, + "lockdown info is ready, not read again from flash\n"); + goto exit; + } + + retval = reflash_read_data(BOOT_CONFIG, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read oem_info data\n"); + goto exit; + } + + boot_config_size = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS; + + LOCK_BUFFER(reflash_hcd->read); + if ( boot_config_size != reflash_hcd->read.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "incorrect data len [%d], expected len [%d]\n", + reflash_hcd->read.data_length, boot_config_size); + retval = -EINVAL; + } else { + for (i = (BOOT_CONFIG_SLOTS - 1); i >= 0; i--) { + data = reflash_hcd->read.buf[i * BOOT_CONFIG_SIZE + 0]; + if (data != 0) { + break; + } + } + i = (i < 0) ? 0 : i; + p = &reflash_hcd->read.buf[i*BOOT_CONFIG_SIZE + 0]; + + for (i = 0; i < 8; i++) { + tcm_hcd->lockdown_info[i] = p[i]; + LOGD(tcm_hcd->pdev->dev.parent, + "%s: p[%d] = 0x%02x, PAGE_SIZE = %d\n", __func__, i , p[i], PAGE_SIZE); + } + tcm_hcd->lockdown_info_ready = true; + retval = 0; + } + + UNLOCK_BUFFER(reflash_hcd->read); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +#if (USE_KOBJ_SYSFS) +static ssize_t reflash_sysfs_oem_info_show(struct kobject *kobj, + struct kobj_attribute *attributes, char *buf) +#else +static ssize_t reflash_sysfs_oem_info_show(struct device *dev, + struct device_attribute *attributes, char *buf) +#endif +{ + int retval = 0; + int i = 0; + int boot_config_size = 0; + unsigned char data; + unsigned char *p; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(BOOT_CONFIG, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read oem_info data\n"); + goto exit; + } + + boot_config_size = BOOT_CONFIG_SIZE*BOOT_CONFIG_SLOTS; + + LOCK_BUFFER(reflash_hcd->read); + if ( boot_config_size != reflash_hcd->read.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "incorrect data len [%d], expected len [%d]\n", + reflash_hcd->read.data_length, boot_config_size); + retval = -EINVAL; + } else { + for (i = (BOOT_CONFIG_SLOTS - 1); i >= 0; i--) { + data = reflash_hcd->read.buf[i*BOOT_CONFIG_SIZE + 0]; + if (data != 0) { + break; + } + } + i = (i < 0) ? 0 : i; + p = &reflash_hcd->read.buf[i*BOOT_CONFIG_SIZE + 0]; + retval = snprintf(buf, PAGE_SIZE, "OEM_INFO:%02X %02X %02X %02X %02X %02X %02X %02X\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + } + UNLOCK_BUFFER(reflash_hcd->read); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +#if (USE_KOBJ_SYSFS) +static ssize_t reflash_sysfs_reflash_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +#else +static ssize_t reflash_sysfs_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +#endif +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + if (reflash_hcd->image_size != 0) + reflash_hcd->image = reflash_hcd->image_buf; + + reflash_hcd->force_update = input & FORCE_UPDATE ? true : false; + + reflash_hcd->reflash_by_manual = true; + + if (input & REFLASH || input & FORCE_UPDATE) { + retval = reflash_do_reflash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reflash\n"); + goto exit; + } + } + + if ((input & ~(REFLASH | FORCE_UPDATE)) == 0) { + retval = count; + goto exit; + } + + retval = reflash_get_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + if (input & BOOT_CFG_LOCKDOWN) { + retval = reflash_update_boot_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to lockdown boot config\n"); + goto exit; + } + } else if (input & BOOT_CFG_UPDATE) { + retval = reflash_update_boot_config(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update boot config\n"); + goto exit; + } + } + + if (input & REFLASH || input & FORCE_UPDATE) { + retval = count; + goto exit; + } + + if (input & DISP_CFG_UPDATE) { + if (input & APP_CFG_UPDATE) + retval = reflash_update_disp_config(false); + else + retval = reflash_update_disp_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash display config\n"); + goto exit; + } + } + + if (input & APP_CFG_UPDATE) { + retval = reflash_update_app_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application config\n"); + goto exit; + } + } + + retval = count; + +exit: + if (reflash_hcd->fw_entry) { + release_firmware(reflash_hcd->fw_entry); + reflash_hcd->fw_entry = NULL; + } + + reflash_hcd->reflash_by_manual = false; + reflash_hcd->image = NULL; + reflash_hcd->image_size = 0; + reflash_hcd->force_update = FORCE_REFLASH; + mutex_unlock(&reflash_hcd->reflash_mutex); + pm_relax(&tcm_hcd->pdev->dev); + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_image_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = secure_memcpy(&reflash_hcd->image_buf[pos], + IMAGE_BUF_SIZE - pos, + buf, + count, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware image data\n"); + reflash_hcd->image_size = 0; + goto exit; + } + + reflash_hcd->image_size = pos + count; + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lockdown_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_OTP, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read lockdown data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lockdown_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + pm_stay_awake(&tcm_hcd->pdev->dev); + mutex_lock(&reflash_hcd->reflash_mutex); + retval = reflash_update_custom_otp(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom OTP data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + pm_relax(&tcm_hcd->pdev->dev); + mutex_unlock(&tcm_hcd->extif_mutex); + return retval; +} + +static ssize_t reflash_sysfs_lcm_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + mutex_lock(&reflash_hcd->reflash_mutex); + retval = reflash_read_data(CUSTOM_LCM, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read LCM data\n"); + goto exit; + } + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + mutex_unlock(&tcm_hcd->extif_mutex); + return retval; +} + +static ssize_t reflash_sysfs_lcm_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + pm_stay_awake(&tcm_hcd->pdev->dev); + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_lcm(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom LCM data\n"); + goto exit; + } + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + pm_relax(&tcm_hcd->pdev->dev); + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_oem_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + mutex_lock(&reflash_hcd->reflash_mutex); + retval = reflash_read_data(CUSTOM_OEM, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read OEM data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + mutex_unlock(&tcm_hcd->extif_mutex); + return retval; +} + +static ssize_t reflash_sysfs_oem_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + pm_stay_awake(&tcm_hcd->pdev->dev); + mutex_lock(&reflash_hcd->reflash_mutex); + retval = reflash_update_custom_oem(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom OEM data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + pm_relax(&tcm_hcd->pdev->dev); + mutex_unlock(&tcm_hcd->extif_mutex); + return retval; +} + +static ssize_t reflash_sysfs_cs_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + mutex_lock(&reflash_hcd->reflash_mutex); + retval = reflash_read_data(BOOT_CONFIG, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read OEM data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + mutex_unlock(&tcm_hcd->extif_mutex); + return retval; +} + +static int reflash_set_up_flash_access(void) +{ + int retval; + unsigned int temp; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION_FIRMWARE) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } + + temp = tcm_hcd->boot_info.write_block_size_words; + reflash_hcd->write_block_size = temp * 2; + + temp = le2_to_uint(tcm_hcd->boot_info.erase_page_size_words); + reflash_hcd->page_size = temp * 2; + + temp = le2_to_uint(tcm_hcd->boot_info.max_write_payload_size); + reflash_hcd->max_write_payload_size = temp; + + LOGD(tcm_hcd->pdev->dev.parent, + "Write block size = %d\n", + reflash_hcd->write_block_size); + + LOGD(tcm_hcd->pdev->dev.parent, + "Page size = %d\n", + reflash_hcd->page_size); + + LOGD(tcm_hcd->pdev->dev.parent, + "Max write payload size = %d\n", + reflash_hcd->max_write_payload_size); + + if (reflash_hcd->write_block_size > (tcm_hcd->wr_chunk_size - 5)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Write block size greater than available chunk space\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_parse_fw_image(void) +{ + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct image_info *image_info; + struct area_descriptor *descriptor; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *image; + const unsigned char *content; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + image = reflash_hcd->image; + image_info = &reflash_hcd->image_info; + header = (struct image_header *)image; + + reflash_hcd->disp_cfg_update = false; + + magic_value = le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid image file magic value\n"); + return -EINVAL; + } + + memset(image_info, 0x00, sizeof(*image_info)); + + offset = sizeof(*header); + num_of_areas = le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2; + checksum = le4_to_uint(descriptor->checksum); + + if (0 == strncmp((char *)descriptor->id_string, + BOOT_CONFIG_ID, + strlen(BOOT_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config checksum error\n"); + return -EINVAL; + } + image_info->boot_config.size = length; + image_info->boot_config.data = content; + image_info->boot_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config flash address = 0x%08x\n", + flash_addr); + } else if (0 == strncmp((char *)descriptor->id_string, + APP_CODE_ID, + strlen(APP_CODE_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application firmware checksum error\n"); + return -EINVAL; + } + image_info->app_firmware.size = length; + image_info->app_firmware.data = content; + image_info->app_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware flash address = 0x%08x\n", + flash_addr); + } else if (0 == strncmp((char *)descriptor->id_string, + PROD_TEST_ID, + strlen(PROD_TEST_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Production test firmware checksum error\n"); + return -EINVAL; + } + image_info->prod_test_firmware.size = length; + image_info->prod_test_firmware.data = content; + image_info->prod_test_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Production test firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Production test firmware flash address = 0x%08x\n", + flash_addr); + } else if (0 == strncmp((char *)descriptor->id_string, + APP_CONFIG_ID, + strlen(APP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application config checksum error\n"); + return -EINVAL; + } + image_info->app_config.size = length; + image_info->app_config.data = content; + image_info->app_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config flash address = 0x%08x\n", + flash_addr); + } else if (0 == strncmp((char *)descriptor->id_string, + DISP_CONFIG_ID, + strlen(DISP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Display config checksum error\n"); + return -EINVAL; + } + reflash_hcd->disp_cfg_update = true; + image_info->disp_config.size = length; + image_info->disp_config.data = content; + image_info->disp_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Display config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Display config flash address = 0x%08x\n", + flash_addr); + } + } + + return 0; +} + +static int reflash_get_fw_image(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + /* reflash_hcd->reflash_by_manual=false; */ + + if (reflash_hcd->image == NULL) { + if (reflash_hcd->reflash_by_manual == false) { + + retval = request_firmware(&reflash_hcd->fw_entry, + FW_IMAGE_NAME, tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to request %s\n", + FW_IMAGE_NAME); + return retval; + } + + } else { + retval = request_firmware(&reflash_hcd->fw_entry, + FW_IMAGE_NAME_MANUAL, + tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to request %s\n", + FW_IMAGE_NAME_MANUAL); + return retval; + } + } + + reflash_hcd->image = reflash_hcd->fw_entry->data; + reflash_hcd->image_size = reflash_hcd->fw_entry->size; + + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware image size = %d\n", + reflash_hcd->image_size); + } + + retval = reflash_parse_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse firmware image\n"); + return retval; + } + + return 0; +} + +static enum update_area reflash_compare_id_info(void) +{ + enum update_area update_area; + unsigned int idx; + unsigned int image_fw_id; + unsigned int device_fw_id; + unsigned char *image_config_id; + unsigned char *device_config_id; + struct app_config_header *header; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *app_config_data; + + update_area = NONE; + + if (reflash_hcd->image_info.app_config.size < sizeof(*header)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid application config in image file\n"); + goto exit; + } + + app_config_data = reflash_hcd->image_info.app_config.data; + header = (struct app_config_header *)app_config_data; + + if (reflash_hcd->force_update) { + update_area = FIRMWARE_CONFIG; + goto exit; + } + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode)) { + update_area = FIRMWARE_CONFIG; + goto exit; + } + + image_fw_id = le4_to_uint(header->build_id); + device_fw_id = tcm_hcd->packrat_number; + + if (image_fw_id != device_fw_id) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image firmware ID not equal device firmware ID\n"); + update_area = FIRMWARE_CONFIG; + goto exit; + } else if (image_fw_id < device_fw_id) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image firmware ID older than device firmware ID\n"); + update_area = NONE; + goto exit; + } + + image_config_id = header->customer_config_id; + device_config_id = tcm_hcd->app_info.customer_config_id; + + for (idx = 0; idx < 16; idx++) { + if (image_config_id[idx] > device_config_id[idx]) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image config ID newer than device config ID\n"); + update_area = CONFIG_ONLY; + goto exit; + } else if (image_config_id[idx] < device_config_id[idx]) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image config ID older than device config ID\n"); + update_area = NONE; + goto exit; + } + } + + update_area = NONE; + +exit: + /* if (update_area == NONE) { + LOGE(tcm_hcd->pdev->dev.parent, + "No need to do reflash\n"); + } else { + LOGE(tcm_hcd->pdev->dev.parent, + "Updating %s\n", + update_area == FIRMWARE_CONFIG ? + "firmware and config" : + "config only"); + } + update_area = FIRMWARE_CONFIG; */ + return update_area; +} + +static int reflash_read_flash(unsigned int address, unsigned char *data, + unsigned int datalen) +{ + int retval; + unsigned int length_words; + unsigned int flash_addr_words; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOCK_BUFFER(reflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->out, + 6); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd->out.buf\n"); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + length_words = datalen / 2; + flash_addr_words = address / 2; + + reflash_hcd->out.buf[0] = (unsigned char)flash_addr_words; + reflash_hcd->out.buf[1] = (unsigned char)(flash_addr_words >> 8); + reflash_hcd->out.buf[2] = (unsigned char)(flash_addr_words >> 16); + reflash_hcd->out.buf[3] = (unsigned char)(flash_addr_words >> 24); + reflash_hcd->out.buf[4] = (unsigned char)length_words; + reflash_hcd->out.buf[5] = (unsigned char)(length_words >> 8); + + LOCK_BUFFER(reflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_READ_FLASH, + reflash_hcd->out.buf, + 6, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_READ_FLASH)); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->out); + + if (reflash_hcd->resp.data_length != datalen) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read requested length\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + return -EIO; + } + + retval = secure_memcpy(data, + datalen, + reflash_hcd->resp.buf, + reflash_hcd->resp.buf_size, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy read data\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + + return 0; +} + +static int reflash_read_data(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output) +{ + int retval; + unsigned int temp; + unsigned int addr; + unsigned int length; + struct syna_tcm_app_info *app_info; + struct syna_tcm_boot_info *boot_info; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + switch (area) { + case CUSTOM_LCM: + case CUSTOM_OEM: + case PPDT: + retval = tcm_hcd->get_data_location(tcm_hcd, + area, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + break; + default: + break; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + app_info = &tcm_hcd->app_info; + boot_info = &tcm_hcd->boot_info; + + switch (area) { + case BOOT_CONFIG: + temp = le2_to_uint(boot_info->boot_config_start_block); + addr = temp * reflash_hcd->write_block_size; + length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS; + break; + case APP_CONFIG: + temp = le2_to_uint(app_info->app_config_start_write_block); + addr = temp * reflash_hcd->write_block_size; + length = le2_to_uint(app_info->app_config_size); + break; + case DISP_CONFIG: + temp = le4_to_uint(boot_info->display_config_start_block); + addr = temp * reflash_hcd->write_block_size; + temp = le2_to_uint(boot_info->display_config_length_blocks); + length = temp * reflash_hcd->write_block_size; + break; + case CUSTOM_OTP: + temp = le2_to_uint(boot_info->custom_otp_start_block); + addr = temp * reflash_hcd->write_block_size; + temp = le2_to_uint(boot_info->custom_otp_length_blocks); + length = temp * reflash_hcd->write_block_size; + break; + case CUSTOM_LCM: + case CUSTOM_OEM: + case PPDT: + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data area\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + LOCK_BUFFER(reflash_hcd->read); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->read, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd->read.buf\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + retval = reflash_read_flash(addr, reflash_hcd->read.buf, length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from flash\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + reflash_hcd->read.data_length = length; + + if (output != NULL) { + retval = syna_tcm_alloc_mem(tcm_hcd, + output, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output->buf\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + retval = secure_memcpy(output->buf, + output->buf_size, + reflash_hcd->read.buf, + reflash_hcd->read.buf_size, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy read data\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + output->data_length = length; + } + + UNLOCK_BUFFER(reflash_hcd->read); + + retval = 0; + +run_app_firmware: + if (!run_app_firmware) + goto exit; + + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + +exit: + return retval; +} + +static int reflash_check_boot_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int device_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.boot_config.size < BOOT_CONFIG_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "No valid boot config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.boot_config.flash_addr; + + temp = le2_to_uint(tcm_hcd->boot_info.boot_config_start_block); + device_addr = temp * reflash_hcd->write_block_size; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_app_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.app_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.app_config.flash_addr; + image_size = reflash_hcd->image_info.app_config.size; + + temp = le2_to_uint(tcm_hcd->app_info.app_config_start_write_block); + device_addr = temp * reflash_hcd->write_block_size; + device_size = le2_to_uint(tcm_hcd->app_info.app_config_size); + + if (device_addr == 0 && device_size == 0) + return 0; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + if (image_size != device_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config size mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_disp_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.disp_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No display config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.disp_config.flash_addr; + image_size = reflash_hcd->image_info.disp_config.size; + + temp = le4_to_uint(tcm_hcd->boot_info.display_config_start_block); + device_addr = temp * reflash_hcd->write_block_size; + + temp = le2_to_uint(tcm_hcd->boot_info.display_config_length_blocks); + device_size = temp * reflash_hcd->write_block_size; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + if (image_size != device_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config size mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_prod_test_firmware(void) +{ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.prod_test_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No production test firmware in image file\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_app_firmware(void) +{ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + if (reflash_hcd->image_info.app_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application firmware in image file\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_write_flash(unsigned int address, const unsigned char *data, + unsigned int datalen) +{ + int retval; + unsigned int offset; + unsigned int w_length; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int flash_address; + unsigned int block_address; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + w_length = tcm_hcd->wr_chunk_size - 5; + + w_length = w_length - (w_length % reflash_hcd->write_block_size); + + w_length = MIN(w_length, reflash_hcd->max_write_payload_size); + + offset = 0; + + remaining_length = datalen; + + LOCK_BUFFER(reflash_hcd->out); + LOCK_BUFFER(reflash_hcd->resp); + + while (remaining_length) { + if (remaining_length > w_length) + xfer_length = w_length; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->out, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd->out.buf\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + flash_address = address + offset; + block_address = flash_address / reflash_hcd->write_block_size; + reflash_hcd->out.buf[0] = (unsigned char)block_address; + reflash_hcd->out.buf[1] = (unsigned char)(block_address >> 8); + + retval = secure_memcpy(&reflash_hcd->out.buf[2], + reflash_hcd->out.buf_size - 2, + &data[offset], + datalen - offset, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy write data\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_WRITE_FLASH, + reflash_hcd->out.buf, + xfer_length + 2, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + WRITE_FLASH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_WRITE_FLASH)); + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address = 0x%08x\n", + flash_address); + LOGE(tcm_hcd->pdev->dev.parent, + "Data length = %d\n", + xfer_length); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + + return 0; +} + +static int reflash_write_app_config(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *data; + + data = reflash_hcd->image_info.app_config.data; + size = reflash_hcd->image_info.app_config.size; + flash_addr = reflash_hcd->image_info.app_config.flash_addr; + + retval = reflash_write_flash(flash_addr, data, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write app_config to flash\n"); + return retval; + } + + return 0; +} + +static int reflash_write_disp_config(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *data; + + data = reflash_hcd->image_info.disp_config.data; + size = reflash_hcd->image_info.disp_config.size; + flash_addr = reflash_hcd->image_info.disp_config.flash_addr; + + retval = reflash_write_flash(flash_addr, data, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write disp_config to flash\n"); + return retval; + } + + return 0; +} + +static int reflash_write_prod_test_firmware(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *data; + + data = reflash_hcd->image_info.prod_test_firmware.data; + size = reflash_hcd->image_info.prod_test_firmware.size; + flash_addr = reflash_hcd->image_info.prod_test_firmware.flash_addr; + + retval = reflash_write_flash(flash_addr, data, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write prod_test_firmware to flash\n"); + return retval; + } + + return 0; +} + +static int reflash_write_app_firmware(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *data; + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + data = reflash_hcd->image_info.app_firmware.data; + size = reflash_hcd->image_info.app_firmware.size; + flash_addr = reflash_hcd->image_info.app_firmware.flash_addr; + + retval = reflash_write_flash(flash_addr, data, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write app_firmware to flash\n"); + return retval; + } + + return 0; +} + + +static int reflash_erase_flash(unsigned int page_start, unsigned int page_count) +{ + int retval; + unsigned char out_buf[4] = {0}; + int size_erase_cmd; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + if ((page_start > 0xff) || (page_count > 0xff)) { + size_erase_cmd = 4; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)((page_start >> 8) & 0xff); + out_buf[2] = (unsigned char)(page_count & 0xff); + out_buf[3] = (unsigned char)((page_count >> 8) & 0xff); + } else { + size_erase_cmd = 2; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)(page_count & 0xff); + } + + LOCK_BUFFER(reflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ERASE_FLASH, + out_buf, + size_erase_cmd, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + ERASE_FLASH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ERASE_FLASH)); + UNLOCK_BUFFER(reflash_hcd->resp); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + + return 0; +} + + +static int reflash_erase(unsigned int flash_addr, unsigned int size) +{ + int retval; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + page_start = flash_addr / reflash_hcd->page_size; + + page_count = ceil_div(size, reflash_hcd->page_size); + + /* LOGD(tcm_hcd->pdev->dev.parent, + "Page start = %d (0x%04x)\n", + page_start, page_start); + + LOGD(tcm_hcd->pdev->dev.parent, + "Page count = %d (0x%04x)\n", + page_count, page_count); */ + LOGE(tcm_hcd->pdev->dev.parent, + "Erase Page start = %d (0x%04x), addr= %d (0x%04x)\n", + page_start, page_start, flash_addr, flash_addr); + + LOGE(tcm_hcd->pdev->dev.parent, + "Erase Page count = %d (0x%04x), size= %d (0x%04x), page_size= %d (0x%04x)\n", + page_count, page_count, size, size, reflash_hcd->page_size, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase pages, addr = 0x%04x, count = %d\n", + page_start, page_count); + return retval; + } + + return 0; +} + +static int reflash_erase_app_config(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + flash_addr = reflash_hcd->image_info.app_config.flash_addr; + size = reflash_hcd->image_info.app_config.size; + retval = reflash_erase(flash_addr, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase app_config\n"); + return retval; + } + + return 0; +} + +static int reflash_erase_disp_config(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + flash_addr = reflash_hcd->image_info.disp_config.flash_addr; + + size = reflash_hcd->image_info.disp_config.size; + + retval = reflash_erase(flash_addr, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase disp_config\n"); + return retval; + } + + return 0; +} + +static int reflash_erase_prod_test_firmware(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + flash_addr = reflash_hcd->image_info.prod_test_firmware.flash_addr; + + size = reflash_hcd->image_info.prod_test_firmware.size; + + retval = reflash_erase(flash_addr, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase prod_test_firmware\n"); + return retval; + } + + return 0; +} + +static int reflash_erase_app_firmware(void) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + flash_addr = reflash_hcd->image_info.app_firmware.flash_addr; + size = reflash_hcd->image_info.app_firmware.size; + retval = reflash_erase(flash_addr, size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase app_firmware\n"); + return retval; + } + + return 0; +} + + +static int reflash_update_custom_otp(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int temp; + unsigned int addr; + unsigned int length; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_start_block); + addr = temp * reflash_hcd->write_block_size; + + temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_length_blocks); + length = temp * reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_custom_lcm(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int addr; + unsigned int length; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->get_data_location(tcm_hcd, + CUSTOM_LCM, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (offset == 0) { + page_start = addr / reflash_hcd->page_size; + + page_count = ceil_div(length, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash pages\n"); + goto run_app_firmware; + } + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_custom_oem(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int addr; + unsigned int length; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->get_data_location(tcm_hcd, + CUSTOM_OEM, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (offset == 0) { + page_start = addr / reflash_hcd->page_size; + + page_count = ceil_div(length, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash pages\n"); + goto run_app_firmware; + } + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_boot_config(bool lock) +{ + int retval; + unsigned char slot_used; + unsigned int idx; + unsigned int addr; + struct boot_config *data; + struct boot_config *last_slot; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + addr = 0; + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + retval = reflash_check_boot_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed boot_config partition check\n"); + goto reset; + } + + retval = reflash_read_data(BOOT_CONFIG, false, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read boot config\n"); + goto reset; + } + + LOCK_BUFFER(reflash_hcd->read); + + data = (struct boot_config *)reflash_hcd->read.buf; + last_slot = data + (BOOT_CONFIG_SLOTS - 1); + slot_used = tcm_hcd->id_info.mode == MODE_TDDI_BOOTLOADER ? 0 : 1; + + if (last_slot->used == slot_used) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config already locked down\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto reset; + } + + if (lock) { + idx = BOOT_CONFIG_SLOTS - 1; + } else { + for (idx = 0; idx < BOOT_CONFIG_SLOTS; idx++) { + if (data->used == slot_used) { + data++; + continue; + } else { + break; + } + } + } + + UNLOCK_BUFFER(reflash_hcd->read); + + if (idx == BOOT_CONFIG_SLOTS) { + LOGE(tcm_hcd->pdev->dev.parent, + "No free boot config slot available\n"); + goto reset; + } + + addr += idx * BOOT_CONFIG_SIZE; + + retval = reflash_write_flash(addr, + reflash_hcd->image_info.boot_config.data, + BOOT_CONFIG_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Slot %d updated with new boot config\n", + idx); + + retval = 0; + +reset: + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_app_config(bool do_reset) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + retval = reflash_check_app_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check app_config partition\n"); + do_reset = true; + goto reset; + } + + retval = reflash_erase_app_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase app_config partition\n"); + do_reset = true; + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "app_config partition erased\n"); + + retval = reflash_write_app_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write app_config partition\n"); + do_reset = true; + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "app_config partition written\n"); + + retval = 0; + +reset: + if (!do_reset) + goto exit; + + if (tcm_hcd->reset_n_reinit(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + +exit: +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_disp_config(bool do_reset) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + retval = reflash_check_disp_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check disp_config partition\n"); + do_reset = true; + goto reset; + } + + retval = reflash_erase_disp_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase disp_config partition\n"); + do_reset = true; + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "disp_config partition erased\n"); + + retval = reflash_write_disp_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write disp_config partition\n"); + do_reset = true; + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "disp_config partition written\n"); + + retval = 0; + +reset: + if (!do_reset) + goto exit; + + if (tcm_hcd->reset_n_reinit(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + +exit: +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_prod_test_firmware(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + retval = reflash_check_prod_test_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check prod_test_firmware partition\n"); + goto reset; + } + + retval = reflash_erase_prod_test_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase prod_test_firmware partition\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "prod_test_firmware partition erased\n"); + + retval = reflash_write_prod_test_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write prod_test_firmware partition\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "prod_test_firmware partition written\n"); + + retval = 0; + +reset: + if (tcm_hcd->reset_n_reinit(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + +static int reflash_update_app_firmware(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + retval = reflash_check_app_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check app_firmware partition\n"); + goto reset; + } + + retval = reflash_erase_app_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase app_firmware partition\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "app_firmware partition erased\n"); + + retval = reflash_write_app_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write app_firmware partition\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "app_firmware partition written\n"); + + retval = 0; + +reset: + if (tcm_hcd->reset_n_reinit(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + return retval; +} + + +static int reflash_do_reflash(void) +{ + int retval; + enum update_area update_area; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + retval = reflash_get_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of reflash\n"); + + atomic_set(&tcm_hcd->firmware_flashing, 1); + + update_area = reflash_compare_id_info(); + + switch (update_area) { + case FIRMWARE_CONFIG: + retval = reflash_update_app_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application firmware\n"); + goto exit; + } + memset(&tcm_hcd->app_info, 0x00, sizeof(tcm_hcd->app_info)); + if (tcm_hcd->features.dual_firmware) { + retval = reflash_update_prod_test_firmware(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash production test firmware\n"); + goto exit; + } + } + case CONFIG_ONLY: + if (reflash_hcd->disp_cfg_update) { + retval = reflash_update_disp_config(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash display config\n"); + goto exit; + } + } + retval = reflash_update_app_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application config\n"); + goto exit; + } + break; + case NONE: + default: + break; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "End of reflash\n"); + + retval = 0; + +exit: + if (reflash_hcd->fw_entry) { + release_firmware(reflash_hcd->fw_entry); + reflash_hcd->fw_entry = NULL; + reflash_hcd->image = NULL; + reflash_hcd->image_size = 0; + } + + atomic_set(&tcm_hcd->firmware_flashing, 0); + wake_up_interruptible(&tcm_hcd->reflash_wq); + return retval; +} + +#ifdef STARTUP_REFLASH +static void reflash_startup_work(struct work_struct *work) +{ + int retval; +#ifdef CONFIG_FB + unsigned int timeout; +#endif + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOGE(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + +#ifdef CONFIG_FB + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS; + + while (tcm_hcd->fb_ready != FB_READY_COUNT) { + if (timeout == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for FB ready\n"); + return; + } + msleep(FB_READY_WAIT_MS); + timeout--; + } +#endif + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_do_reflash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reflash\n"); + } + + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + return; +} +#endif + +static int reflash_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + int idx; + + reflash_hcd = NULL; + if (tcm_hcd->in_hdl_mode) + return 0; + + reflash_hcd = kzalloc(sizeof(*reflash_hcd), GFP_KERNEL); + if (!reflash_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd\n"); + return -ENOMEM; + } + + reflash_hcd->image_buf = kzalloc(IMAGE_BUF_SIZE, GFP_KERNEL); + if (!reflash_hcd->image_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd->image_buf\n"); + goto err_allocate_memory; + } + + reflash_hcd->tcm_hcd = tcm_hcd; + tcm_hcd->syna_tcm_lockdown_info = syna_tcm_lockdown_info; + + reflash_hcd->force_update = FORCE_REFLASH; + + mutex_init(&reflash_hcd->reflash_mutex); + + INIT_BUFFER(reflash_hcd->out, false); + INIT_BUFFER(reflash_hcd->resp, false); + INIT_BUFFER(reflash_hcd->read, false); + +#ifdef STARTUP_REFLASH + reflash_hcd->workqueue = + create_singlethread_workqueue("syna_tcm_reflash"); + INIT_WORK(&reflash_hcd->work, reflash_startup_work); + queue_work(reflash_hcd->workqueue, &reflash_hcd->work); +#endif + + if (ENABLE_SYS_REFLASH == false) + goto init_finished; + + reflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!reflash_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(reflash_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + reflash_hcd->custom_dir = kobject_create_and_add(CUSTOM_DIR_NAME, + reflash_hcd->sysfs_dir); + if (!reflash_hcd->custom_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create custom sysfs directory\n"); + retval = -EINVAL; + goto err_custom_sysfs_create_dir; + } + + for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) { + retval = sysfs_create_bin_file(reflash_hcd->custom_dir, + &bin_attrs[idx]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_custom_sysfs_create_bin_file; + } + } + +init_finished: + tcm_hcd->read_flash_data = reflash_read_data; + return 0; + +err_custom_sysfs_create_bin_file: + for (idx--; idx > 0; idx--) + sysfs_remove_bin_file(reflash_hcd->custom_dir, &bin_attrs[idx]); + + kobject_put(reflash_hcd->custom_dir); + idx = ARRAY_SIZE(attrs); + +err_custom_sysfs_create_dir: + sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(reflash_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(reflash_hcd->sysfs_dir); + +err_sysfs_create_dir: +err_allocate_memory: + kfree(reflash_hcd->image_buf); + + RELEASE_BUFFER(reflash_hcd->read); + RELEASE_BUFFER(reflash_hcd->resp); + RELEASE_BUFFER(reflash_hcd->out); + + kfree(reflash_hcd); + reflash_hcd = NULL; + + return retval; +} + +static int reflash_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!reflash_hcd) + goto exit; + + tcm_hcd->read_flash_data = NULL; + + if (ENABLE_SYS_REFLASH == true) { + for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) { + sysfs_remove_bin_file(reflash_hcd->custom_dir, + &bin_attrs[idx]); + } + + kobject_put(reflash_hcd->custom_dir); + sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + sysfs_remove_file(reflash_hcd->sysfs_dir, + &(*attrs[idx]).attr); + } + + kobject_put(reflash_hcd->sysfs_dir); + } + +#ifdef STARTUP_REFLASH + cancel_work_sync(&reflash_hcd->work); + flush_workqueue(reflash_hcd->workqueue); + destroy_workqueue(reflash_hcd->workqueue); +#endif + + kfree(reflash_hcd->image_buf); + + RELEASE_BUFFER(reflash_hcd->read); + RELEASE_BUFFER(reflash_hcd->resp); + RELEASE_BUFFER(reflash_hcd->out); + + kfree(reflash_hcd); + reflash_hcd = NULL; + +exit: + complete(&reflash_remove_complete); + return 0; +} + +static int reflash_reinit(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!reflash_hcd && !(tcm_hcd->in_hdl_mode)) { + retval = reflash_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb reflash_module = { + .type = TCM_REFLASH, + .init = reflash_init, + .remove = reflash_remove, + .syncbox = NULL, +#ifdef REPORT_NOTIFIER + .asyncbox = NULL, +#endif + .reinit = reflash_reinit, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init reflash_module_init(void) +{ + return syna_tcm_add_module(&reflash_module, true); +} + +static void __exit reflash_module_exit(void) +{ + syna_tcm_add_module(&reflash_module, false); + + wait_for_completion(&reflash_remove_complete); + + return; +} + +module_init(reflash_module_init); +module_exit(reflash_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Reflash Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_spi.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_spi.c new file mode 100755 index 000000000000..26d9c7de467a --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_spi.c @@ -0,0 +1,742 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include "synaptics_tcm_core.h" + +static unsigned char *buf; +static unsigned int buf_size; +static struct spi_transfer *xfer; +static struct syna_tcm_bus_io bus_io; +static struct syna_tcm_hw_interface hw_if; +static struct platform_device *syna_tcm_spi_device; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata) +{ + int retval; + u32 value; + struct property *prop; + struct device_node *np = dev->of_node; + const char *name_tmp; + + LOGE(dev, "-----entert-----%s\n", __func__); + prop = of_find_property(np, "synaptics,irq-gpio", NULL); + if (prop && prop->length) { + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + } else { + bdata->irq_gpio = -1; + } + + retval = of_property_read_u32(np, "synaptics,irq-on-state", &value); + if (retval < 0) + bdata->irq_on_state = 0; + else + bdata->irq_on_state = value; + + /* retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name); + if (retval < 0) + bdata->pwr_reg_name = NULL; + else + bdata->pwr_reg_name = name; */ + + /* retval = of_property_read_string(np, "synaptics,bus-reg-name", &name); + if (retval < 0) + bdata->bus_reg_name = NULL; + else + bdata->bus_reg_name = name; */ + + prop = of_find_property(np, "synaptics,power-gpio", NULL); + if (prop && prop->length) { + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + } else { + bdata->power_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + LOGE(dev, + "Failed to read synaptics,power-on-state property\n"); + return retval; + } else { + bdata->power_on_state = value; + } + } else { + bdata->power_on_state = 0; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + LOGE(dev, + "Failed to read synaptics,power-delay-ms property\n"); + return retval; + } else { + bdata->power_delay_ms = value; + } + } else { + bdata->power_delay_ms = 0; + } + + memset(bdata->avdd_name, 0, sizeof(bdata->avdd_name)); + retval = of_property_read_string(np, "synaptics,avdd-name", &name_tmp); + if (!retval) { + LOGE(dev, "avdd name form dt: %s\n", name_tmp); + if (strlen(name_tmp) < sizeof(bdata->avdd_name)) + strncpy(bdata->avdd_name, + name_tmp, sizeof(bdata->avdd_name)); + else + LOGE(dev, "invalied avdd name length: %ld > %ld", strlen(name_tmp), sizeof(bdata->avdd_name)); + } + + memset(bdata->iovdd_name, 0, sizeof(bdata->iovdd_name)); + retval = of_property_read_string(np, "synaptics,iovdd-name", &name_tmp); + if (!retval) { + LOGE(dev, "iovdd name form dt: %s", name_tmp); + if (strlen(name_tmp) < sizeof(bdata->iovdd_name)) + strncpy(bdata->iovdd_name, + name_tmp, sizeof(bdata->iovdd_name)); + else + LOGE(dev, "invalied iovdd name length: %ld > %ld", + strlen(name_tmp), + sizeof(bdata->iovdd_name)); + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + } else { + bdata->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + LOGE(dev, + "Failed to read synaptics,reset-on-state property\n"); + return retval; + } else { + bdata->reset_on_state = value; + } + } else { + bdata->reset_on_state = 0; + } + + prop = of_find_property(np, "synaptics,reset-active-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + LOGE(dev, + "Failed to read synaptics,reset-active-ms property\n"); + return retval; + } else { + bdata->reset_active_ms = value; + } + } else { + bdata->reset_active_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,reset-delay-ms property\n"); + return retval; + } else { + bdata->reset_delay_ms = value; + } + } else { + bdata->reset_delay_ms = 0; + } + + /*prop = of_find_property(np, "synaptics,tpio-reset-gpio", NULL); + if (prop && prop->length) { + bdata->tpio_reset_gpio = of_get_named_gpio_flags(np, + "synaptics,tpio-reset-gpio", 0, NULL); + } else { + bdata->tpio_reset_gpio = -1; + }*/ + + prop = of_find_property(np, "synaptics,x-flip", NULL); + bdata->x_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,y-flip", NULL); + bdata->y_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,byte-delay-us", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,byte-delay-us property\n"); + return retval; + } else { + bdata->byte_delay_us = value; + } + } else { + bdata->byte_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,block-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,block-delay-us", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,block-delay-us property\n"); + return retval; + } else { + bdata->block_delay_us = value; + } + } else { + bdata->block_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,spi-mode", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,spi-mode", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,spi-mode property\n"); + return retval; + } else { + bdata->spi_mode = value; + } + } else { + bdata->spi_mode = 0; + } + + prop = of_find_property(np, "synaptics,ubl-max-freq", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ubl-max-freq", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,ubl-max-freq property\n"); + return retval; + } else { + bdata->ubl_max_freq = value; + } + } else { + bdata->ubl_max_freq = 0; + } + + prop = of_find_property(np, "synaptics,ubl-byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ubl-byte-delay-us", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,ubl-byte-delay-us property\n"); + return retval; + } else { + bdata->ubl_byte_delay_us = value; + } + } else { + bdata->ubl_byte_delay_us = 0; + } + + return 0; +} +#endif + +static int syna_tcm_spi_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + unsigned int count, unsigned int size) +{ + static unsigned int xfer_count; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + + if (count > xfer_count) { + kfree(xfer); + xfer = kcalloc(count, sizeof(*xfer), GFP_KERNEL); + if (!xfer) { + LOGE(&spi->dev, + "Failed to allocate memory for xfer\n"); + xfer_count = 0; + return -ENOMEM; + } + xfer_count = count; + } else { + memset(xfer, 0, count * sizeof(*xfer)); + } + + if (size > buf_size) { + if (buf_size) + kfree(buf); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + LOGE(&spi->dev, + "Failed to allocate memory for buf\n"); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + return 0; +} + +static int syna_tcm_spi_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int idx; + unsigned int mode; + unsigned int byte_count; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + byte_count = length + 2; + + if (bdata->ubl_byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 2, byte_count); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, byte_count, 3); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)(addr >> 8) | 0x80; + buf[1] = (unsigned char)addr; + + if (bdata->ubl_byte_delay_us == 0) { + xfer[0].len = 2; + xfer[0].tx_buf = buf; + xfer[0].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[0], &msg); + memset(&buf[2], 0xff, length); + xfer[1].len = length; + xfer[1].tx_buf = &buf[2]; + xfer[1].rx_buf = data; + if (bdata->block_delay_us) + xfer[1].delay_usecs = bdata->block_delay_us; + xfer[1].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[1], &msg); + } else { + buf[2] = 0xff; + for (idx = 0; idx < byte_count; idx++) { + xfer[idx].len = 1; + if (idx < 2) { + xfer[idx].tx_buf = &buf[idx]; + } else { + xfer[idx].tx_buf = &buf[2]; + xfer[idx].rx_buf = &data[idx - 2]; + } + xfer[idx].delay_usecs = bdata->ubl_byte_delay_us; + if (bdata->block_delay_us && (idx == byte_count - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + xfer[idx].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + mode = spi->mode; + spi->mode = SPI_MODE_3; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + + spi->mode = mode; + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int mode; + unsigned int byte_count; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + byte_count = length + 2; + + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, byte_count); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)(addr >> 8) & ~0x80; + buf[1] = (unsigned char)addr; + retval = secure_memcpy(&buf[2], + buf_size - 2, + data, + length, + length); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to copy write data\n"); + goto exit; + } + + xfer[0].len = byte_count; + xfer[0].tx_buf = buf; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + + mode = spi->mode; + spi->mode = SPI_MODE_3; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + + spi->mode = mode; + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + pm_wakeup_event(&syna_tcm_spi_device->dev, 0); + pm_stay_awake(&syna_tcm_spi_device->dev); + if (tcm_hcd->tp_pm_suspend) { + LOGI(tcm_hcd->pdev->dev.parent, + "Touch is in pm_suspend status!\n"); + retval = wait_for_completion_timeout(&tcm_hcd->pm_resume_completion, + msecs_to_jiffies(500)); + if (!retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "wait_for_completion_timeout!\n"); + goto exit; + } + } + + spi_message_init(&msg); + + if (bdata->byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, length); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory\n"); + goto exit; + } + + if (bdata->byte_delay_us == 0) { + memset(buf, 0xff, length); + xfer[0].len = length; + xfer[0].tx_buf = buf; + xfer[0].rx_buf = data; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + buf[0] = 0xff; + for (idx = 0; idx < length; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = buf; + xfer[idx].rx_buf = &data[idx]; + xfer[idx].delay_usecs = bdata->byte_delay_us; + if (bdata->block_delay_us && (idx == length - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } +exit: + pm_relax(&syna_tcm_spi_device->dev); + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + return retval; +} + +static int syna_tcm_spi_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + pm_wakeup_event(&syna_tcm_spi_device->dev, 0); + pm_stay_awake(&syna_tcm_spi_device->dev); + if (tcm_hcd->tp_pm_suspend) { + LOGI(tcm_hcd->pdev->dev.parent, + "Touch is in pm_suspend status!\n"); + retval = wait_for_completion_timeout(&tcm_hcd->pm_resume_completion, + msecs_to_jiffies(500)); + if (!retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "wait_for_completion_timeout!\n"); + goto exit; + } + } + + spi_message_init(&msg); + + if (bdata->byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, 0); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 0); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + if (bdata->byte_delay_us == 0) { + xfer[0].len = length; + xfer[0].tx_buf = data; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + for (idx = 0; idx < length; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = &data[idx]; + xfer[idx].delay_usecs = bdata->byte_delay_us; + if (bdata->block_delay_us && (idx == length - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } +exit: + pm_relax(&syna_tcm_spi_device->dev); + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + return retval; +} + +static int syna_tcm_spi_probe(struct spi_device *spi) +{ + int retval; + + LOGE(&spi->dev, "-----enter-----%s\n", __func__); + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + LOGE(&spi->dev, + "Full duplex not supported by host\n"); + return -EIO; + } + + syna_tcm_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_tcm_spi_device) { + LOGE(&spi->dev, + "Failed to allocate platform device\n"); + return -ENOMEM; + } + +#ifdef CONFIG_OF + hw_if.bdata = devm_kzalloc(&spi->dev, sizeof(*hw_if.bdata), GFP_KERNEL); + if (!hw_if.bdata) { + LOGE(&spi->dev, + "Failed to allocate memory for board data\n"); + return -ENOMEM; + } + parse_dt(&spi->dev, hw_if.bdata); +#else + hw_if.bdata = spi->dev.platform_data; +#endif + + switch (hw_if.bdata->spi_mode) { + case 0: + spi->mode = SPI_MODE_0; + break; + case 1: + spi->mode = SPI_MODE_1; + break; + case 2: + spi->mode = SPI_MODE_2; + break; + case 3: + spi->mode = SPI_MODE_3; + break; + } + + bus_io.type = BUS_SPI; + bus_io.read = syna_tcm_spi_read; + bus_io.write = syna_tcm_spi_write; + bus_io.rmi_read = syna_tcm_spi_rmi_read; + bus_io.rmi_write = syna_tcm_spi_rmi_write; + + hw_if.bus_io = &bus_io; + + spi->bits_per_word = 8; + + retval = spi_setup(spi); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to set up SPI protocol driver\n"); + return retval; + } + + syna_tcm_spi_device->dev.parent = &spi->dev; + syna_tcm_spi_device->dev.platform_data = &hw_if; + + retval = platform_device_add(syna_tcm_spi_device); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to add platform device\n"); + return retval; + } + + return 0; +} + +static int syna_tcm_spi_remove(struct spi_device *spi) +{ + syna_tcm_spi_device->dev.platform_data = NULL; + + platform_device_unregister(syna_tcm_spi_device); + + return 0; +} + +static const struct spi_device_id syna_tcm_id_table[] = { + {SPI_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, syna_tcm_id_table); + +#ifdef CONFIG_OF +static struct of_device_id syna_tcm_of_match_table[] = { + { + .compatible = "synaptics,tcm-spi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table); +#else +#define syna_tcm_of_match_table NULL +#endif + +static struct spi_driver syna_tcm_spi_driver = { + .driver = { + .name = SPI_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_tcm_of_match_table, + }, + .probe = syna_tcm_spi_probe, + .remove = syna_tcm_spi_remove, + .id_table = syna_tcm_id_table, +}; + +int syna_tcm_bus_init(void) +{ + return spi_register_driver(&syna_tcm_spi_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_init); + +void syna_tcm_bus_exit(void) +{ + kfree(buf); + kfree(xfer); + spi_unregister_driver(&syna_tcm_spi_driver); + return; +} +EXPORT_SYMBOL(syna_tcm_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM SPI Bus Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_testing.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_testing.c new file mode 100755 index 000000000000..e4d94472987f --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_testing.c @@ -0,0 +1,2883 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include +#include +#include "synaptics_tcm_core.h" +#include "synaptics_tcm_testing.h" + +#define SYSFS_DIR_NAME "testing" + +#define REPORT_TIMEOUT_MS 5000 + +#define FORMAT_4D "%4hd " +#define FORMAT_5D "%5hd " +#define FORMAT_4U "%4hu " +#define FORMAT_5U "%5hu " + +#if (USE_KOBJ_SYSFS) +#define testing_sysfs_show(t_name) \ +static ssize_t testing_sysfs_##t_name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ +{ \ + int retval; \ + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = testing_##t_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to do "#t_name" test\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, \ + "%s\n", \ + testing_hcd->result ? "Passed" : "Failed"); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} +#else +#define testing_sysfs_show(t_name) \ +static ssize_t testing_sysfs_##t_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int retval; \ + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = testing_##t_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to do "#t_name" test\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, \ + "%s\n", \ + testing_hcd->result ? "Passed" : "Failed"); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} +#endif + +#define CHECK_BIT(var, pos) ((var) & (1<<(pos))) + +/* Sync with the Comm2 Release 21 */ +/* Not all are implemented for every ASIC */ +enum test_code { + TEST_NOT_IMPLEMENTED = 0x00, + TEST_PT1_TRX_TRX_SHORTS = 0x01, + TEST_PT2_TRX_SENSOR_OPENS = 0x02, + TEST_PT3_TRX_GROUND_SHORTS = 0x03, + TEST_PT5_FULL_RAW_CAP = 0x05, + TEST_PT6_EE_SHORT = 0x06, + TEST_PT7_DYNAMIC_RANGE = 0x07, + TEST_PT8_HIGH_RESISTANCE = 0x08, + TEST_PT10_DELTA_NOISE = 0x0a, + TEST_PT11_OPEN_DETECTION = 0x0b, + TEST_PT12 = 0x0c, + TEST_PT13 = 0x0d, + TEST_PT14_DOZE_DYNAMIC_RANGE = 0x0e, + TEST_PT15_DOZE_NOISE = 0x0f, + TEST_PT16_SENSOR_SPEED = 0x10, + TEST_PT17_ADC_RANGE = 0x11, + TEST_PT18_HYBRID_ABS_RAW = 0x12, + TEST_PT22_TRANS_RAW_CAP = 0x16, + TEST_PT29_HYBRID_ABS_NOISE = 0x1D, +}; + +struct testing_hcd { + bool result; + unsigned char report_type; + enum test_code test_item; + unsigned int report_index; + unsigned int num_of_reports; + struct kobject *sysfs_dir; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer report; + struct syna_tcm_buffer process; + struct syna_tcm_buffer output; + struct syna_tcm_buffer pt_hi_limits; + struct syna_tcm_buffer pt_lo_limits; + struct syna_tcm_hcd *tcm_hcd; + int (*collect_reports)(enum report_type report_type, + unsigned int num_of_reports); +}; + +DECLARE_COMPLETION(report_complete); + +DECLARE_COMPLETION(testing_remove_complete); + +static struct testing_hcd *testing_hcd; + + +/* testing implementation */ +static int testing_device_id(void); + +static int testing_config_id(void); + +static int testing_reset_open(void); + +static int testing_pt01_trx_trx_short(void); + +static int testing_pt03_trx_ground(void); + +static int testing_pt05_full_raw(void); + +static int testing_pt07_dynamic_range(void); + +static int testing_pt10_noise(void); + +static int testing_pt11_open_detection(void); + +static int testing_pt18_hybrid_abs_raw(void); + +static int testing_self_test(char *buf); + +#if (USE_KOBJ_SYSFS) +KOBJ_SHOW_PROTOTYPE(testing, size) +KOBJ_SHOW_PROTOTYPE(testing, device_id) +KOBJ_SHOW_PROTOTYPE(testing, config_id) +KOBJ_SHOW_PROTOTYPE(testing, pt01_trx_trx_short) +KOBJ_SHOW_PROTOTYPE(testing, pt03_trx_ground) +KOBJ_SHOW_PROTOTYPE(testing, pt05_full_raw) +KOBJ_SHOW_PROTOTYPE(testing, pt07_dynamic_range) +KOBJ_SHOW_PROTOTYPE(testing, pt10_noise) +KOBJ_SHOW_PROTOTYPE(testing, pt11_open_detection) +KOBJ_SHOW_PROTOTYPE(testing, pt18_hybrid_abs_raw) +KOBJ_SHOW_PROTOTYPE(testing, reset_open) +KOBJ_SHOW_PROTOTYPE(testing, self_test) +KOBJ_SHOW_PROTOTYPE(testing, rid18_data) +KOBJ_SHOW_PROTOTYPE(testing, rid161_data) + +static struct kobj_attribute *attrs[] = { + KOBJ_ATTRIFY(size), + KOBJ_ATTRIFY(device_id), + KOBJ_ATTRIFY(config_id), + KOBJ_ATTRIFY(pt01_trx_trx_short), + KOBJ_ATTRIFY(pt03_trx_ground), + KOBJ_ATTRIFY(pt05_full_raw), + KOBJ_ATTRIFY(pt07_dynamic_range), + KOBJ_ATTRIFY(pt10_noise), + KOBJ_ATTRIFY(pt11_open_detection), + KOBJ_ATTRIFY(pt18_hybrid_abs_raw), + KOBJ_ATTRIFY(reset_open), + KOBJ_ATTRIFY(self_test), + KOBJ_ATTRIFY(rid18_data), + KOBJ_ATTRIFY(rid161_data) +}; +#else +/* nodes for testing */ +SHOW_PROTOTYPE(testing, size) +SHOW_PROTOTYPE(testing, device_id) +SHOW_PROTOTYPE(testing, config_id) +SHOW_PROTOTYPE(testing, pt01_trx_trx_short) +SHOW_PROTOTYPE(testing, pt03_trx_ground) +SHOW_PROTOTYPE(testing, pt05_full_raw) +SHOW_PROTOTYPE(testing, pt07_dynamic_range) +SHOW_PROTOTYPE(testing, pt10_noise) +SHOW_PROTOTYPE(testing, pt11_open_detection) +SHOW_PROTOTYPE(testing, pt18_hybrid_abs_raw) +SHOW_PROTOTYPE(testing, reset_open) +SHOW_PROTOTYPE(testing, self_test) +SHOW_PROTOTYPE(testing, rid18_data) +SHOW_PROTOTYPE(testing, rid161_data) + +static struct device_attribute *attrs[] = { + ATTRIFY(size), + ATTRIFY(device_id), + ATTRIFY(config_id), + ATTRIFY(pt01_trx_trx_short), + ATTRIFY(pt03_trx_ground), + ATTRIFY(pt05_full_raw), + ATTRIFY(pt07_dynamic_range), + ATTRIFY(pt10_noise), + ATTRIFY(pt11_open_detection), + ATTRIFY(pt18_hybrid_abs_raw), + ATTRIFY(reset_open), + ATTRIFY(self_test), + ATTRIFY(rid18_data), + ATTRIFY(rid161_data) +}; +#endif + +static ssize_t testing_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "data", + .mode = S_IRUGO, + }, + .size = 0, + .read = testing_sysfs_data_show, +}; + +testing_sysfs_show(device_id) + +testing_sysfs_show(config_id) + +testing_sysfs_show(pt01_trx_trx_short) + +testing_sysfs_show(pt03_trx_ground) + +testing_sysfs_show(pt05_full_raw) + +testing_sysfs_show(pt07_dynamic_range) + +testing_sysfs_show(pt10_noise) + +testing_sysfs_show(pt11_open_detection) + +testing_sysfs_show(pt18_hybrid_abs_raw) + +testing_sysfs_show(reset_open) + +#if (USE_KOBJ_SYSFS) +static ssize_t testing_sysfs_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +#else +static ssize_t testing_sysfs_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +#endif +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(testing_hcd->output); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + testing_hcd->output.data_length); + + UNLOCK_BUFFER(testing_hcd->output); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(testing_hcd->output); + + readlen = MIN(count, testing_hcd->output.data_length - pos); + + retval = secure_memcpy(buf, + count, + &testing_hcd->output.buf[pos], + testing_hcd->output.buf_size - pos, + readlen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + } else { + retval = readlen; + } + + UNLOCK_BUFFER(testing_hcd->output); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static void goto_next_line(char **p) +{ + while (**p != '\n') { + if (**p == '\0') { + return; + } + *p = *p + 1; + } + *p = *p + 1; + return; +} + +static int testing_get_limits_bytes(unsigned int *row, unsigned int *col, enum test_code code) +{ + unsigned int rows; + unsigned int cols; + unsigned int size = 0; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + switch(code) { + case TEST_PT1_TRX_TRX_SHORTS: + case TEST_PT3_TRX_GROUND_SHORTS: + *row = 1; + *col = PT1_PT3_LIMITS_BYTES_SIZE; + size = PT1_PT3_LIMITS_BYTES_SIZE; + break; + case TEST_PT5_FULL_RAW_CAP: + *row = rows; + *col = cols; + size = rows*cols*2; + break; + case TEST_PT22_TRANS_RAW_CAP: + *row = rows; + *col = cols; + size = rows*cols*2; + break; + case TEST_PT10_DELTA_NOISE: + *row = 1; + *col = PT10_LIMITS_BYTES_SIZE; + size = PT10_LIMITS_BYTES_SIZE*2; + break; + case TEST_PT18_HYBRID_ABS_RAW: + *row = 1; + *col = rows + cols; + size = (rows + cols)*4; + break; + default: + *row = rows; + *col = cols; + size = 0; + break; + } + + return size; +} + +static int testing_copy_valid_data(struct syna_tcm_buffer *dest_buffer, char *src_buf, enum test_code code) +{ + int retval = 0; + int i, j, n; + int offset = 0; + long data; + unsigned int byte_cnt = 0; + unsigned int limit_cnt = 0; + char *pdest = dest_buffer->buf; + char *psrc = src_buf; + char data_buf[DATA_SIZE_MAX+2] = {0}; + unsigned int limit_rows; + unsigned int limit_cols; + unsigned int buf_size_bytes = 0; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (dest_buffer == NULL || src_buf == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid src or dest pointer\n"); + retval = -EINVAL; + goto exit; + } + + buf_size_bytes = testing_get_limits_bytes(&limit_rows, &limit_cols, code); + if (buf_size_bytes == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get buf_size_bytes for PT%d\n",code); + retval = -EINVAL; + goto exit; + } + + if (buf_size_bytes > dest_buffer->buf_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect buf_size[%d] for PT%d, expected buf bytes size[%d]\n", + dest_buffer->buf_size, code, buf_size_bytes); + retval = -EINVAL; + goto exit; + } + + byte_cnt = 0; + limit_cnt = 0; + for (i = 0; i < limit_rows; i++) { + for (j = 0; j < limit_cols; j++) { + /* get one data */ + n = 0; + data_buf[0] = '\0'; + while (n < DATA_SIZE_MAX) { + if (*psrc == '\n' || *psrc == '\0' || *psrc == '\r') { + break; + } else if (*psrc == ',') { + psrc++; + break; + } else if (*psrc == ' ') { + psrc++; + continue; + } else { + data_buf[n] = *psrc; + n++; + psrc++; + } + } + data_buf[n] = '\0'; + + if (strlen(data_buf) == 0) + continue; + + if (kstrtol(data_buf, 0, &data)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Err to convert string(%s) to a long value for PT%d\n", + data_buf, code); + retval = -EINVAL; + goto exit; + } + + if (code == TEST_PT18_HYBRID_ABS_RAW) { + uint_to_le4(pdest, data); + offset = 4; + } else if (code == TEST_PT1_TRX_TRX_SHORTS || + code == TEST_PT3_TRX_GROUND_SHORTS) { + uint_to_le1(pdest, data); + offset = 1; + } else { + uint_to_le2(pdest, data); + offset = 2; + } + + pdest += offset; + byte_cnt += offset; + limit_cnt += 1; + } + goto_next_line(&psrc); + } + + if (byte_cnt != buf_size_bytes) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect valid limit bytes size[%d], expected bytes size[%d]\n", + byte_cnt, buf_size_bytes); + goto exit; + } + + if (limit_cnt != (limit_rows*limit_cols)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect valid limit data size[%d], expected size[%d]\n", + limit_cnt, limit_rows*limit_cols); + goto exit; + } + + dest_buffer->data_length = byte_cnt; + +exit: + return retval; +} + +static int testing_parse_csv_data(struct syna_tcm_buffer *dest_buffer, char *src_buf, + char *name, enum test_code code) +{ + int retval; + char *psrc = src_buf; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (!dest_buffer || !src_buf || !name) { + retval = -EINVAL; + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid buf pointer for %s\n", name); + goto exit; + } + + psrc = strstr(psrc, name); + if (!psrc) { + retval = -EINTR; + LOGE(tcm_hcd->pdev->dev.parent, "search %s failed\n", name); + goto exit; + } + + goto_next_line(&psrc); + if (!psrc || (strlen(psrc) == 0)) { + retval = -EIO; + LOGE(tcm_hcd->pdev->dev.parent, + "there is no valid data for %s\n", name); + goto exit; + } + + retval = testing_copy_valid_data(dest_buffer, psrc, code); + if(retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "load PT%d %s data: Failed[%d]\n", code, name, retval); + goto exit; + } else { + LOGI(tcm_hcd->pdev->dev.parent, + "load PT%d %s data: Success\n", code, name); + } + +exit: + return retval; +} + +static int testing_load_testlimits(enum test_code testcode, unsigned int gapdiff) +{ + int retval = 0; + char *csv_buf = NULL; + char limit_file_name[100] = {0}; + char test_name0[100] = {0}; + char test_name1[100] = {0}; + unsigned int rows; + unsigned int cols; + unsigned int buf_size_bytes = 0; + const struct firmware *firmware = NULL; + struct syna_tcm_buffer *dest_buffer0 = NULL; + struct syna_tcm_buffer *dest_buffer1 = NULL; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + switch(testcode) { + case TEST_PT1_TRX_TRX_SHORTS: + sprintf(test_name0, "%s", CSV_PT1_TESTING_LIMITS); + dest_buffer0 = &testing_hcd->pt_hi_limits; + dest_buffer1 = NULL; + break; + case TEST_PT3_TRX_GROUND_SHORTS: + sprintf(test_name0, "%s", CSV_PT3_TESTING_LIMITS); + dest_buffer0 = &testing_hcd->pt_hi_limits; + dest_buffer1 = NULL; + break; + case TEST_PT5_FULL_RAW_CAP: + sprintf(test_name0, "%s", CSV_PT5_TESTING_LIMITS_MIN); + dest_buffer0 = &testing_hcd->pt_lo_limits; + + sprintf(test_name1, "%s", CSV_PT5_TESTING_LIMITS_MAX); + dest_buffer1 = &testing_hcd->pt_hi_limits; + break; + case TEST_PT22_TRANS_RAW_CAP: + if (gapdiff) { + sprintf(test_name0, "%s", CSV_GAP_DIFF_TESTING_LIMITS_MAX); + dest_buffer0 = &testing_hcd->pt_hi_limits; + } else { + sprintf(test_name0, "%s", CSV_PT22_TESTING_LIMITS_MIN); + dest_buffer0 = &testing_hcd->pt_lo_limits; + + sprintf(test_name1, "%s", CSV_PT22_TESTING_LIMITS_MAX); + dest_buffer1 = &testing_hcd->pt_hi_limits; + } + break; + case TEST_PT10_DELTA_NOISE: + sprintf(test_name0, "%s", CSV_PT10_TESTING_LIMITS); + dest_buffer0 = &testing_hcd->pt_hi_limits; + dest_buffer1 = NULL; + break; + case TEST_PT18_HYBRID_ABS_RAW: + sprintf(test_name0, "%s", CSV_PT18_TESTING_LIMITS_MIN); + dest_buffer0 = &testing_hcd->pt_lo_limits; + + sprintf(test_name1, "%s", CSV_PT18_TESTING_LIMITS_MAX); + dest_buffer1 = &testing_hcd->pt_hi_limits; + break; + default: + dest_buffer0 = NULL; + dest_buffer1 = NULL; + break; + } + + if (!dest_buffer0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Limit data is not in csv file for [PT%d]\n", testcode); + goto exit; + } + + buf_size_bytes = testing_get_limits_bytes(&rows, &cols, testcode); + if (buf_size_bytes == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get buf size for PT%d\n", testcode); + retval = -EINVAL; + goto exit; + } + + /* read limit csv file */ + sprintf(limit_file_name, "%s", SYNA_TCM_TESTING_LIMITS_FILE_NAME); + LOGN(tcm_hcd->pdev->dev.parent, + "limit_file_name:%s.\n", limit_file_name); + + retval = request_firmware(&firmware, + limit_file_name, tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to request limit file:%s\n", + limit_file_name); + goto exit; + } + + if (firmware->size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "request_firmware, limits file length error\n"); + retval = -EINVAL; + goto exit; + } + + csv_buf = kzalloc(firmware->size + 1, GFP_KERNEL); + if (!csv_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for csv_buf\n"); + retval = -ENOMEM; + goto exit; + } + memcpy(csv_buf, firmware->data, firmware->size); + + if (dest_buffer0) { + /* allocate the mem for limits buffer */ + retval = syna_tcm_alloc_mem(tcm_hcd, dest_buffer0, buf_size_bytes); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for PT%d buf\n", testcode); + goto exit; + } + + /* load the limits value */ + retval = testing_parse_csv_data(dest_buffer0, csv_buf, test_name0, testcode); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read limts data for %s [PT%d] from csv file\n",test_name0, testcode); + goto exit; + } + } + + if (dest_buffer1) { + /* allocate the mem for limits buffer */ + retval = syna_tcm_alloc_mem(tcm_hcd, dest_buffer1, buf_size_bytes); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for PT%d buf\n", testcode); + goto exit; + } + + /* load the limits */ + retval = testing_parse_csv_data(dest_buffer1, csv_buf, test_name1, testcode); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read limts data for %s [PT%d] from csv file\n",test_name1, testcode); + goto exit; + } + } + +exit: + if (csv_buf) + kfree(csv_buf); + + if (firmware) + release_firmware(firmware); + + return retval; +} + +#ifdef CONFIG_FACTORY_BUILD +#if TESTING_RESULT_IN_CSV +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +static ssize_t fs_write(const void *buf, size_t size, struct file *fp) +{ + ssize_t len = -1; + loff_t pos; + + pos = fp->f_pos; + len = __kernel_write(fp, buf, size, &pos); + fp->f_pos = pos; + + return len; +} +#else +static ssize_t fs_write(const void *buf, size_t size, struct file *fp) +{ + mm_segment_t old_fs; + loff_t pos; + ssize_t len; + + pos = fp->f_pos; + old_fs = get_fs(); + set_fs(KERNEL_DS); + len = vfs_write(fp, buf, size, &pos); + set_fs(old_fs); + fp->f_pos = pos; + + return len; +} +#endif + +static int testing_result_save_csv(char *buf, unsigned int size) +{ + int retval = 0; + char save_path[100] = {0}; + struct file *fp = NULL; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + /* open save file */ + sprintf(save_path, "%s", SYNA_TCM_TESTING_RESULT_SAVE_PATH); + LOGI(tcm_hcd->pdev->dev.parent, + "save_path:%s. Save result data, starting...\n", save_path); + fp = filp_open(save_path, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (IS_ERR(fp)) { + LOGE(tcm_hcd->pdev->dev.parent, + "open file:%s failed. fp:%ld\n", save_path, PTR_ERR(fp)); + retval = -EIO; + return retval; + } + + if ((buf != NULL) && size > 0) { + /* save data to csv file */ + if (fp != NULL) { + retval = fs_write(buf, size, fp); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write data to file\n"); + goto exit; + } + } + } + LOGI(tcm_hcd->pdev->dev.parent, + "save_path:%s. Save result data, completed\n", save_path); +exit: + filp_close(fp, NULL); + return retval; +} +#endif +#endif + +static unsigned int testing_save_output(char *out_buf, unsigned int offset, char *pstr) +{ + unsigned int data_len; + unsigned int cnt, sum; + int data; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int idx; + unsigned char *buf = NULL; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + sum = offset; + if (out_buf == NULL) { + LOGI(tcm_hcd->pdev->dev.parent, "Do not support save ouput data\n"); + goto exit; + } + + app_info = &tcm_hcd->app_info; + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + LOCK_BUFFER(testing_hcd->output); + data_len = testing_hcd->output.data_length; + if (data_len == 0) + goto unlockbuffer; + + cnt = 0; + if (pstr == NULL) { + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "PT%d Test Result = %s\n", + testing_hcd->test_item, (testing_hcd->result)?"pass":"fail"); + } else { + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "%s Test Result = %s\n", + pstr, (testing_hcd->result)?"pass":"fail"); + } + sum += cnt; + if (data_len == (rows*cols*2)) { + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "Rows=%d,Cols=%d\n", rows, cols); + sum += cnt; + } + + /* print data */ + buf = testing_hcd->output.buf; + if (data_len == (rows*cols*2)) { + idx = 0; + for (row = 0; row < rows; row++) { + cnt = 0; + for (col = 0; col < cols; col++) { + data = (short)((unsigned short)(buf[idx] & 0xff) | + (unsigned short)(buf[idx+1] << 8)); + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "%d,", data); + sum += cnt; + + idx += 2; + } + + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "\n"); + sum += cnt; + } + } else if ((data_len == ((rows + cols)*4)) && (testing_hcd->test_item == TEST_PT18_HYBRID_ABS_RAW)){ + idx = 0; + cnt = 0; + + for (col = 0; col < (cols + rows); col++) { + data = (int)(buf[idx] & 0xff) | + (int)(buf[idx+1] << 8) | + (int)(buf[idx+2] << 16) | + (int)(buf[idx+3] << 24); + + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "%d,", data); + sum += cnt; + + idx+=4; + } + + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "\n"); + sum += cnt; + } else if (data_len <= ((rows + cols)*2)) { + idx = 0; + cnt = 0; + + for (col = 0; col < data_len; col++) { + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "0x%02X,", buf[idx]); + sum += cnt; + + idx += 1; + } + + cnt = snprintf(out_buf + sum, SAVE_BUF_SIZE - sum, "\n"); + sum += cnt; + } else { + LOGE(tcm_hcd->pdev->dev.parent, "Invalid data\n"); + } + +unlockbuffer: + UNLOCK_BUFFER(testing_hcd->output); +exit: + return sum; +} + +static int testing_run_prod_test_item(enum test_code test_code) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (tcm_hcd->features.dual_firmware && + tcm_hcd->id_info.mode != MODE_PRODUCTIONTEST_FIRMWARE) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_PRODUCTION_TEST); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run production test firmware\n"); + return retval; + } + } else if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Identifying mode = 0x%02x\n", + tcm_hcd->id_info.mode); + return -ENODEV; + } + + LOCK_BUFFER(testing_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->out, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->out.buf\n"); + UNLOCK_BUFFER(testing_hcd->out); + return retval; + } + + testing_hcd->test_item = test_code; + testing_hcd->out.buf[0] = test_code; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_PRODUCTION_TEST, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_PRODUCTION_TEST)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + return retval; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + return 0; +} + +static int testing_collect_reports(enum report_type report_type, + unsigned int num_of_reports) +{ + int retval; + bool completed; + unsigned int timeout; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + testing_hcd->report_index = 0; + testing_hcd->report_type = report_type; + testing_hcd->num_of_reports = num_of_reports; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + reinit_completion(&report_complete); +#else + INIT_COMPLETION(report_complete); +#endif + + LOCK_BUFFER(testing_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->out, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->out.buf\n"); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + testing_hcd->out.buf[0] = testing_hcd->report_type; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ENABLE_REPORT, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ENABLE_REPORT)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + completed = false; + timeout = REPORT_TIMEOUT_MS * num_of_reports; + + retval = wait_for_completion_timeout(&report_complete, + msecs_to_jiffies(timeout)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for report collection\n"); + } else { + completed = true; + } + + LOCK_BUFFER(testing_hcd->out); + + testing_hcd->out.buf[0] = testing_hcd->report_type; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DISABLE_REPORT, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DISABLE_REPORT)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + if (completed) + retval = 0; + else + retval = -EIO; + +exit: + testing_hcd->report_type = 0; + + return retval; +} + +static void testing_get_frame_size_words(unsigned int *size, bool image_only) +{ + unsigned int rows; + unsigned int cols; + unsigned int hybrid; + unsigned int buttons; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + hybrid = le2_to_uint(app_info->has_hybrid_data); + buttons = le2_to_uint(app_info->num_of_buttons); + + *size = rows * cols; + + if (!image_only) { + if (hybrid) + *size += rows + cols; + *size += buttons; + } + + return; +} + +static void testing_copy_resp_to_output(void) +{ + int retval; + unsigned int output_size; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + output_size = testing_hcd->resp.data_length; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test resp data\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); + + return; +} + +static void testing_standard_frame_output(bool image_only) +{ + int retval; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + testing_get_frame_size_words(&data_size, image_only); + + header_size = sizeof(app_info->num_of_buttons) + + sizeof(app_info->num_of_image_rows) + + sizeof(app_info->num_of_image_cols) + + sizeof(app_info->has_hybrid_data); + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + &app_info->num_of_buttons[0], + header_size, + header_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy header data\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); + + return; +} + +static int testing_device_id(void) +{ + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + struct syna_tcm_identification *id_info; + char *strptr = NULL; + int retval; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing\n"); + testing_hcd->result = false; + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + /* 1-failed 2-passed */ + retval = 1; + goto exit; + } + + id_info = &tcm_hcd->id_info; + + strptr = strnstr(id_info->part_number, + device_id_limit, + sizeof(id_info->part_number)); + if (strptr != NULL) + testing_hcd->result = true; + else + LOGE(tcm_hcd->pdev->dev.parent, + "Device ID is mismatching, FW: %s (%s)\n", + id_info->part_number, device_id_limit); + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + + retval = (testing_hcd->result) ? 2 : 1; // 1-failed 2-passed + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + return retval; + +} + +static int testing_config_id(void) +{ + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + struct syna_tcm_app_info *app_info; + int i; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing\n"); + testing_hcd->result = false; + + app_info = &tcm_hcd->app_info; + + testing_hcd->result = true; + for (i = 0; i < sizeof(config_id_limit); i++) { + if (config_id_limit[i] != + tcm_hcd->app_info.customer_config_id[i]) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config ID is mismatching at byte %-2d: limit=0x%02x, value=0x%02x\n", + i, config_id_limit[i], tcm_hcd->app_info.customer_config_id[i]); + testing_hcd->result = false; + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return 0; +} + +static int testing_pt01_trx_trx_short(void) +{ + int retval; + int i, j; + int phy_pin; + bool do_pin_test = false; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + unsigned int limits_size; + unsigned int limits_row; + unsigned int limits_col; + unsigned char *buf; + unsigned char pt1_limits; + unsigned char data; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:%s\n", STR(TEST_PT1_TRX_TRX_SHORTS)); + testing_hcd->result = false; + + app_info = &tcm_hcd->app_info; + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + limits_size = testing_get_limits_bytes(&limits_row, &limits_col, TEST_PT1_TRX_TRX_SHORTS); + retval = testing_load_testlimits(TEST_PT1_TRX_TRX_SHORTS, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", TEST_PT1_TRX_TRX_SHORTS); + retval = -EINVAL; + goto exit; + } + + if (limits_size != testing_hcd->pt_hi_limits.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + retval = testing_run_prod_test_item(TEST_PT1_TRX_TRX_SHORTS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + retval = -EIO; + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (limits_size < testing_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (i = 0; i < testing_hcd->resp.data_length; i++) { + data = buf[i]; + pt1_limits = testing_hcd->pt_hi_limits.buf[i]; + LOGI(tcm_hcd->pdev->dev.parent, + "[%d]: 0x%02x, limit[0x%02x]\n", + i, data, pt1_limits); + for (j = 0; j < 8; j++) { + phy_pin = (i*8 + j); + + do_pin_test = true; + + if (do_pin_test) { + if (CHECK_BIT(data, j) != CHECK_BIT(pt1_limits, j)) { + LOGE(tcm_hcd->pdev->dev.parent, + "pin-%2d : fail\n", phy_pin); + testing_hcd->result = false; + } + else + LOGD(tcm_hcd->pdev->dev.parent, + "pin-%2d : pass\n", phy_pin); + } + } + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + +exit: + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + + +static int testing_pt03_trx_ground(void) +{ + int retval; + int i, j; + int phy_pin; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + unsigned int limits_size; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned char pt3_limits; + unsigned char data; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:%s\n", STR(TEST_PT3_TRX_GROUND_SHORTS)); + testing_hcd->result = false; + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + limits_size = testing_get_limits_bytes(&limits_rows, &limits_cols, TEST_PT3_TRX_GROUND_SHORTS); + retval = testing_load_testlimits(TEST_PT3_TRX_GROUND_SHORTS, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", TEST_PT3_TRX_GROUND_SHORTS); + retval = -EINVAL; + goto exit; + } + + if (limits_size != testing_hcd->pt_hi_limits.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + retval = testing_run_prod_test_item(TEST_PT3_TRX_GROUND_SHORTS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + retval = -EIO; + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (limits_size < testing_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + testing_hcd->result = true; + + for (i = 0; i < testing_hcd->resp.data_length; i++) { + + data = testing_hcd->resp.buf[i]; + pt3_limits = testing_hcd->pt_hi_limits.buf[i]; + LOGI(tcm_hcd->pdev->dev.parent, "[%d]: 0x%02x, limit[0x%02x]\n",i , data, pt3_limits); + + for (j = 0; j < 8; j++) { + + phy_pin = (i*8 + j); + + if (CHECK_BIT(data, j) != CHECK_BIT(pt3_limits, j)) { + LOGE(tcm_hcd->pdev->dev.parent, + "pin-%2d : fail\n", phy_pin); + testing_hcd->result = false; + } + else + LOGD(tcm_hcd->pdev->dev.parent, + "pin-%2d : pass\n", phy_pin); + } + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + +exit: + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + +static int testing_pt05_full_raw(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_size; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + unsigned short data; + unsigned short pt5_hi, pt5_lo; + char *pt5_hi_limits; + char *pt5_lo_limits; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:%s\n",STR(TEST_PT5_FULL_RAW_CAP)); + testing_hcd->result = false; + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + LOCK_BUFFER(testing_hcd->pt_lo_limits); + limits_size = testing_get_limits_bytes(&limits_rows, &limits_cols, TEST_PT5_FULL_RAW_CAP); + retval = testing_load_testlimits(TEST_PT5_FULL_RAW_CAP, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", TEST_PT5_FULL_RAW_CAP); + retval = -EINVAL; + goto exit; + } + + if ((limits_size != testing_hcd->pt_hi_limits.data_length) || + (limits_size != testing_hcd->pt_lo_limits.data_length)){ + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + frame_size = rows * cols * 2; + + retval = testing_run_prod_test_item(TEST_PT5_FULL_RAW_CAP); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if ((limits_size != frame_size) && + (frame_size != testing_hcd->resp.data_length)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + buf = testing_hcd->resp.buf; + pt5_hi_limits = testing_hcd->pt_hi_limits.buf; + pt5_lo_limits = testing_hcd->pt_lo_limits.buf; + testing_hcd->result = true; + + idx = 0; + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + + data = (unsigned short)(buf[idx] & 0xff) | + (unsigned short)(buf[idx+1] << 8); + pt5_hi = (unsigned short)(pt5_hi_limits[idx] & 0xff) | + (unsigned short)(pt5_hi_limits[idx+1] << 8); + pt5_lo = (unsigned short)(pt5_lo_limits[idx] & 0xff) | + (unsigned short)(pt5_lo_limits[idx+1] << 8); + + if (data > pt5_hi || data < pt5_lo) { + LOGE(tcm_hcd->pdev->dev.parent, + "fail at (%2d, %2d) data = %5d, limit = [%4d, %4d]\n", + row, col, data, pt5_lo, pt5_hi); + + testing_hcd->result = false; + } + + idx += 2; + } + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + + retval = 0; + +exit: + UNLOCK_BUFFER(testing_hcd->pt_lo_limits); + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + + +static int testing_pt07_dynamic_range(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int data; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing\n"); + testing_hcd->result = false; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&frame_size_words, false); + + retval = testing_run_prod_test_item(TEST_PT7_DYNAMIC_RANGE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = + sizeof(pt7_hi_limits) / sizeof(pt7_hi_limits[0]); + limits_cols = + sizeof(pt7_hi_limits[0]) / sizeof(pt7_hi_limits[0][0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = + sizeof(pt7_lo_limits) / sizeof(pt7_lo_limits[0]); + limits_cols = + sizeof(pt7_lo_limits[0]) / sizeof(pt7_lo_limits[0][0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = le2_to_uint(&buf[idx * 2]); + if (data > pt7_hi_limits[row][col] || + data < pt7_lo_limits[row][col]) { + + LOGE(tcm_hcd->pdev->dev.parent, + "fail at (%2d, %2d) data = %5d, limit = (%4d, %4d)\n", + row, col, data, pt7_lo_limits[row][col], + pt7_hi_limits[row][col]); + + testing_hcd->result = false; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(false); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + +static int testing_pt10_noise(void) +{ + int retval; + short data; + short pt10_limits; + unsigned char *pt10_buf; + unsigned char *buf; + unsigned int idx; + unsigned int limits_size; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int frame_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:%s\n", STR(TEST_PT10_DELTA_NOISE)); + testing_hcd->result = false; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&frame_size_words, true); + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + + limits_size = testing_get_limits_bytes(&limits_rows, &limits_cols, TEST_PT10_DELTA_NOISE); + retval = testing_load_testlimits(TEST_PT10_DELTA_NOISE, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", TEST_PT10_DELTA_NOISE); + retval = -EINVAL; + goto exit; + } + + if (limits_size != testing_hcd->pt_hi_limits.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + retval = testing_run_prod_test_item(TEST_PT10_DELTA_NOISE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + pt10_buf = testing_hcd->pt_hi_limits.buf; + pt10_limits = (unsigned short)(pt10_buf[0] & 0xff) | + (unsigned short)(pt10_buf[1] << 8); + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > pt10_limits) { + + LOGE(tcm_hcd->pdev->dev.parent, + "fail at (%2d, %2d) data = %5d, limit = %4d\n", + row, col, data, pt10_limits); + + testing_hcd->result = false; + } + idx++; + } + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + + retval = 0; + +exit: + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + + +static int testing_pt11_open_detection(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing\n"); + testing_hcd->result = false; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT11_OPEN_DETECTION); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = + sizeof(pt11_hi_limits) / sizeof(pt11_hi_limits[0]); + limits_cols = + sizeof(pt11_hi_limits[0]) / sizeof(pt11_hi_limits[0][0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = + sizeof(pt11_lo_limits) / sizeof(pt11_lo_limits[0]); + limits_cols = + sizeof(pt11_lo_limits[0]) / sizeof(pt11_lo_limits[0][0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > pt11_hi_limits[row][col] || + data < pt11_lo_limits[row][col]) { + + LOGE(tcm_hcd->pdev->dev.parent, + "fail at (%2d, %2d) data = %5d, limit = (%4d, %4d)\n", + row, col, data, pt11_lo_limits[row][col], + pt11_hi_limits[row][col]); + + testing_hcd->result = false; + } + idx++; + } + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + +static int testing_pt18_hybrid_abs_raw(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_size; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + int data; + int pt18_hi, pt18_lo; + char *pt18_hi_limits; + char *pt18_lo_limits; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:%s\n", STR(TEST_PT18_HYBRID_ABS_RAW)); + testing_hcd->result = false; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + frame_size = (rows + cols) * 4; + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + LOCK_BUFFER(testing_hcd->pt_lo_limits); + + limits_size = testing_get_limits_bytes(&limits_rows, &limits_cols, TEST_PT18_HYBRID_ABS_RAW); + retval = testing_load_testlimits(TEST_PT18_HYBRID_ABS_RAW, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", TEST_PT18_HYBRID_ABS_RAW); + retval = -EINVAL; + goto exit; + } + + if ((limits_size != frame_size) || + (limits_size != testing_hcd->pt_hi_limits.data_length) || + (limits_size != testing_hcd->pt_lo_limits.data_length)){ + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + retval = testing_run_prod_test_item(TEST_PT18_HYBRID_ABS_RAW); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size != testing_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size is mismatching\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + if ((rows +cols) != limits_cols || limits_rows != 1) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + pt18_hi_limits = testing_hcd->pt_hi_limits.buf; + pt18_lo_limits = testing_hcd->pt_lo_limits.buf; + testing_hcd->result = true; + + idx = 0; + for (col = 0; col < limits_cols; col++) { + data = (int)(buf[idx] & 0xff) | + (int)(buf[idx+1] << 8) | + (int)(buf[idx+2] << 16) | + (int)(buf[idx+3] << 24); + + pt18_hi = (int)(pt18_hi_limits[idx] & 0xff) | + (int)(pt18_hi_limits[idx+1] << 8) | + (int)(pt18_hi_limits[idx+2] << 16) | + (int)(pt18_hi_limits[idx+3] << 24); + + pt18_lo = (int)(pt18_lo_limits[idx] & 0xff) | + (int)(pt18_lo_limits[idx+1] << 8) | + (int)(pt18_lo_limits[idx+2] << 16) | + (int)(pt18_lo_limits[idx+3] << 24); + + if ((data > pt18_hi) || (data < pt18_lo)) { + testing_hcd->result = false; + LOGE(tcm_hcd->pdev->dev.parent, + "fail at index = %-2d. data = %d, limit = [%d, %d]\n", + col, data, pt18_lo, pt18_hi); + } + idx+=4; + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + + retval = 0; + +exit: + UNLOCK_BUFFER(testing_hcd->pt_lo_limits); + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + + +static int testing_reset_open(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing\n"); + testing_hcd->result = false; + + if (bdata->reset_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Hardware reset unavailable\n"); + return -EINVAL; + } + + mutex_lock(&tcm_hcd->reset_mutex); + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, false); +#endif + + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + +#ifdef WATCHDOG_SW + tcm_hcd->update_watchdog(tcm_hcd, true); +#endif + + mutex_unlock(&tcm_hcd->reset_mutex); + + if (tcm_hcd->id_info.mode == MODE_APPLICATION_FIRMWARE) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } else { + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto run_app_firmware; + } + } + + if (tcm_hcd->boot_info.last_reset_reason == reset_open_limit) + testing_hcd->result = true; + else + testing_hcd->result = false; + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + +static int testing_pt22_trans_raw_cap(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row, col; + unsigned int rows, cols; + unsigned int limits_size; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + unsigned short data, pt22_hi, pt22_lo; + char *pt22_hi_limits; + char *pt22_lo_limits; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:%s\n",STR(TEST_PT22_TRANS_RAW_CAP)); + testing_hcd->result = false; + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + LOCK_BUFFER(testing_hcd->pt_lo_limits); + + retval = testing_load_testlimits(TEST_PT22_TRANS_RAW_CAP, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", TEST_PT22_TRANS_RAW_CAP); + retval = -EINVAL; + goto exit; + } + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + frame_size = rows * cols * 2; + + limits_size = testing_get_limits_bytes(&limits_rows, &limits_cols, TEST_PT22_TRANS_RAW_CAP); + retval = testing_run_prod_test_item(TEST_PT22_TRANS_RAW_CAP); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + if ((frame_size != limits_size) || + (limits_size != testing_hcd->pt_hi_limits.data_length) || + (limits_size != testing_hcd->pt_lo_limits.data_length)){ + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size != testing_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + buf = testing_hcd->resp.buf; + pt22_hi_limits = testing_hcd->pt_hi_limits.buf; + pt22_lo_limits = testing_hcd->pt_lo_limits.buf; + testing_hcd->result = true; + + /* check PT22 min/max limits */ + idx = 0; + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + + data = (unsigned short)(buf[idx] & 0xff) | + (unsigned short)(buf[idx+1] << 8); + pt22_hi = (unsigned short)(pt22_hi_limits[idx] & 0xff) | + (unsigned short)(pt22_hi_limits[idx+1] << 8); + pt22_lo = (unsigned short)(pt22_lo_limits[idx] & 0xff) | + (unsigned short)(pt22_lo_limits[idx+1] << 8); + + if (data > pt22_hi || data < pt22_lo) { + LOGE(tcm_hcd->pdev->dev.parent, + "fail at (%2d, %2d) data = %5d, limit = [%4d, %4d]\n", + row, col, data, pt22_lo, pt22_hi); + + testing_hcd->result = false; + } + + idx += 2; + } + } + + testing_copy_resp_to_output(); + + UNLOCK_BUFFER(testing_hcd->resp); + + retval = 0; + +exit: + UNLOCK_BUFFER(testing_hcd->pt_lo_limits); + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + +static int testing_gap_diff(void) +{ + int retval; + unsigned char *buf; + unsigned int row, col; + unsigned int rows, cols; + unsigned int limits_size; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + short data, d0, d1, d2, d3, dmax, diff; + short gap_diff_limits; + char *gap_diff_limits_buf; + char *gap_diff_data_buf = NULL; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start testing:GAP_DIFF_TEST based on PT%d output data\n", testing_hcd->test_item); + testing_hcd->result = false; + + LOCK_BUFFER(testing_hcd->pt_hi_limits); + + retval = testing_load_testlimits(testing_hcd->test_item, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to load PT%d limits from csv file\n", testing_hcd->test_item); + goto exit; + } + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + frame_size = rows * cols * 2; + + limits_size = testing_get_limits_bytes(&limits_rows, &limits_cols, testing_hcd->test_item); + /* data in output buffer, no need to read from FW */ + if ((limits_size != frame_size) || + (limits_size != testing_hcd->pt_hi_limits.data_length)){ + LOGE(tcm_hcd->pdev->dev.parent, "Mismatching limits size\n"); + } + + /* calc the gap diff based on the data in output */ + LOCK_BUFFER(testing_hcd->output); + + if (frame_size != testing_hcd->output.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->output); + retval = -EINVAL; + goto unlock_bufffer; + } + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->output); + retval = -EINVAL; + goto unlock_bufffer; + } + + /* alloc the buf to store the gap diff data */ + gap_diff_data_buf = kzalloc(frame_size, GFP_KERNEL); + if (!gap_diff_data_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for gap_diff_data_buf\n"); + retval = -ENOMEM; + goto unlock_bufffer; + } + + buf = testing_hcd->output.buf; + gap_diff_limits_buf = testing_hcd->pt_hi_limits.buf; + testing_hcd->result = true; + + /* calc the gap diff */ + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + gap_diff_limits = le2_to_uint(&gap_diff_limits_buf[(row*cols + col)*2]); + data = le2_to_uint(&buf[(row*cols + col)*2]); + + d0 = (row == 0) ? data : (le2_to_uint(&buf[((row-1)*cols + col)*2])); + d1 = (row == (rows-1)) ? data : (le2_to_uint(&buf[((row+1)*cols + col)*2])); + d2 = (col == 0) ? data : (le2_to_uint(&buf[(row*cols + (col-1))*2])); + d3 = (col == (cols-1)) ? data : (le2_to_uint(&buf[(row*cols + (col+1))*2])); + + dmax = MAX(ABS(data-d0), ABS(data-d1)); + dmax = MAX(dmax, ABS(data-d2)); + dmax = MAX(dmax, ABS(data-d3)); + + if (data == 0) { + diff = 100; + } else { + diff = (unsigned short)((unsigned int)dmax*100/data); + } + if (diff > gap_diff_limits) { + LOGE(tcm_hcd->pdev->dev.parent, + "fail at (%2d, %2d), diff_max = %4d, data = %4d, gap_diff = %4d, limit = %4d\n", + row, col, dmax, data, diff, gap_diff_limits); + + testing_hcd->result = false; + } + + /* backup the diff data */ + uint_to_le2(&gap_diff_data_buf[(row*cols + col)*2], diff); + } + } + + /* save the diff data back to output buf */ + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + gap_diff_data_buf, + frame_size, + frame_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test resp data\n"); + goto unlock_bufffer; + } + testing_hcd->output.data_length = frame_size; + +unlock_bufffer: + UNLOCK_BUFFER(testing_hcd->output); + +exit: + UNLOCK_BUFFER(testing_hcd->pt_hi_limits); + + if (gap_diff_data_buf) + kfree(gap_diff_data_buf); + LOGN(tcm_hcd->pdev->dev.parent, + "Result = %s\n", (testing_hcd->result) ? "pass" : "fail"); + + return retval; +} + +static int testing_self_test(char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + char result_info[RESULT_INFO_LEN] = {0}; + char *save_buf = NULL; + unsigned int offset = 0; + unsigned int cnt; + unsigned int result_flag = 0; + unsigned int gap_diff_test_result = 0; + unsigned int mask; + + mask = (1<result = false; + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto exit; + } + +#ifdef CONFIG_FACTORY_BUILD +#if TESTING_RESULT_IN_CSV + save_buf = kzalloc(SAVE_BUF_SIZE, GFP_KERNEL); + if (!save_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for printBuf\n"); + retval = -ENOMEM; + goto exit; + } + offset = 0; +#endif +#endif + + LOGI(tcm_hcd->pdev->dev.parent, "\n"); + LOGI(tcm_hcd->pdev->dev.parent, "Start Panel testing\n"); + + /* PT1 Test */ + retval = testing_pt01_trx_trx_short(); + if (retval < 0) { + goto exit; + } + result_flag |= (testing_hcd->result) ? (1<result) ? (1<result) ? (1<result) ? 1 : 0; + offset = testing_save_output(save_buf, offset, "GAP_DIFF"); + + /* PT10 Test */ + retval = testing_pt10_noise(); + if (retval < 0) { + goto exit; + } + result_flag |= (testing_hcd->result) ? (1<result) ? (1<result = ((result_flag & mask) == mask) ? true : false; + + cnt = snprintf(result_info, RESULT_INFO_LEN, + "Panel Test Result = %s: PT1 = %s PT3 = %s PT22 = %s GAP_DIFF = %s PT10 = %s PT18 = %s\n", + (testing_hcd->result) ? "pass" : "fail", + (result_flag & (1<pdev->dev.parent, "%s\n", result_info); + +#ifdef CONFIG_FACTORY_BUILD +#if TESTING_RESULT_IN_CSV + cnt = snprintf(save_buf + offset, SAVE_BUF_SIZE - offset, "%s\n", result_info); + offset += cnt; + retval = testing_result_save_csv(save_buf, offset); +#endif +#endif + + if (buf != NULL) + snprintf(buf, RESULT_INFO_LEN, "%s", result_info); + +exit: + LOGN(tcm_hcd->pdev->dev.parent, + "Panel Test Result = %s\n", (testing_hcd->result)?"pass":"fail"); + return retval; +} + +static ssize_t testing_sysfs_self_test_show(struct kobject *kobj, + struct kobj_attribute *attributes, char *buf) +{ + int retval; + char result_info[RESULT_INFO_LEN + 1] = {0}; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_self_test(result_info); + + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do self_test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int testing_xiaomi_self_test(char *buf) +{ + int retval; + char result_info[RESULT_INFO_LEN + 1] = {0}; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto exit; + } + + retval = testing_self_test(result_info); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do self_test, testing_hcd->result = %d\n", + testing_hcd->result); + goto exit; + } + +exit: + if (testing_hcd->result) + retval = true; + else + retval = false; + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int testing_print_report_data(enum report_type report_type, char *buf) +{ + int retval; + int data; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int has_hybrid; + unsigned int idx; + unsigned int cnt, offset; + unsigned char *data_buf = NULL; + char *pfmt = NULL; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + has_hybrid = le2_to_uint(app_info->has_hybrid_data); + + /* print data */ + LOCK_BUFFER(testing_hcd->report); + pfmt = (report_type == REPORT_RID161) ? (FORMAT_4U) : (FORMAT_4D); + offset = 0; + cnt = snprintf(buf + offset, PAGE_SIZE - offset, "RID%d: Rows=%d Cols=%d\n", report_type, rows, cols); + offset += cnt; + data_buf = testing_hcd->report.buf; + + idx = 0; + for (row = 0; row < rows; row++) { + cnt = 0; + for (col = 0; col < cols; col++) { + data = (int)((unsigned short)(data_buf[idx] & 0xff) | + (unsigned short)(data_buf[idx+1] << 8)); + cnt = snprintf(buf + offset, PAGE_SIZE - offset, pfmt, data); + offset += cnt; + + idx += 2; + } + + cnt = snprintf(buf + offset, PAGE_SIZE - offset, "\n"); + offset += cnt; + } + + if (has_hybrid) { + pfmt = (report_type == REPORT_RID161) ? (FORMAT_5U) : (FORMAT_5D); + /* print hybrid-x data */ + cnt = snprintf(buf + offset, PAGE_SIZE - offset, "Hybird-X:\n"); + offset += cnt; + for (col =0; col < cols; col++) { + data = (int)((unsigned short)(data_buf[idx] & 0xff) | + (unsigned short)(data_buf[idx+1] << 8)); + cnt = snprintf(buf + offset, PAGE_SIZE - offset, pfmt, data); + offset += cnt; + + idx += 2; + } + cnt = snprintf(buf + offset, PAGE_SIZE - offset, "\n"); + offset += cnt; + + /* print hybrid-y data */ + cnt = snprintf(buf + offset, PAGE_SIZE - offset, "Hybird-Y:\n"); + offset += cnt; + for (row = 0; row < rows; row++) { + data = (int)((unsigned short)(data_buf[idx] & 0xff) | + (unsigned short)(data_buf[idx+1] << 8)); + cnt = snprintf(buf + offset, PAGE_SIZE - offset, pfmt, data); + offset += cnt; + + idx += 2; + } + cnt = snprintf(buf + offset, PAGE_SIZE - offset, "\n"); + offset += cnt; + } + + retval = offset; + UNLOCK_BUFFER(testing_hcd->report); + + return retval; +} + +static int testing_xiaomi_report_data(int report_type, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOGI(tcm_hcd->pdev->dev.parent, "collect RID %d start...\n", report_type); + + retval = testing_hcd->collect_reports(report_type, 0x01); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read RID %d data\n", report_type); + goto exit; + } + + LOGI(tcm_hcd->pdev->dev.parent, "collect RID %d done\n", report_type); + + retval = testing_print_report_data(report_type, buf); + + LOGI(tcm_hcd->pdev->dev.parent, + "retval=%d, PAGE_SIZE=%ld\n", retval, PAGE_SIZE); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_rid18_data_show(struct kobject *kobj, + struct kobj_attribute *attributes, char *buf) +{ + int retval; + + retval = testing_xiaomi_report_data(REPORT_DELTA, buf); + + return retval; +} + +static ssize_t testing_sysfs_rid161_data_show(struct kobject *kobj, + struct kobj_attribute *attributes, char *buf) +{ + int retval; + + retval = testing_xiaomi_report_data(REPORT_RID161, buf); + + return retval; +} + +static void testing_report(void) +{ + int retval; + unsigned int offset; + unsigned int report_size; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + report_size = tcm_hcd->report.buffer.data_length; + + LOCK_BUFFER(testing_hcd->report); + + if (testing_hcd->report_index == 0) { + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->report, + report_size * testing_hcd->num_of_reports); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->report.buf\n"); + UNLOCK_BUFFER(testing_hcd->report); + return; + } + } + + if (testing_hcd->report_index < testing_hcd->num_of_reports) { + offset = report_size * testing_hcd->report_index; + + retval = secure_memcpy(testing_hcd->report.buf + offset, + testing_hcd->report.buf_size - offset, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(testing_hcd->report); + return; + } + + testing_hcd->report_index++; + testing_hcd->report.data_length += report_size; + } + + UNLOCK_BUFFER(testing_hcd->report); + + if (testing_hcd->report_index == testing_hcd->num_of_reports) + complete(&report_complete); + + return; +} + + +static int testing_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + + testing_hcd = kzalloc(sizeof(*testing_hcd), GFP_KERNEL); + if (!testing_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd\n"); + return -ENOMEM; + } + + testing_hcd->tcm_hcd = tcm_hcd; + + testing_hcd->collect_reports = testing_collect_reports; + + INIT_BUFFER(testing_hcd->out, false); + INIT_BUFFER(testing_hcd->resp, false); + INIT_BUFFER(testing_hcd->report, false); + INIT_BUFFER(testing_hcd->process, false); + INIT_BUFFER(testing_hcd->output, false); + INIT_BUFFER(testing_hcd->pt_hi_limits, false); + INIT_BUFFER(testing_hcd->pt_lo_limits, false); + + testing_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!testing_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(testing_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(testing_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + tcm_hcd->testing_xiaomi_report_data = testing_xiaomi_report_data; + tcm_hcd->testing_xiaomi_self_test = testing_xiaomi_self_test; + tcm_hcd->testing_xiaomi_chip_id_read = testing_device_id; + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(testing_hcd->sysfs_dir); + +err_sysfs_create_dir: + RELEASE_BUFFER(testing_hcd->pt_lo_limits); + RELEASE_BUFFER(testing_hcd->pt_hi_limits); + RELEASE_BUFFER(testing_hcd->output); + RELEASE_BUFFER(testing_hcd->process); + RELEASE_BUFFER(testing_hcd->report); + RELEASE_BUFFER(testing_hcd->resp); + RELEASE_BUFFER(testing_hcd->out); + + kfree(testing_hcd); + testing_hcd = NULL; + + return retval; +} + +static int testing_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!testing_hcd) + goto exit; + + sysfs_remove_bin_file(testing_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(testing_hcd->sysfs_dir); + + RELEASE_BUFFER(testing_hcd->pt_lo_limits); + RELEASE_BUFFER(testing_hcd->pt_hi_limits); + RELEASE_BUFFER(testing_hcd->output); + RELEASE_BUFFER(testing_hcd->process); + RELEASE_BUFFER(testing_hcd->report); + RELEASE_BUFFER(testing_hcd->resp); + RELEASE_BUFFER(testing_hcd->out); + + kfree(testing_hcd); + testing_hcd = NULL; + +exit: + complete(&testing_remove_complete); + + return 0; +} + +static int testing_reinit(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!testing_hcd) { + retval = testing_init(tcm_hcd); + return retval; + } + + return 0; +} + +static int testing_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!testing_hcd) + return 0; + + if (tcm_hcd->report.id == testing_hcd->report_type) + testing_report(); + + return 0; +} + +static struct syna_tcm_module_cb testing_module = { + .type = TCM_TESTING, + .init = testing_init, + .remove = testing_remove, + .syncbox = testing_syncbox, +#ifdef REPORT_NOTIFIER + .asyncbox = NULL, +#endif + .reinit = testing_reinit, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init testing_module_init(void) +{ + return syna_tcm_add_module(&testing_module, true); +} + +static void __exit testing_module_exit(void) +{ + syna_tcm_add_module(&testing_module, false); + + wait_for_completion(&testing_remove_complete); + + return; +} + +module_init(testing_module_init); +module_exit(testing_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Testing Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_testing.h b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_testing.h new file mode 100755 index 000000000000..bb17e5e49eb2 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_testing.h @@ -0,0 +1,271 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_TESTING_H_ +#define _SYNAPTICS_TCM_TESTING_H_ + +#define ABS(x) (((x) < 0) ? (-(x)) : (x)) +#define DATA_SIZE_MAX (30) +#define SAVE_BUF_SIZE (4096*2) +#define RESULT_INFO_LEN (200) + +#define PT1_PT3_LIMITS_BYTES_SIZE (8) +#define PT10_LIMITS_BYTES_SIZE (1) +#define SYNA_TCM_TESTING_LIMITS_FILE_NAME "k9b_test_limits_S3908P.csv" +#define CSV_PT1_TESTING_LIMITS "PT1_TRx_TRx_short_test" +#define CSV_PT3_TESTING_LIMITS "PT3_TRX_GND_short_test" +#define CSV_PT5_TESTING_LIMITS_MIN "PT5_Full_raw_cap_test_min" +#define CSV_PT5_TESTING_LIMITS_MAX "PT5_Full_raw_cap_test_max" +#define CSV_PT10_TESTING_LIMITS "PT10_(Mutal)_noise_test" +#define CSV_PT18_TESTING_LIMITS_MIN "PT18_Abs_raw_cap_test_min" +#define CSV_PT18_TESTING_LIMITS_MAX "PT18_Abs_raw_cap_test_max" +#define CSV_PT22_TESTING_LIMITS_MIN "PT22_Trans_raw_cap_test_min" +#define CSV_PT22_TESTING_LIMITS_MAX "PT22_Trans_raw_cap_test_max" +#define CSV_GAP_DIFF_TESTING_LIMITS_MAX "Gap_Diff_test_max" + +#define TESTING_RESULT_IN_CSV (1) +#if TESTING_RESULT_IN_CSV +#define SYNA_TCM_TESTING_RESULT_SAVE_PATH "/data/k9b_tp_test_result.csv" +#endif + +static inline void uint_to_le1(unsigned char *dest, unsigned int val) +{ + dest[0] = val%0x100; +} + +static inline void uint_to_le2(unsigned char *dest, unsigned int val) +{ + dest[0] = val%0x100; + dest[1] = val/0x100; +} + +static inline void uint_to_le4(unsigned char *dest, unsigned int val) +{ + unsigned int vl, vh; + vl = val%0x10000; + vh = val/0x10000; + uint_to_le2(dest, vl); + uint_to_le2(dest + 2, vh); +} + +/* test limit for the device id checking */ +static const char *device_id_limit = "s3908p-15.0.0"; + +/* test limit for the config id checking */ +static const char config_id_limit[16] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x47 +}; + +/* test limit for the pt7 testing */ +/* including upper and lower bounds */ +static const unsigned short pt7_hi_limits[40][40] = { + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, + {1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700}, +}; + +static const unsigned short pt7_lo_limits[40][40] = { + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300}, + {300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300} +}; + +/* test limit for the pt11 testing */ +/* including upper and lower bounds */ +static const short pt11_hi_limits[40][40] = { + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, +}; + +static const short pt11_lo_limits[40][40] = { + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8,-8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, + {-8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8}, +}; + +/* test limit for the hw reset pin testing */ +static const unsigned char reset_open_limit = 0x13; + +#endif /* end of _SYNAPTICS_TCM_TESTING_H_ */ diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_touch.c b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_touch.c new file mode 100755 index 000000000000..07f8e8f7e0ef --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_touch.c @@ -0,0 +1,1539 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include +#include +#include "synaptics_tcm_core.h" + +#define TYPE_B_PROTOCOL + +//#define USE_DEFAULT_TOUCH_REPORT_CONFIG + +#define TOUCH_REPORT_CONFIG_SIZE 128 + +enum touch_status { + LIFT = 0, + FINGER = 1, + GLOVED_FINGER = 2, + PALM = 6, + NOP = -1, +}; + +enum gesture_id { + NO_GESTURE_DETECTED = 0, + GESTURE_DOUBLE_TAP = 0x01, + GESTURE_SINGLE_TAP = 0x10, + GESTURE_TOUCH_AND_HOLD_DOWN_EVENT = 0x80, + GESTURE_TOUCH_AND_HOLD_UP_EVENT = 0x81, + GESTURE_TOUCH_AND_HOLD_MOVE_EVENT = 0x83, +}; + +enum touch_report_code { + TOUCH_END = 0, + TOUCH_FOREACH_ACTIVE_OBJECT, + TOUCH_FOREACH_OBJECT, + TOUCH_FOREACH_END, + TOUCH_PAD_TO_NEXT_BYTE, + TOUCH_TIMESTAMP, + TOUCH_OBJECT_N_INDEX, + TOUCH_OBJECT_N_CLASSIFICATION, + TOUCH_OBJECT_N_X_POSITION, + TOUCH_OBJECT_N_Y_POSITION, + TOUCH_OBJECT_N_Z, + TOUCH_OBJECT_N_X_WIDTH, + TOUCH_OBJECT_N_Y_WIDTH, + TOUCH_OBJECT_N_TX_POSITION_TIXELS, + TOUCH_OBJECT_N_RX_POSITION_TIXELS, + TOUCH_0D_BUTTONS_STATE, + TOUCH_GESTURE_ID, + TOUCH_FRAME_RATE, + TOUCH_POWER_IM, + TOUCH_CID_IM, + TOUCH_RAIL_IM, + TOUCH_CID_VARIANCE_IM, + TOUCH_NSM_FREQUENCY, + TOUCH_NSM_STATE, + TOUCH_NUM_OF_ACTIVE_OBJECTS, + TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME, + TOUCH_FACE_DETECT, + TOUCH_GESTURE_DATA, + TOUCH_OBJECT_N_FORCE, + TOUCH_FINGERPRINT_AREA_MEET, + TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80, + TOUCH_TUNING_SMALL_OBJECT_PARAMS, + TOUCH_TUNING_0D_BUTTONS_VARIANCE, +}; + +struct object_data { + unsigned char status; + unsigned int x_pos; + unsigned int y_pos; + unsigned int x_width; + unsigned int y_width; + unsigned int z; + unsigned int tx_pos; + unsigned int rx_pos; +}; + +struct input_params { + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; +}; + +struct gesture_data { + unsigned char x_pos[2]; + unsigned char y_pos[2]; + unsigned char area[2]; +}; + +struct touch_data { + struct object_data *object_data; + unsigned int timestamp; + unsigned int buttons_state; + unsigned int gesture_id; + struct gesture_data gesture_data; + unsigned int frame_rate; + unsigned int power_im; + unsigned int cid_im; + unsigned int rail_im; + unsigned int cid_variance_im; + unsigned int nsm_frequency; + unsigned int nsm_state; + unsigned int num_of_active_objects; + unsigned int num_of_cpu_cycles; + unsigned int fd_data; + unsigned int force_data; + unsigned int fingerprint_area_meet; +}; + +struct touch_hcd { + bool irq_wake; + bool init_touch_ok; + bool suspend_touch; + unsigned char *prev_status; + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; + struct mutex report_mutex; + struct input_dev *input_dev; + struct touch_data touch_data; + struct input_params input_params; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_hcd *tcm_hcd; +}; + +static struct touch_hcd *touch_hcd; +static unsigned int pre_overlap = 0; + +#define CENTER_X_MONA 5400 +#define CENTER_Y_MONA 21470 +void touch_fod_test(int value) { + if (value) { + input_report_key(touch_hcd->input_dev, BTN_INFO, 1); + mi_disp_set_fod_queue_work(1, true); + input_sync(touch_hcd->input_dev); + input_mt_slot(touch_hcd->input_dev, 0); + input_mt_report_slot_state(touch_hcd->input_dev, MT_TOOL_FINGER, 1); + input_report_key(touch_hcd->input_dev, BTN_TOUCH, 1); + input_report_key(touch_hcd->input_dev, BTN_TOOL_FINGER, 1); + input_report_abs(touch_hcd->input_dev, ABS_MT_TRACKING_ID, 0); + input_report_abs(touch_hcd->input_dev, ABS_MT_WIDTH_MINOR, 1); + input_report_abs(touch_hcd->input_dev, ABS_MT_POSITION_X, CENTER_X_MONA); + input_report_abs(touch_hcd->input_dev, ABS_MT_POSITION_Y, CENTER_Y_MONA); + input_sync(touch_hcd->input_dev); + } else { + input_mt_slot(touch_hcd->input_dev, 0); + input_report_abs(touch_hcd->input_dev, ABS_MT_WIDTH_MINOR, 0); + input_mt_report_slot_state(touch_hcd->input_dev, MT_TOOL_FINGER, 0); + input_report_abs(touch_hcd->input_dev, ABS_MT_TRACKING_ID, -1); + input_report_key(touch_hcd->input_dev, BTN_INFO, 0); + mi_disp_set_fod_queue_work(0, true); + input_sync(touch_hcd->input_dev); + } +} + + +static void touch_fod_down_event(void) +{ + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + /* Todo: add customer FOD action. */ + if (!tcm_hcd->fod_display_enabled) { + input_report_key(touch_hcd->input_dev, BTN_INFO, 1); + input_sync(touch_hcd->input_dev); + LOGI(tcm_hcd->pdev->dev.parent, "FOD DOWN Dfetected\n"); + tcm_hcd->fod_display_enabled = true; + mi_disp_set_fod_queue_work(1, true); + } +} + +static void touch_fod_up_event(void) +{ + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + LOGI(tcm_hcd->pdev->dev.parent, "FOD UP Detected\n"); + input_report_key(touch_hcd->input_dev, BTN_INFO, 0); + input_sync(touch_hcd->input_dev); + tcm_hcd->fod_display_enabled = false; + mi_disp_set_fod_queue_work(0, true); +} + +int touch_flush_slots(struct syna_tcm_hcd *tcm_hcd) +{ + unsigned int idx; + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + if (touch_hcd->input_dev == NULL) + return 0; + + mutex_lock(&touch_hcd->report_mutex); + + /* finger down */ + for (idx = 0; idx < touch_hcd->max_objects; idx++) { +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 1); +#endif + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 1); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(touch_hcd->input_dev, + ABS_MT_TOUCH_MAJOR, 0); + +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + } + input_sync(touch_hcd->input_dev); + + mutex_unlock(&touch_hcd->report_mutex); + + /* finger up */ + touch_free_objects(tcm_hcd); + + LOGN(tcm_hcd->pdev->dev.parent, "-----exit-----%s\n", __func__); + + return 0; +} + +/** + * touch_free_objects() - Free all touch objects + * + * Report finger lift events to the input subsystem for all touch objects. + */ +int touch_free_objects(struct syna_tcm_hcd *tcm_hcd) +{ +#ifdef TYPE_B_PROTOCOL + unsigned int idx; +#endif + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter-----%s\n", __func__); + + if (touch_hcd->input_dev == NULL) + return 0; + + mutex_lock(&touch_hcd->report_mutex); + + /* clear FOD finger flag */ + if (tcm_hcd->fod_finger) { + tcm_hcd->fod_finger = false; + /*For FOD_STATUS_DELETED */ + LOGN(tcm_hcd->pdev->dev.parent, "touch free FOD event\n"); + touch_fod_up_event(); + } + +#ifdef TYPE_B_PROTOCOL + for (idx = 0; idx < touch_hcd->max_objects; idx++) { + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 0); + } +#endif + + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 0); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + input_sync(touch_hcd->input_dev); + + mutex_unlock(&touch_hcd->report_mutex); + LOGN(tcm_hcd->pdev->dev.parent, "-----exit-----%s\n", __func__); + + return 0; +} + +/** + * touch_get_report_data() - Retrieve data from touch report + * + * Retrieve data from the touch report based on the bit offset and bit length + * information from the touch report configuration. + */ +static int touch_get_report_data(unsigned int offset, + unsigned int bits, unsigned int *data) +{ + unsigned char mask; + unsigned char byte_data; + unsigned int output_data; + unsigned int bit_offset; + unsigned int byte_offset; + unsigned int data_bits; + unsigned int available_bits; + unsigned int remaining_bits; + unsigned char *touch_report; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (bits == 0 || bits > 32) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid number of bits\n"); + return -EINVAL; + } + + if (offset + bits > tcm_hcd->report.buffer.data_length * 8) { + *data = 0; + return 0; + } + + touch_report = tcm_hcd->report.buffer.buf; + + output_data = 0; + remaining_bits = bits; + + bit_offset = offset % 8; + byte_offset = offset / 8; + + while (remaining_bits) { + byte_data = touch_report[byte_offset]; + byte_data >>= bit_offset; + + available_bits = 8 - bit_offset; + data_bits = MIN(available_bits, remaining_bits); + mask = 0xff >> (8 - data_bits); + + byte_data &= mask; + + output_data |= byte_data << (bits - remaining_bits); + + bit_offset = 0; + byte_offset += 1; + remaining_bits -= data_bits; + } + + *data = output_data; + + return 0; +} + +/** + * touch_parse_report() - Parse touch report + * + * Traverse through the touch report configuration and parse the touch report + * generated by the device accordingly to retrieve the touch data. + */ +static int touch_parse_report(void) +{ + int retval; + bool active_only; + bool num_of_active_objects; + unsigned char code; + unsigned int size; + unsigned int idx; + unsigned int obj; + unsigned int next; + unsigned int data; + unsigned int bits; + unsigned int offset; + unsigned int objects; + unsigned int active_objects; + unsigned int report_size; + unsigned int config_size; + unsigned int left_bits; + unsigned int i; + unsigned char *config_data; + unsigned char *temp_data; + struct touch_data *touch_data; + struct object_data *object_data; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + static unsigned int end_of_foreach; + + touch_data = &touch_hcd->touch_data; + object_data = touch_hcd->touch_data.object_data; + + config_data = tcm_hcd->config.buf; + config_size = tcm_hcd->config.data_length; + + report_size = tcm_hcd->report.buffer.data_length; + + size = sizeof(*object_data) * touch_hcd->max_objects; + memset(touch_hcd->touch_data.object_data, 0x00, size); + + num_of_active_objects = false; + + idx = 0; + offset = 0; + objects = 0; + active_objects = 0; + active_only = false; + + while (idx < config_size) { + code = config_data[idx++]; + switch (code) { + case TOUCH_END: + goto exit; + case TOUCH_FOREACH_ACTIVE_OBJECT: + obj = 0; + next = idx; + active_only = true; + break; + case TOUCH_FOREACH_OBJECT: + obj = 0; + next = idx; + active_only = false; + break; + case TOUCH_FOREACH_END: + end_of_foreach = idx; + if (active_only) { + if (num_of_active_objects) { + objects++; + if (objects < active_objects) + idx = next; + } else if (offset < report_size * 8) { + idx = next; + } + } else { + obj++; + if (obj < touch_hcd->max_objects) + idx = next; + } + break; + case TOUCH_PAD_TO_NEXT_BYTE: + offset = ceil_div(offset, 8) * 8; + break; + case TOUCH_TIMESTAMP: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get timestamp\n"); + return retval; + } + touch_data->timestamp = data; + offset += bits; + break; + case TOUCH_OBJECT_N_INDEX: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &obj); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object index\n"); + return retval; + } + offset += bits; + break; + case TOUCH_OBJECT_N_CLASSIFICATION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object classification\n"); + return retval; + } + object_data[obj].status = data; + offset += bits; + break; + case TOUCH_OBJECT_N_X_POSITION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object x position\n"); + return retval; + } + object_data[obj].x_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Y_POSITION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object y position\n"); + return retval; + } + object_data[obj].y_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Z: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object z\n"); + return retval; + } + object_data[obj].z = data; + offset += bits; + break; + case TOUCH_OBJECT_N_X_WIDTH: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object x width\n"); + return retval; + } + object_data[obj].x_width = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Y_WIDTH: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object y width\n"); + return retval; + } + object_data[obj].y_width = data; + offset += bits; + break; + case TOUCH_OBJECT_N_TX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object tx position\n"); + return retval; + } + object_data[obj].tx_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_RX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object rx position\n"); + return retval; + } + object_data[obj].rx_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_FORCE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object force\n"); + return retval; + } + touch_data->force_data = data; + offset += bits; + break; + case TOUCH_FINGERPRINT_AREA_MEET: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object force\n"); + return retval; + } + touch_data->fingerprint_area_meet = data; + LOGN(tcm_hcd->pdev->dev.parent, + "fingerprint_area_meet = %x\n", + touch_data->fingerprint_area_meet); + offset += bits; + break; + case TOUCH_0D_BUTTONS_STATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get 0D buttons state\n"); + return retval; + } + touch_data->buttons_state = data; + offset += bits; + break; + case TOUCH_GESTURE_ID: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get gesture double tap\n"); + return retval; + } + touch_data->gesture_id = data; + offset += bits; + break; + case TOUCH_FRAME_RATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get frame rate\n"); + return retval; + } + touch_data->frame_rate = data; + offset += bits; + break; + case TOUCH_POWER_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get power IM\n"); + return retval; + } + touch_data->power_im = data; + offset += bits; + break; + case TOUCH_CID_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get CID IM\n"); + return retval; + } + touch_data->cid_im = data; + offset += bits; + break; + case TOUCH_RAIL_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get rail IM\n"); + return retval; + } + touch_data->rail_im = data; + offset += bits; + break; + case TOUCH_CID_VARIANCE_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get CID variance IM\n"); + return retval; + } + touch_data->cid_variance_im = data; + offset += bits; + break; + case TOUCH_NSM_FREQUENCY: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get NSM frequency\n"); + return retval; + } + touch_data->nsm_frequency = data; + offset += bits; + break; + case TOUCH_NSM_STATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get NSM state\n"); + return retval; + } + touch_data->nsm_state = data; + offset += bits; + break; + case TOUCH_GESTURE_DATA: + bits = config_data[idx++]; + if (bits != (sizeof(struct gesture_data)*8)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect Gesture Data length:%d\n", bits); + return -EIO; + } + + temp_data = (unsigned char *)&touch_data->gesture_data; + left_bits = bits; + i = 0; + while (left_bits) { + bits = (left_bits >= 8) ? 8 : left_bits; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get number of active objects\n"); + return retval; + } + temp_data[i++] = data&0xFF; + left_bits -= bits; + offset += bits; + } + break; + case TOUCH_NUM_OF_ACTIVE_OBJECTS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get number of active objects\n"); + return retval; + } + active_objects = data; + num_of_active_objects = true; + touch_data->num_of_active_objects = data; + offset += bits; + if (touch_data->num_of_active_objects == 0) { + if (0 == end_of_foreach) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid report, num_active and end_foreach are 0\n"); + return 0; + } + idx = end_of_foreach; + } + break; + case TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get num CPU cycles used since last frame\n"); + return retval; + } + touch_data->num_of_cpu_cycles = data; + offset += bits; + break; + case TOUCH_FACE_DETECT: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to detect face\n"); + return retval; + } + touch_data->fd_data = data; + offset += bits; + break; + case TOUCH_TUNING_GAUSSIAN_WIDTHS: + bits = config_data[idx++]; + offset += bits; + break; + case TOUCH_TUNING_SMALL_OBJECT_PARAMS: + bits = config_data[idx++]; + offset += bits; + break; + case TOUCH_TUNING_0D_BUTTONS_VARIANCE: + bits = config_data[idx++]; + offset += bits; + break; + default: + bits = config_data[idx++]; + offset += bits; + break; + } + } + +exit: + return 0; +} + +/** + * touch_report() - Report touch events + * + * Retrieve data from the touch report generated by the device and report touch + * events to the input subsystem. + */ +static void touch_report(void) +{ + int retval; + unsigned int idx; + unsigned int x; + unsigned int y; + unsigned int fod_overlap; + unsigned int temp; + unsigned int status; + unsigned int touch_count; + struct touch_data *touch_data; + struct object_data *object_data; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!touch_hcd->init_touch_ok) + return; + + if (touch_hcd->input_dev == NULL) + return; + + if (tcm_hcd->in_suspending) { + return; + } + + if (touch_hcd->suspend_touch) + return; + + mutex_lock(&touch_hcd->report_mutex); + + retval = touch_parse_report(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse touch report\n"); + goto exit; + } + + touch_data = &touch_hcd->touch_data; + object_data = touch_hcd->touch_data.object_data; + +#if WAKEUP_GESTURE + x = le2_to_uint(touch_data->gesture_data.x_pos); + y = le2_to_uint(touch_data->gesture_data.y_pos); + fod_overlap = touch_data->gesture_data.area[0]; + LOGD(tcm_hcd->pdev->dev.parent, + "Gesture Event:%02x, Gesture Data:x:%4d, y:%4d, major:%2d, fod_overlap:%2d\n", + touch_data->gesture_id, x, y, touch_data->gesture_data.area[1], touch_data->gesture_data.area[0]); + LOGD(tcm_hcd->pdev->dev.parent, + "fod_enabled: %d, finger_unlock_status:%d, fod_finger:%d\n", + tcm_hcd->fod_enabled, tcm_hcd->finger_unlock_status, tcm_hcd->fod_finger); + if (tcm_hcd->fod_enabled && (tcm_hcd->finger_unlock_status || tcm_hcd->fod_finger)) { + if (touch_data->gesture_id == GESTURE_TOUCH_AND_HOLD_DOWN_EVENT || + touch_data->gesture_id == GESTURE_TOUCH_AND_HOLD_MOVE_EVENT) { + tcm_hcd->fod_finger = true; + touch_fod_down_event(); + goto finger_pos; + } else if (touch_data->gesture_id == GESTURE_TOUCH_AND_HOLD_UP_EVENT) { + /* Todo: add customer FOD action. */ + if (tcm_hcd->fod_finger) { + tcm_hcd->fod_finger = false; + touch_fod_up_event(); + } + goto finger_pos; + } + } + + if (tcm_hcd->in_suspend && tcm_hcd->wakeup_gesture_enabled) { + if (touch_data->gesture_id == GESTURE_DOUBLE_TAP) { + LOGI(tcm_hcd->pdev->dev.parent, "Double TAP Detected\n"); + input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 1); + input_sync(touch_hcd->input_dev); + input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 0); + input_sync(touch_hcd->input_dev); + } else if (touch_data->gesture_id == GESTURE_SINGLE_TAP) { + LOGI(tcm_hcd->pdev->dev.parent, "Single TAP Detected\n"); + input_report_key(touch_hcd->input_dev, KEY_GOTO, 1); + input_sync(touch_hcd->input_dev); + input_report_key(touch_hcd->input_dev, KEY_GOTO, 0); + input_sync(touch_hcd->input_dev); + } + } + +#endif + + if (tcm_hcd->in_suspend) + goto exit; + +finger_pos: + touch_count = 0; + + for (idx = 0; idx < touch_hcd->max_objects; idx++) { + if (touch_hcd->prev_status[idx] == LIFT && + object_data[idx].status == LIFT) + status = NOP; + else + status = object_data[idx].status; + + switch (status) { + case LIFT: +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 0); +#endif + if (tcm_hcd->palm_sensor_enable && tcm_hcd->palm_enable_status) { + tcm_hcd->palm_enable_status = 0; + update_palm_sensor_value(tcm_hcd->palm_enable_status); + } + + break; + case PALM: + /* Todo: add customer code for palm handle */ + if (tcm_hcd->palm_sensor_enable && !tcm_hcd->palm_enable_status) { + LOGI(tcm_hcd->pdev->dev.parent, + "Palm %d detected, palm_sensor_enable = %d\n", + idx, tcm_hcd->palm_sensor_enable); + tcm_hcd->palm_enable_status = 1; + update_palm_sensor_value(tcm_hcd->palm_enable_status); + } + break; + case FINGER: + case GLOVED_FINGER: + x = object_data[idx].x_pos; + y = object_data[idx].y_pos; + if (bdata->swap_axes) { + temp = x; + x = y; + y = temp; + } + if (bdata->x_flip) + x = touch_hcd->input_params.max_x - x; + if (bdata->y_flip) + y = touch_hcd->input_params.max_y - y; +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 1); +#endif + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 1); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(touch_hcd->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(touch_hcd->input_dev, + ABS_MT_POSITION_Y, y); + + if (tcm_hcd->fod_enabled && tcm_hcd->fod_finger && tcm_hcd->finger_unlock_status) { + if (pre_overlap == fod_overlap) + fod_overlap++; + input_report_abs(touch_hcd->input_dev, + ABS_MT_TOUCH_MINOR, fod_overlap); + input_report_abs(touch_hcd->input_dev, + ABS_MT_TOUCH_MAJOR, touch_data->gesture_data.area[1]); + + } + +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + LOGD(tcm_hcd->pdev->dev.parent, + "Finger %d: x = %d, y = %d\n", + idx, x, y); + touch_count++; + break; + default: + break; + } + + touch_hcd->prev_status[idx] = object_data[idx].status; + } + + if (touch_count == 0) { + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 0); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + } + + input_sync(touch_hcd->input_dev); + +exit: + if (tcm_hcd->fod_finger) + pre_overlap = fod_overlap; + mutex_unlock(&touch_hcd->report_mutex); + + return; +} + +/** + * touch_set_input_params() - Set input parameters + * + * Set the input parameters of the input device based on the information + * retrieved from the application information packet. In addition, set up an + * array for tracking the status of touch objects. + */ +static int touch_set_input_params(void) +{ + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_POSITION_X, 0, touch_hcd->max_x, 0, 0); + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_POSITION_Y, 0, touch_hcd->max_y, 0, 0); + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_TOUCH_MINOR, 0, 100, 0, 0); + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_TOUCH_MAJOR, 0, 100, 0, 0); + + input_mt_init_slots(touch_hcd->input_dev, touch_hcd->max_objects, + INPUT_MT_DIRECT); + + touch_hcd->input_params.max_x = touch_hcd->max_x; + touch_hcd->input_params.max_y = touch_hcd->max_y; + touch_hcd->input_params.max_objects = touch_hcd->max_objects; + + if (touch_hcd->max_objects == 0) + return 0; + + kfree(touch_hcd->prev_status); + touch_hcd->prev_status = kzalloc(touch_hcd->max_objects, GFP_KERNEL); + if (!touch_hcd->prev_status) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd->prev_status\n"); + return -ENOMEM; + } + + return 0; +} + +/** + * touch_get_input_params() - Get input parameters + * + * Retrieve the input parameters to register with the input subsystem for + * the input device from the application information packet. In addition, + * the touch report configuration is retrieved and stored. + */ +static int touch_get_input_params(void) +{ + int retval; + unsigned int temp; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + app_info = &tcm_hcd->app_info; + touch_hcd->max_x = le2_to_uint(app_info->max_x); + touch_hcd->max_y = le2_to_uint(app_info->max_y); + touch_hcd->max_objects = le2_to_uint(app_info->max_objects); + + if (bdata->swap_axes) { + temp = touch_hcd->max_x; + touch_hcd->max_x = touch_hcd->max_y; + touch_hcd->max_y = temp; + } + + LOCK_BUFFER(tcm_hcd->config); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_TOUCH_REPORT_CONFIG, + NULL, + 0, + &tcm_hcd->config.buf, + &tcm_hcd->config.buf_size, + &tcm_hcd->config.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_TOUCH_REPORT_CONFIG)); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + UNLOCK_BUFFER(tcm_hcd->config); + + return 0; +} + +/** + * touch_set_input_dev() - Set up input device + * + * Allocate an input device, configure the input device based on the particular + * input events to be reported, and register the input device with the input + * subsystem. + */ +static int touch_set_input_dev(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + touch_hcd->input_dev = input_allocate_device(); + if (touch_hcd->input_dev == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate input device\n"); + return -ENODEV; + } + + touch_hcd->input_dev->name = TOUCH_INPUT_NAME; + touch_hcd->input_dev->phys = TOUCH_INPUT_PHYS_PATH; + touch_hcd->input_dev->id.product = SYNAPTICS_TCM_ID_PRODUCT; + touch_hcd->input_dev->id.version = SYNAPTICS_TCM_ID_VERSION; + touch_hcd->input_dev->dev.parent = tcm_hcd->pdev->dev.parent; + input_set_drvdata(touch_hcd->input_dev, tcm_hcd); + + set_bit(EV_SYN, touch_hcd->input_dev->evbit); + set_bit(EV_KEY, touch_hcd->input_dev->evbit); + set_bit(EV_ABS, touch_hcd->input_dev->evbit); + set_bit(BTN_TOUCH, touch_hcd->input_dev->keybit); + set_bit(BTN_TOOL_FINGER, touch_hcd->input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, touch_hcd->input_dev->propbit); +#endif + +#if WAKEUP_GESTURE + set_bit(KEY_WAKEUP, touch_hcd->input_dev->keybit); + set_bit(BTN_INFO, touch_hcd->input_dev->keybit); + set_bit(KEY_GOTO, touch_hcd->input_dev->keybit); + + input_set_capability(touch_hcd->input_dev, EV_KEY, KEY_WAKEUP); + input_set_capability(touch_hcd->input_dev, EV_KEY, BTN_INFO); + input_set_capability(touch_hcd->input_dev, EV_KEY, KEY_GOTO); +#endif + + retval = touch_set_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set input parameters\n"); + input_free_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + return retval; + } + + retval = input_register_device(touch_hcd->input_dev); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register input device\n"); + input_free_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + return retval; + } + + return 0; +} + +/** + * touch_set_report_config() - Set touch report configuration + * + * Send the SET_TOUCH_REPORT_CONFIG command to configure the format and content + * of the touch report. + */ +static int touch_set_report_config(void) +{ + int retval; + unsigned int idx; + unsigned int length; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + +#ifdef USE_DEFAULT_TOUCH_REPORT_CONFIG + return 0; +#endif + + app_info = &tcm_hcd->app_info; + length = le2_to_uint(app_info->max_touch_report_config_size); + + if (length < TOUCH_REPORT_CONFIG_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid maximum touch report config size\n"); + return -EINVAL; + } + + LOCK_BUFFER(touch_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &touch_hcd->out, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd->out.buf\n"); + UNLOCK_BUFFER(touch_hcd->out); + return retval; + } + + idx = 0; + +#if WAKEUP_GESTURE + touch_hcd->out.buf[idx++] = TOUCH_GESTURE_ID; + touch_hcd->out.buf[idx++] = 8; + touch_hcd->out.buf[idx++] = TOUCH_GESTURE_DATA; + touch_hcd->out.buf[idx++] = 8*6; +#endif + touch_hcd->out.buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_INDEX; + touch_hcd->out.buf[idx++] = 4; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION; + touch_hcd->out.buf[idx++] = 4; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_X_POSITION; + touch_hcd->out.buf[idx++] = 16; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_Y_POSITION; + touch_hcd->out.buf[idx++] = 16; + touch_hcd->out.buf[idx++] = TOUCH_FOREACH_END; + touch_hcd->out.buf[idx++] = TOUCH_END; + + LOCK_BUFFER(touch_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SET_TOUCH_REPORT_CONFIG, + touch_hcd->out.buf, + length, + &touch_hcd->resp.buf, + &touch_hcd->resp.buf_size, + &touch_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_SET_TOUCH_REPORT_CONFIG)); + UNLOCK_BUFFER(touch_hcd->resp); + UNLOCK_BUFFER(touch_hcd->out); + return retval; + } + + UNLOCK_BUFFER(touch_hcd->resp); + UNLOCK_BUFFER(touch_hcd->out); + + LOGN(tcm_hcd->pdev->dev.parent, + "Set touch config done\n"); + + return 0; +} + +/** + * touch_check_input_params() - Check input parameters + * + * Check if any of the input parameters registered with the input subsystem for + * the input device has changed. + */ +static int touch_check_input_params(void) +{ + unsigned int size; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (touch_hcd->max_x == 0 && touch_hcd->max_y == 0) + return 0; + + if (touch_hcd->input_params.max_objects != touch_hcd->max_objects) { + kfree(touch_hcd->touch_data.object_data); + size = sizeof(*touch_hcd->touch_data.object_data); + size *= touch_hcd->max_objects; + touch_hcd->touch_data.object_data = kzalloc(size, GFP_KERNEL); + if (!touch_hcd->touch_data.object_data) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd->touch_data.object_data\n"); + return -ENOMEM; + } + return 1; + } + + if (touch_hcd->input_params.max_x != touch_hcd->max_x) + return 1; + + if (touch_hcd->input_params.max_y != touch_hcd->max_y) + return 1; + + return 0; +} + +/** + * touch_set_input_reporting() - Configure touch report and set up new input + * device if necessary + * + * After a device reset event, configure the touch report and set up a new input + * device if any of the input parameters has changed after the device reset. + */ +static int touch_set_input_reporting(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode) || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Identifying mode = 0x%02x\n", + tcm_hcd->id_info.mode); + + return 0; + } + + touch_hcd->init_touch_ok = false; + + touch_free_objects(tcm_hcd); + + mutex_lock(&touch_hcd->report_mutex); + + retval = touch_set_report_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set report config\n"); + goto exit; + } + + retval = touch_get_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get input parameters\n"); + goto exit; + } + + retval = touch_check_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check input parameters\n"); + goto exit; + } else if (retval == 0) { + LOGN(tcm_hcd->pdev->dev.parent, + "Input parameters unchanged\n"); + goto exit; + } + + if (touch_hcd->input_dev != NULL) { + input_unregister_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + } + + retval = touch_set_input_dev(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input device\n"); + goto exit; + } + +exit: + mutex_unlock(&touch_hcd->report_mutex); + + touch_hcd->init_touch_ok = retval < 0 ? false : true; + + return retval; +} + + +int touch_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + touch_hcd = kzalloc(sizeof(*touch_hcd), GFP_KERNEL); + if (!touch_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd\n"); + return -ENOMEM; + } + + touch_hcd->tcm_hcd = tcm_hcd; + + mutex_init(&touch_hcd->report_mutex); + + INIT_BUFFER(touch_hcd->out, false); + INIT_BUFFER(touch_hcd->resp, false); + + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + goto err_set_input_reporting; + } + + tcm_hcd->report_touch = touch_report; + + return 0; + +err_set_input_reporting: + kfree(touch_hcd->touch_data.object_data); + kfree(touch_hcd->prev_status); + + RELEASE_BUFFER(touch_hcd->resp); + RELEASE_BUFFER(touch_hcd->out); + + kfree(touch_hcd); + touch_hcd = NULL; + + return retval; +} + +int touch_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + goto exit; + + tcm_hcd->report_touch = NULL; + + if (touch_hcd->input_dev) + input_unregister_device(touch_hcd->input_dev); + + kfree(touch_hcd->touch_data.object_data); + kfree(touch_hcd->prev_status); + + RELEASE_BUFFER(touch_hcd->resp); + RELEASE_BUFFER(touch_hcd->out); + + kfree(touch_hcd); + touch_hcd = NULL; + +exit: + + return 0; +} + +int touch_reinit(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + + if (!touch_hcd) { + retval = touch_init(tcm_hcd); + return retval; + } + + touch_free_objects(tcm_hcd); + + if (IS_NOT_FW_MODE(tcm_hcd->id_info.mode)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application mode is not running (firmware mode = %d)\n", + tcm_hcd->id_info.mode); + return 0; + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + } + + return retval; +} + +int touch_early_suspend(struct syna_tcm_hcd *tcm_hcd) +{ + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter---- %s\n",__func__); + if (!touch_hcd) + return 0; + + if (tcm_hcd->wakeup_gesture_enabled) + touch_hcd->suspend_touch = false; + else + touch_hcd->suspend_touch = true; + + if(touch_hcd->suspend_touch) + LOGE(tcm_hcd->pdev->dev.parent, "enter: touch_early_suspend---true---\n"); + + touch_free_objects(tcm_hcd); + LOGN(tcm_hcd->pdev->dev.parent, "-----exit---- %s\n",__func__); + return 0; +} + +int touch_suspend(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter---- %s, wakeup_gesture[0x%02x]\n", + __func__, ((tcm_hcd->wakeup_gesture_enabled) ? 1 : 0)); + + if (!touch_hcd) + return retval; + + touch_hcd->suspend_touch = true; + + if (tcm_hcd->wakeup_gesture_enabled) { + if (!touch_hcd->irq_wake) { + enable_irq_wake(tcm_hcd->irq); + touch_hcd->irq_wake = true; + } + + touch_hcd->suspend_touch = false; + + if (tcm_hcd->nonui_status != 1 && (tcm_hcd->fod_icon_status || tcm_hcd->aod_enable)) + tcm_hcd->gesture_type |= (0x0001<<13); + else { + /* disable single tap */ + tcm_hcd->gesture_type &= ~(0x0001<<13); + } + + if (tcm_hcd->nonui_status != 1 && tcm_hcd->doubletap_enable) + tcm_hcd->gesture_type |= 0x0001; + else { + /* disable double tap */ + tcm_hcd->gesture_type &= ~0x0001; + } + + LOGI(tcm_hcd->pdev->dev.parent, + "set DC_GESTURE_TYPE_ENABLE: 0x%02x\n", tcm_hcd->gesture_type); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_GESTURE_TYPE_ENABLE, + tcm_hcd->gesture_type); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable gesture type\n"); + goto exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, "set DC_IN_WAKEUP_GESTURE_MODE\n"); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + goto exit; + } + } + +exit: + + LOGN(tcm_hcd->pdev->dev.parent, "-----exit---- %s\n",__func__); + return retval; +} + +int touch_resume(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + LOGN(tcm_hcd->pdev->dev.parent, "-----enter---- %s\n",__func__); + + if (!touch_hcd) + return 0; + + touch_hcd->suspend_touch = false; + + if (tcm_hcd->wakeup_gesture_enabled) { + if (touch_hcd->irq_wake) { + disable_irq_wake(tcm_hcd->irq); + touch_hcd->irq_wake = false; + } + + LOGN(tcm_hcd->pdev->dev.parent, "clear DC_IN_WAKEUP_GESTURE_MODE\n"); + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } + } + LOGN(tcm_hcd->pdev->dev.parent, "-----exit---- %s\n",__func__); + return 0; +} + diff --git a/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_xiaomi_board_data.h b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_xiaomi_board_data.h new file mode 100755 index 000000000000..3639e14877d2 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_s3908p/synaptics_tcm_xiaomi_board_data.h @@ -0,0 +1,68 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2018 Scott Lin + * Copyright (C) 2018-2019 Ian Su + * Copyright (C) 2018-2019 Joey Zhou + * Copyright (C) 2018-2019 Yuehao Qiu + * Copyright (C) 2018-2019 Aaron Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_XIAOMI_BOARD_DATA_H_ +#define _SYNAPTICS_TCM_XIAOMI_BOARD_DATA_H_ + +#define SYNA_GRIP_PARAMETERS_SIZE 32 +#define SYNA_TOUCH_MODE_PARAMETERS_SIZE 5 +#define SYNA_CORNERFILTER_AREA_STEP_SIZE 4 +#define SYNA_DISPLAY_RESOLUTION_SIZE 2 + +struct syna_tcm_xiaomi_board_data { + int x_max; + int y_max; + unsigned int game_mode[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int active_mode[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int up_threshold[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int tolerance[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int edge_filter[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int panel_orien[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int report_rate[SYNA_TOUCH_MODE_PARAMETERS_SIZE]; + unsigned int cornerfilter_area_step0; + unsigned int cornerfilter_area_step1; + unsigned int cornerfilter_area_step2; + unsigned int cornerfilter_area_step3; + unsigned int cornerzone_filter_hor1[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int cornerzone_filter_hor2[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int cornerzone_filter_ver[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int deadzone_filter_hor[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int deadzone_filter_ver[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int edgezone_filter_hor[SYNA_GRIP_PARAMETERS_SIZE]; + unsigned int edgezone_filter_ver[SYNA_GRIP_PARAMETERS_SIZE]; +}; + +#endif diff --git a/include/linux/input/synaptics_tcm.h b/include/linux/input/synaptics_tcm.h index 1d650b451f86..5db962ac9b80 100644 --- a/include/linux/input/synaptics_tcm.h +++ b/include/linux/input/synaptics_tcm.h @@ -35,8 +35,11 @@ #define I2C_MODULE_NAME "synaptics_tcm_i2c" #define SPI_MODULE_NAME "synaptics_tcm_spi" +#define SYNAP_MAX_STR_LABLE_LEN 32 struct syna_tcm_board_data { + char avdd_name[SYNAP_MAX_STR_LABLE_LEN]; + char iovdd_name[SYNAP_MAX_STR_LABLE_LEN]; bool x_flip; bool y_flip; bool swap_axes;